From 6c3399293950ae09363b0a4a23e392f579425b5b Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 1 Dec 2025 11:31:49 -0800 Subject: [PATCH 001/538] WWSTCERT-9153 Leviton Decora Smart Wi-Fi (3rd Gen) 15A Switch WWSTCERT-9156 Leviton Decora Smart Wi-Fi (3rd Gen) 600W Dimmer --- drivers/SmartThings/matter-switch/fingerprints.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 0bc24ec7ca..25c3807042 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -646,6 +646,16 @@ matterManufacturer: vendorId: 0x109B productId: 0x1007 deviceProfileName: 3-button + - id: "4251/4113" + deviceLabel: "Decora Smart Wi-Fi (3rd Gen) 15A Switch" + vendorId: 0x109B + productId: 0x1011 + deviceProfileName: switch-binary + - id: "4251/4112" + deviceLabel: "Decora Smart Wi-Fi (3rd Gen) 600W Dimmer" + vendorId: 0x109B + productId: 0x1010 + deviceProfileName: switch-level #LeTianPai - id: "5163/4097" deviceLabel: LeTianPai Smart Light Bulb From 4983848c9ae5dcb2dbc9fb1cad178386c5be87f6 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:34 -0600 Subject: [PATCH 002/538] CHAD-17062: zigbee-bed lazy loading subdrivers --- drivers/SmartThings/zigbee-bed/src/init.lua | 20 +++---------- .../zigbee-bed/src/lazy_load_subdriver.lua | 15 ++++++++++ .../src/shus-mattress/can_handle.lua | 14 +++++++++ .../src/shus-mattress/custom_capabilities.lua | 15 ++-------- .../src/shus-mattress/custom_clusters.lua | 15 ++-------- .../src/shus-mattress/fingerprints.lua | 8 +++++ .../zigbee-bed/src/shus-mattress/init.lua | 29 +++---------------- .../zigbee-bed/src/sub_drivers.lua | 8 +++++ .../src/test/test_shus_mattress.lua | 15 ++-------- 9 files changed, 59 insertions(+), 80 deletions(-) create mode 100644 drivers/SmartThings/zigbee-bed/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-bed/src/shus-mattress/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-bed/src/shus-mattress/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-bed/src/sub_drivers.lua diff --git a/drivers/SmartThings/zigbee-bed/src/init.lua b/drivers/SmartThings/zigbee-bed/src/init.lua index d59cf4e012..9f464c38ce 100755 --- a/drivers/SmartThings/zigbee-bed/src/init.lua +++ b/drivers/SmartThings/zigbee-bed/src/init.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 + local capabilities = require "st.capabilities" local ZigbeeDriver = require "st.zigbee" @@ -20,9 +10,7 @@ local zigbee_bed_template = { supported_capabilities = { capabilities.refresh, }, - sub_drivers = { - require("shus-mattress"), - }, + sub_drivers = require("sub_drivers"), health_check = false, } diff --git a/drivers/SmartThings/zigbee-bed/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-bed/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-bed/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-bed/src/shus-mattress/can_handle.lua b/drivers/SmartThings/zigbee-bed/src/shus-mattress/can_handle.lua new file mode 100644 index 0000000000..0adcc5e46e --- /dev/null +++ b/drivers/SmartThings/zigbee-bed/src/shus-mattress/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_shus_products(opts, driver, device) + local FINGERPRINTS = require("shus-mattress.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("shus-mattress") + end + end + return false +end + +return is_shus_products diff --git a/drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_capabilities.lua b/drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_capabilities.lua index 2c3dab5292..fdd5ab0004 100755 --- a/drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_capabilities.lua +++ b/drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_capabilities.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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_clusters.lua b/drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_clusters.lua index 35baddbaa9..db220da460 100755 --- a/drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_clusters.lua +++ b/drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_clusters.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 data_types = require "st.zigbee.data_types" diff --git a/drivers/SmartThings/zigbee-bed/src/shus-mattress/fingerprints.lua b/drivers/SmartThings/zigbee-bed/src/shus-mattress/fingerprints.lua new file mode 100644 index 0000000000..2d6c80ee1a --- /dev/null +++ b/drivers/SmartThings/zigbee-bed/src/shus-mattress/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FINGERPRINTS = { + { mfr = "SHUS", model = "SX-1" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-bed/src/shus-mattress/init.lua b/drivers/SmartThings/zigbee-bed/src/shus-mattress/init.lua index 08109f35ce..65cf64e446 100755 --- a/drivers/SmartThings/zigbee-bed/src/shus-mattress/init.lua +++ b/drivers/SmartThings/zigbee-bed/src/shus-mattress/init.lua @@ -1,25 +1,12 @@ --- 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 capabilities = require "st.capabilities" local cluster_base = require "st.zigbee.cluster_base" local custom_clusters = require "shus-mattress/custom_clusters" local custom_capabilities = require "shus-mattress/custom_capabilities" -local FINGERPRINTS = { - { mfr = "SHUS", model = "SX-1" } -} -- ############################# -- # Attribute handlers define # @@ -155,14 +142,6 @@ end local function do_configure(driver, device) end -local function is_shus_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 -- ################# -- # Handlers bind # @@ -229,7 +208,7 @@ local shus_smart_mattress = { ["stateControl"] = process_capabilities_factory("stateControl","yoga") } }, - can_handle = is_shus_products + can_handle = require("shus-mattress.can_handle"), } return shus_smart_mattress diff --git a/drivers/SmartThings/zigbee-bed/src/sub_drivers.lua b/drivers/SmartThings/zigbee-bed/src/sub_drivers.lua new file mode 100644 index 0000000000..3e78841f3b --- /dev/null +++ b/drivers/SmartThings/zigbee-bed/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("shus-mattress"), +} +return sub_drivers 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 d3922a3bae..7e5ade0cf5 100755 --- a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua +++ b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.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 -- Mock out globals local test = require "integration_test" From aa71a9a9cd3b17cf00699b3dec8d24db6052ad83 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:19 -0600 Subject: [PATCH 003/538] CHAD-17070: zigbee-lock lazy loading of subdrivers --- .../zigbee-lock/src/configurations.lua | 15 ++------ drivers/SmartThings/zigbee-lock/src/init.lua | 23 +++---------- .../zigbee-lock/src/lazy_load_subdriver.lua | 15 ++++++++ .../src/lock-without-codes/can_handle.lua | 14 ++++++++ .../src/lock-without-codes/fingerprints.lua | 9 +++++ .../src/lock-without-codes/init.lua | 30 +++------------- .../zigbee-lock/src/lock_utils.lua | 15 ++------ .../zigbee-lock/src/samsungsds/can_handle.lua | 11 ++++++ .../zigbee-lock/src/samsungsds/init.lua | 20 +++-------- .../zigbee-lock/src/sub_drivers.lua | 11 ++++++ .../zigbee-lock/src/test/test_c2o_lock.lua | 15 ++------ .../src/test/test_generic_lock_migration.lua | 17 ++-------- ..._yale_fingerprint_bad_battery_reporter.lua | 15 ++------ .../zigbee-lock/src/test/test_zigbee_lock.lua | 15 ++------ .../test/test_zigbee_lock_code_migration.lua | 15 ++------ .../test_zigbee_yale-bad-battery-reporter.lua | 15 ++------ .../test_zigbee_yale-fingerprint-lock.lua | 15 ++------ .../zigbee-lock/src/test/test_zigbee_yale.lua | 15 ++------ .../src/yale-fingerprint-lock/can_handle.lua | 14 ++++++++ .../yale-fingerprint-lock/fingerprints.lua | 11 ++++++ .../src/yale-fingerprint-lock/init.lua | 32 +++-------------- .../zigbee-lock/src/yale/can_handle.lua | 11 ++++++ .../SmartThings/zigbee-lock/src/yale/init.lua | 23 +++---------- .../zigbee-lock/src/yale/sub_drivers.lua | 8 +++++ .../yale-bad-battery-reporter/can_handle.lua | 14 ++++++++ .../fingerprints.lua | 13 +++++++ .../yale/yale-bad-battery-reporter/init.lua | 34 +++---------------- 27 files changed, 177 insertions(+), 268 deletions(-) create mode 100644 drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua diff --git a/drivers/SmartThings/zigbee-lock/src/configurations.lua b/drivers/SmartThings/zigbee-lock/src/configurations.lua index a2429252b0..88e4e59f80 100644 --- a/drivers/SmartThings/zigbee-lock/src/configurations.lua +++ b/drivers/SmartThings/zigbee-lock/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 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-lock/src/init.lua b/drivers/SmartThings/zigbee-lock/src/init.lua index ce6894b868..94f5adc0c4 100644 --- a/drivers/SmartThings/zigbee-lock/src/init.lua +++ b/drivers/SmartThings/zigbee-lock/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 + -- Zigbee Driver utilities local defaults = require "st.zigbee.defaults" @@ -445,12 +435,7 @@ local zigbee_lock_driver = { [capabilities.refresh.commands.refresh.NAME] = refresh } }, - sub_drivers = { - require("samsungsds"), - require("yale"), - require("yale-fingerprint-lock"), - require("lock-without-codes") - }, + sub_drivers = require("sub_drivers"), lifecycle_handlers = { doConfigure = do_configure, added = device_added, diff --git a/drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/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-lock/src/lock-without-codes/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua new file mode 100644 index 0000000000..543e43a8b1 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_lock_without_codes(opts, driver, device) + local FINGERPRINTS = require("lock-without-codes.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("lock-without-codes") + end + end + return false +end + +return can_handle_lock_without_codes diff --git a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua new file mode 100644 index 0000000000..63ae82b46c --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local LOCK_WITHOUT_CODES_FINGERPRINTS = { + { model = "E261-KR0B0Z0-HA" }, + { mfr = "Danalock", model = "V3-BTZB" } +} + +return LOCK_WITHOUT_CODES_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua index 7272991459..e5c6de3408 100644 --- a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/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 configurationMap = require "configurations" local clusters = require "st.zigbee.zcl.clusters" @@ -19,19 +9,7 @@ local capabilities = require "st.capabilities" local DoorLock = clusters.DoorLock local PowerConfiguration = clusters.PowerConfiguration -local LOCK_WITHOUT_CODES_FINGERPRINTS = { - { model = "E261-KR0B0Z0-HA" }, - { mfr = "Danalock", model = "V3-BTZB" } -} -local function can_handle_lock_without_codes(opts, driver, device) - for _, fingerprint in ipairs(LOCK_WITHOUT_CODES_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 configuration = configurationMap.get_device_configuration(device) @@ -95,7 +73,7 @@ local lock_without_codes = { } } }, - can_handle = can_handle_lock_without_codes + can_handle = require("lock-without-codes.can_handle"), } return lock_without_codes diff --git a/drivers/SmartThings/zigbee-lock/src/lock_utils.lua b/drivers/SmartThings/zigbee-lock/src/lock_utils.lua index 0a36a9685e..a02a59963c 100644 --- a/drivers/SmartThings/zigbee-lock/src/lock_utils.lua +++ b/drivers/SmartThings/zigbee-lock/src/lock_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 utils = require "st.utils" local capabilities = require "st.capabilities" local json = require "st.json" diff --git a/drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua new file mode 100644 index 0000000000..c483b2fe27 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function samsungsds_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "SAMSUNG SDS" then + return true, require("samsungsds") + end + return false +end + +return samsungsds_can_handle diff --git a/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua b/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua index b529dd3fd1..fff290df5d 100644 --- a/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/samsungsds/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 clusters = require "st.zigbee.zcl.clusters" @@ -112,9 +102,7 @@ local samsung_sds_driver = { added = device_added, init = device_init }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "SAMSUNG SDS" - end + can_handle = require("samsungsds.can_handle"), } return samsung_sds_driver diff --git a/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua b/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua new file mode 100644 index 0000000000..ff4bf8980d --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/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("samsungsds"), + lazy_load_if_possible("yale"), + lazy_load_if_possible("yale-fingerprint-lock"), + lazy_load_if_possible("lock-without-codes"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua index b6fa3d1323..146c628b8b 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua index ed4ce6e3cc..f287300f60 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_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 2023 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" @@ -45,4 +34,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-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua index d499d7ff66..4f50c3c24a 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.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-lock/src/test/test_zigbee_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua index 80d10d092e..3ed037cd54 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.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-lock/src/test/test_zigbee_lock_code_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua index 1aa9432933..7950e3f62d 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.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-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua index ee1745e3b7..b8f4c386d9 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.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-lock/src/test/test_zigbee_yale-fingerprint-lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua index 2255c063a3..7cda71cdb3 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.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-lock/src/test/test_zigbee_yale.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua index 34b6881028..75ad49a1f5 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.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-lock/src/yale-fingerprint-lock/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua new file mode 100644 index 0000000000..a80632bf80 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local yale_fingerprint_lock_models = function(opts, driver, device) + local FINGERPRINTS = require("yale-fingerprint-lock.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("yale-fingerprint-lock") + end + end + return false +end + +return yale_fingerprint_lock_models diff --git a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua new file mode 100644 index 0000000000..b3db27d719 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local YALE_FINGERPRINT_LOCK = { + { mfr = "ASSA ABLOY iRevo", model = "iZBModule01" }, + { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, + { mfr = "ASSA ABLOY iRevo", model = "0700000001" }, + { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } +} + +return YALE_FINGERPRINT_LOCK diff --git a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua index 9d0a0b4148..b78d043784 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/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" @@ -19,21 +9,7 @@ local LockCodes = capabilities.lockCodes local YALE_FINGERPRINT_MAX_CODES = 0x1E -local YALE_FINGERPRINT_LOCK = { - { mfr = "ASSA ABLOY iRevo", model = "iZBModule01" }, - { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, - { mfr = "ASSA ABLOY iRevo", model = "0700000001" }, - { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } -} -local yale_fingerprint_lock_models = function(opts, driver, device) - for _, fingerprint in ipairs(YALE_FINGERPRINT_LOCK) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local handle_max_codes = function(driver, device, value) device:emit_event(LockCodes.maxCodes(YALE_FINGERPRINT_MAX_CODES), { visibility = { displayed = false } }) @@ -48,7 +24,7 @@ local yale_fingerprint_lock_driver = { } } }, - can_handle = yale_fingerprint_lock_models + can_handle = require("yale-fingerprint-lock.can_handle"), } return yale_fingerprint_lock_driver diff --git a/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua new file mode 100644 index 0000000000..54340c7811 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function yale_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "ASSA ABLOY iRevo" or device:get_manufacturer() == "Yale" then + return true, require("yale") + end + return false +end + +return yale_can_handle diff --git a/drivers/SmartThings/zigbee-lock/src/yale/init.lua b/drivers/SmartThings/zigbee-lock/src/yale/init.lua index 73e984036e..8ba98b2aa8 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale/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 + + -- Zigbee Spec Utils local clusters = require "st.zigbee.zcl.clusters" @@ -151,11 +142,7 @@ local yale_door_lock_driver = { [LockCodes.commands.setCode.NAME] = set_code } }, - - sub_drivers = { require("yale.yale-bad-battery-reporter") }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "ASSA ABLOY iRevo" or device:get_manufacturer() == "Yale" - end + sub_drivers = require("yale.sub_drivers"), } return yale_door_lock_driver diff --git a/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua b/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua new file mode 100644 index 0000000000..4b546979d3 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/yale/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("yale.yale-bad-battery-reporter"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua new file mode 100644 index 0000000000..67169e9268 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_bad_yale_lock_models = function(opts, driver, device) + local FINGERPRINTS = require("yale.yale-bad-battery-reporter.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("yale.yale-bad-battery-reporter") + end + end + return false +end + +return is_bad_yale_lock_models diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua new file mode 100644 index 0000000000..cbb7c3404f --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local BAD_YALE_LOCK_FINGERPRINTS = { + { mfr = "Yale", model = "YRD220/240 TSDB" }, + { mfr = "Yale", model = "YRL220 TS LL" }, + { mfr = "Yale", model = "YRD210 PB DB" }, + { mfr = "Yale", model = "YRL210 PB LL" }, + { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, + { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } +} + +return BAD_YALE_LOCK_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua index 59fdbf228b..3b77f32563 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua @@ -1,37 +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 clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" -local BAD_YALE_LOCK_FINGERPRINTS = { - { mfr = "Yale", model = "YRD220/240 TSDB" }, - { mfr = "Yale", model = "YRL220 TS LL" }, - { mfr = "Yale", model = "YRD210 PB DB" }, - { mfr = "Yale", model = "YRL210 PB LL" }, - { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, - { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } -} -local is_bad_yale_lock_models = function(opts, driver, device) - for _, fingerprint in ipairs(BAD_YALE_LOCK_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local battery_report_handler = function(driver, device, value) device:emit_event(capabilities.battery.battery(value.value)) @@ -46,7 +20,7 @@ local bad_yale_driver = { } } }, - can_handle = is_bad_yale_lock_models + can_handle = require("yale.yale-bad-battery-reporter.can_handle"), } return bad_yale_driver From 1224494b99a15132f177b51cd0ccbcd082da2499 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:25 -0600 Subject: [PATCH 004/538] CHAD-17071: zigbee-presence-sensor lazy loading of subdrivers --- .../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 ++----------- 10 files changed, 67 insertions(+), 81 deletions(-) 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 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" From 395c266db2229774c92ce75dc77616dd3ef256ff Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 1 Dec 2025 11:34:12 -0800 Subject: [PATCH 005/538] WWSTCERT-9170 Linkind Smart Light Bulb --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 0bc24ec7ca..78817141db 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -131,6 +131,11 @@ 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 #Bosch Smart Home - id: "4617/12310" deviceLabel: Plug Compact [M] From d17c89a912f8a863e61276fdb57aa5e1fa5446bc Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 1 Dec 2025 11:37:26 -0800 Subject: [PATCH 006/538] WWSTCERT-9159 Osram SMART MAT B40 TW 827 FR E14 --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 78817141db..6350a5b06a 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -945,6 +945,11 @@ matterManufacturer: 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 #Shelly - id: "5264/1" deviceLabel: Shelly Plug S MTR Gen3 From 0257e120c0c1e6bebb96244479786362ea3ca789 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 1 Dec 2025 11:44:48 -0800 Subject: [PATCH 007/538] WWSTCERT-9195 WiZ Gradient Light Bars WWSTCERT-9198 WiZ Gradient Light Bars WWSTCERT-9209 WiZ Gradient Floor lamp WWSTCERT-9212 WiZ Gradient Floor lamp --- .../matter-switch/fingerprints.yml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 6350a5b06a..7703b38b87 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -2865,6 +2865,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 From bdd990bd6c6c8d98b475fec58036d83af32f4ecf Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 8 Dec 2025 10:02:30 -0800 Subject: [PATCH 008/538] add missing copyright notice --- .../src/frient-sensor/air-quality/init.lua | 3 +++ 1 file changed, 3 insertions(+) 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 index e33c1b07e7..e9ca8e85cf 100644 --- 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 @@ -1,3 +1,6 @@ +-- 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" From 39a2d7385b3a2e18467590c19d85995e6ecfb2ac Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:29 -0600 Subject: [PATCH 009/538] CHAD-17072: zigbee-range-extender lazy load subdrivers --- .../src/frient/can_handle.lua | 11 ++++++++++ .../zigbee-range-extender/src/frient/init.lua | 22 +++++-------------- .../zigbee-range-extender/src/init.lua | 20 ++++------------- .../src/lazy_load_subdriver.lua | 15 +++++++++++++ .../zigbee-range-extender/src/sub_drivers.lua | 8 +++++++ .../test_frient_zigbee_range_extender.lua | 15 ++----------- .../src/test/test_zigbee_extend.lua | 15 ++----------- 7 files changed, 47 insertions(+), 59 deletions(-) create mode 100644 drivers/SmartThings/zigbee-range-extender/src/frient/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-range-extender/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-range-extender/src/sub_drivers.lua diff --git a/drivers/SmartThings/zigbee-range-extender/src/frient/can_handle.lua b/drivers/SmartThings/zigbee-range-extender/src/frient/can_handle.lua new file mode 100644 index 0000000000..9967c0edf8 --- /dev/null +++ b/drivers/SmartThings/zigbee-range-extender/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() == "REXZB-111") then + return true, require("frient") + end + return false +end + +return frient_can_handle diff --git a/drivers/SmartThings/zigbee-range-extender/src/frient/init.lua b/drivers/SmartThings/zigbee-range-extender/src/frient/init.lua index f4a754bfeb..fef5d8470a 100644 --- a/drivers/SmartThings/zigbee-range-extender/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-range-extender/src/frient/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.zigbee.zcl.clusters" @@ -70,9 +60,7 @@ local frient_range_extender = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "frient A/S" and (device:get_model() == "REXZB-111") - end + can_handle = require("frient.can_handle"), } -return frient_range_extender \ No newline at end of file +return frient_range_extender diff --git a/drivers/SmartThings/zigbee-range-extender/src/init.lua b/drivers/SmartThings/zigbee-range-extender/src/init.lua index 2523a3d45e..565aa74a28 100644 --- a/drivers/SmartThings/zigbee-range-extender/src/init.lua +++ b/drivers/SmartThings/zigbee-range-extender/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 defaults = require "st.zigbee.defaults" @@ -32,9 +22,7 @@ local zigbee_range_driver_template = { } }, health_check = false, - sub_drivers = { - require("frient") - } + sub_drivers = require("sub_drivers"), } defaults.register_for_default_handlers(zigbee_range_driver_template, zigbee_range_driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zigbee-range-extender/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-range-extender/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-range-extender/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-range-extender/src/sub_drivers.lua b/drivers/SmartThings/zigbee-range-extender/src/sub_drivers.lua new file mode 100644 index 0000000000..2f30e461ee --- /dev/null +++ b/drivers/SmartThings/zigbee-range-extender/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("frient"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua b/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua index 8f63df1705..4aa03d2bca 100644 --- a/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua +++ b/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.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-range-extender/src/test/test_zigbee_extend.lua b/drivers/SmartThings/zigbee-range-extender/src/test/test_zigbee_extend.lua index 39404005fa..c76e228cef 100644 --- a/drivers/SmartThings/zigbee-range-extender/src/test/test_zigbee_extend.lua +++ b/drivers/SmartThings/zigbee-range-extender/src/test/test_zigbee_extend.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" From 6ca2f11fba0a0918176fe6971f2c2a4ce79f0f26 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:14 -0600 Subject: [PATCH 010/538] CHAD-17075: zigbee-smoke-detector lazy load subdrivers --- .../src/aqara-gas/can_handle.lua | 14 +++++++++ .../src/aqara-gas/fingerprints.lua | 8 +++++ .../src/aqara-gas/init.lua | 30 +++---------------- .../src/aqara/can_handle.lua | 14 +++++++++ .../src/aqara/fingerprints.lua | 8 +++++ .../zigbee-smoke-detector/src/aqara/init.lua | 30 +++---------------- .../src/frient/can_handle.lua | 11 +++++++ .../zigbee-smoke-detector/src/frient/init.lua | 20 +++---------- .../zigbee-smoke-detector/src/init.lua | 24 ++++----------- .../src/lazy_load_subdriver.lua | 15 ++++++++++ .../zigbee-smoke-detector/src/sub_drivers.lua | 10 +++++++ .../src/test/test_aqara_gas_detector.lua | 15 ++-------- .../src/test/test_aqara_smoke_detector.lua | 15 ++-------- .../src/test/test_frient_heat_detector.lua | 17 ++--------- .../src/test/test_frient_smoke_detector.lua | 15 ++-------- .../src/test/test_zigbee_smoke_detector.lua | 15 ++-------- 16 files changed, 108 insertions(+), 153 deletions(-) create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/aqara/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/aqara/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/frient/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/can_handle.lua b/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/can_handle.lua new file mode 100644 index 0000000000..41ba568b80 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/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-gas.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("aqara-gas") + end + end + return false +end + +return is_aqara_products diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/fingerprints.lua b/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/fingerprints.lua new file mode 100644 index 0000000000..61cc4d9730 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/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_gas.acn02" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/init.lua index 297701272d..e0474243ce 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/init.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/init.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 + local data_types = require "st.zigbee.data_types" local cluster_base = require "st.zigbee.cluster_base" local capabilities = require "st.capabilities" @@ -31,9 +21,6 @@ local PRIVATE_LIFE_TIME_ATTRIBUTE_ID = 0x0128 local PRIVATE_GAS_ZONE_STATUS_ATTRIBUTE_ID = 0x013A -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.sensor_gas.acn02" } -} local CONFIGURATIONS = { @@ -125,14 +112,6 @@ local function self_check_attr_handler(self, device, zone_status, zb_rx) PRIVATE_CLUSTER_ID, PRIVATE_SELF_CHECK_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true)) end -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_init(driver, device) if CONFIGURATIONS ~= nil then @@ -186,8 +165,7 @@ local aqara_gas_detector_handler = { [startSelfCheckCommandName] = self_check_attr_handler }, }, - can_handle = is_aqara_products + can_handle = require("aqara-gas.can_handle"), } return aqara_gas_detector_handler - diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/aqara/can_handle.lua b/drivers/SmartThings/zigbee-smoke-detector/src/aqara/can_handle.lua new file mode 100644 index 0000000000..e4453597ed --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/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-smoke-detector/src/aqara/fingerprints.lua b/drivers/SmartThings/zigbee-smoke-detector/src/aqara/fingerprints.lua new file mode 100644 index 0000000000..d296aa8518 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/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_smoke.acn03" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/aqara/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/aqara/init.lua index 91aba0a5e9..1950d1f923 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/aqara/init.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 + local data_types = require "st.zigbee.data_types" local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" @@ -30,9 +20,6 @@ local PRIVATE_SMOKE_ZONE_STATUS_ATTRIBUTE_ID = 0x013A local PowerConfiguration = clusters.PowerConfiguration -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.sensor_smoke.acn03" } -} local CONFIGURATIONS = { @@ -98,14 +85,6 @@ local function self_check_attr_handler(self, device, zone_status, zb_rx) PRIVATE_CLUSTER_ID, PRIVATE_SELF_CHECK_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true)) end -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_init(driver, device) battery_defaults.build_linear_voltage_init(2.6, 3.0)(driver, device) @@ -154,8 +133,7 @@ local aqara_gas_detector_handler = { [startSelfCheckCommandName] = self_check_attr_handler }, }, - can_handle = is_aqara_products + can_handle = require("aqara.can_handle"), } return aqara_gas_detector_handler - diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/frient/can_handle.lua b/drivers/SmartThings/zigbee-smoke-detector/src/frient/can_handle.lua new file mode 100644 index 0000000000..5154ec5fe6 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/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() == "SMSZB-120" or device:get_model() == "HESZB-120") then + return true, require("frient") + end + return false +end + +return frient_can_handle diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/frient/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/frient/init.lua index 1d6d85311a..d3a3604c26 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/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 battery_defaults = require "st.zigbee.defaults.battery_defaults" local capabilities = require "st.capabilities" @@ -270,8 +260,6 @@ local frient_smoke_sensor = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "frient A/S" and (device:get_model() == "SMSZB-120" or device:get_model() == "HESZB-120") - end + can_handle = require("frient.can_handle"), } 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 941c3312da..fe64260c11 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-smoke-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 capabilities = require "st.capabilities" local ZigbeeDriver = require "st.zigbee" @@ -25,11 +15,7 @@ local zigbee_smoke_driver_template = { capabilities.temperatureMeasurement, capabilities.temperatureAlarm }, - sub_drivers = { - require("frient"), - require("aqara-gas"), - require("aqara") - }, + sub_drivers = require("sub_drivers"), ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, health_check = false, } @@ -37,4 +23,4 @@ local zigbee_smoke_driver_template = { defaults.register_for_default_handlers(zigbee_smoke_driver_template, zigbee_smoke_driver_template.supported_capabilities, {native_capability_attrs_enabled = true}) local zigbee_smoke_driver = ZigbeeDriver("zigbee-smoke-detector", zigbee_smoke_driver_template) -zigbee_smoke_driver:run() \ No newline at end of file +zigbee_smoke_driver:run() diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-smoke-detector/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-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-smoke-detector/src/sub_drivers.lua b/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua new file mode 100644 index 0000000000..e6c5e004f3 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-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("frient"), + lazy_load_if_possible("aqara-gas"), + lazy_load_if_possible("aqara"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua index 6c194351a6..4c1dcb1552 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.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" local zigbee_test_utils = require "integration_test.zigbee_test_utils" diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua index bd0fae534c..1af67b36cd 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.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" local zigbee_test_utils = require "integration_test.zigbee_test_utils" 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 index aec09a8435..364aae57a0 100644 --- 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 @@ -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" @@ -580,4 +569,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-smoke-detector/src/test/test_frient_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_smoke_detector.lua index 574f08ae76..fa012a321b 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 @@ -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/src/test/test_zigbee_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_zigbee_smoke_detector.lua index 00d0b43c10..b27713fa19 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_zigbee_smoke_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_zigbee_smoke_detector.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" From 7235ce7b6c3b51710fddf61a4e830069cb5da2a5 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:23 -0600 Subject: [PATCH 011/538] CHAD-17081: zigbee-watering-kit lazy loading of sub-drivers --- .../SmartThings/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 +++++++++++ .../zigbee-watering-kit/src/thirdreality/init.lua | 7 ++++--- 5 files changed, 42 insertions(+), 6 deletions(-) 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 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 From 9278b2bc4427fd886967ab0feda1f93714ab9e61 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:38 -0600 Subject: [PATCH 012/538] CHAD-17084: zwave-bulb lazy loading of sub-drivers --- .../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 ++------- 14 files changed, 123 insertions(+), 154 deletions(-) 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 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" From 440180b7a61b3f87dfc4ac68421effbe7c4ac358 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:15 -0600 Subject: [PATCH 013/538] CHAD-17086: zwave-electric-meter lazy loading of sub-drivers --- .../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 ++-------- 17 files changed, 126 insertions(+), 165 deletions(-) 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 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" From 873a288be15989fd44189e6414189f76a753d1cb Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:32 -0600 Subject: [PATCH 014/538] CHAD-17094: zwave-valve lazy loading of sub-drivers --- drivers/SmartThings/zwave-valve/src/init.lua | 21 ++++--------------- .../src/inverse_valve/can_handle.lua | 11 ++++++++++ .../zwave-valve/src/inverse_valve/init.lua | 20 ++++-------------- .../zwave-valve/src/lazy_load_subdriver.lua | 18 ++++++++++++++++ .../zwave-valve/src/sub_drivers.lua | 8 +++++++ .../src/test/test_inverse.valve.lua | 16 +++----------- .../zwave-valve/src/test/test_zwave_valve.lua | 16 +++----------- 7 files changed, 51 insertions(+), 59 deletions(-) create mode 100644 drivers/SmartThings/zwave-valve/src/inverse_valve/can_handle.lua create mode 100644 drivers/SmartThings/zwave-valve/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-valve/src/sub_drivers.lua diff --git a/drivers/SmartThings/zwave-valve/src/init.lua b/drivers/SmartThings/zwave-valve/src/init.lua index 43de12dcc4..cea5b877c1 100644 --- a/drivers/SmartThings/zwave-valve/src/init.lua +++ b/drivers/SmartThings/zwave-valve/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 @@ -26,10 +16,7 @@ local driver_template = { supported_capabilities = { capabilities.valve, }, - sub_drivers = { - -- Fortrezz and Zooz valves treat open as "off" and close as "on" - require("inverse_valve") - }, + sub_drivers = require("sub_drivers"), } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zwave-valve/src/inverse_valve/can_handle.lua b/drivers/SmartThings/zwave-valve/src/inverse_valve/can_handle.lua new file mode 100644 index 0000000000..b36334f2c9 --- /dev/null +++ b/drivers/SmartThings/zwave-valve/src/inverse_valve/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function inverse_valve_can_handle(opts, driver, device, ...) + if device.zwave_manufacturer_id == 0x0084 or device.zwave_manufacturer_id == 0x027A then + return true, require("inverse_valve") + end + return false +end + +return inverse_valve_can_handle diff --git a/drivers/SmartThings/zwave-valve/src/inverse_valve/init.lua b/drivers/SmartThings/zwave-valve/src/inverse_valve/init.lua index 510e79019b..853bada351 100644 --- a/drivers/SmartThings/zwave-valve/src/inverse_valve/init.lua +++ b/drivers/SmartThings/zwave-valve/src/inverse_valve/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 @@ -55,9 +45,7 @@ local inverse_valve = { [capabilities.valve.commands.close.NAME] = close_handler } }, - can_handle = function(opts, driver, device, ...) - return device.zwave_manufacturer_id == 0x0084 or device.zwave_manufacturer_id == 0x027A - end + can_handle = require("inverse_valve.can_handle"), } return inverse_valve diff --git a/drivers/SmartThings/zwave-valve/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-valve/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-valve/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-valve/src/sub_drivers.lua b/drivers/SmartThings/zwave-valve/src/sub_drivers.lua new file mode 100644 index 0000000000..b43856e0cd --- /dev/null +++ b/drivers/SmartThings/zwave-valve/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("inverse_valve"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-valve/src/test/test_inverse.valve.lua b/drivers/SmartThings/zwave-valve/src/test/test_inverse.valve.lua index db7d6be656..68c563061b 100644 --- a/drivers/SmartThings/zwave-valve/src/test/test_inverse.valve.lua +++ b/drivers/SmartThings/zwave-valve/src/test/test_inverse.valve.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-valve/src/test/test_zwave_valve.lua b/drivers/SmartThings/zwave-valve/src/test/test_zwave_valve.lua index 4a21568119..efa0c42599 100644 --- a/drivers/SmartThings/zwave-valve/src/test/test_zwave_valve.lua +++ b/drivers/SmartThings/zwave-valve/src/test/test_zwave_valve.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" From 7d48d6c094aab90868ea1e16ff175e37a05b663d Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:10 -0600 Subject: [PATCH 015/538] CHAD-17082: zigbee-water-leak-sensor lazy loading of sub-drivers --- .../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 +++--------- 32 files changed, 212 insertions(+), 301 deletions(-) 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 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 From 37d3453784d695b9d51e846aec29724a0dedfb23 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:39 -0600 Subject: [PATCH 016/538] CHAD-17093: zwave-siren lazy loading of sub-drivers --- .../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 +++------------- 44 files changed, 323 insertions(+), 434 deletions(-) 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 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 From f4299ed95d0db938e82b61de6aefd4a03f21ad3d Mon Sep 17 00:00:00 2001 From: Steven Green Date: Sat, 13 Dec 2025 11:53:28 -0800 Subject: [PATCH 017/538] WWSTCERT-9475 eWeLink Zigbee Motion Sensor --- drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) 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 From 6059b79dd6eb0110cbb7c205b09a75508df0bdbb Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 8 Dec 2025 16:00:40 -0800 Subject: [PATCH 018/538] Merge pull request #2605 from SmartThingsCommunity/new_device/WWSTCERT-9289 WWSTCERT-9289 Osram SMART MAT A53 DIM FILGD 824 E27 --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 7703b38b87..a13168ac8b 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -950,6 +950,11 @@ matterManufacturer: 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 #Shelly - id: "5264/1" deviceLabel: Shelly Plug S MTR Gen3 From e5da1759324cc048cd19ca0af071b5ae90b5d54b Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 8 Dec 2025 17:09:26 -0800 Subject: [PATCH 019/538] Merge pull request #2566 from SmartThingsCommunity/new_device/WWSTCERT-9005 WWSTCERT-9005 Aqara Light Switch H2 US (2 Buttons, 2 Channel) --- .../matter-switch/fingerprints.yml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index a13168ac8b..26190c9c6a 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 From ff06e0a1773d38a691df32f966aa4f2e34d840e1 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:27:33 -0600 Subject: [PATCH 020/538] Merge pull request #2615 from SmartThingsCommunity/add/aqs-modular-update-support Matter Sensor: Allow modular profile updates in infoChanged --- .../air_quality_sensor_utils/device_configuration.lua | 1 + .../air_quality_sensor/air_quality_sensor_utils/fields.lua | 2 ++ .../matter-sensor/src/sub_drivers/air_quality_sensor/init.lua | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) 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 index a30eaed0f8..6e1f1a5d81 100644 --- 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 @@ -71,6 +71,7 @@ function DeviceConfiguration.match_profile(device) 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. 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 index 1e4658c99e..0d3c5dd21c 100644 --- 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 @@ -26,6 +26,8 @@ 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 = { 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 index 869f2d337b..a0427ac665 100644 --- 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 @@ -66,11 +66,12 @@ function AirQualitySensorLifecycleHandlers.device_init(driver, device) end function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, args) - if device.profile.id ~= args.old_st_store.profile.id then + 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 From 5e3005cf65a54ecdfefb2419356385a47df1ecc4 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 11 Dec 2025 13:16:43 -0800 Subject: [PATCH 021/538] Merge pull request #2613 from SmartThingsCommunity/new_device/WWSTCERT-9352 WWSTCERT-9352 Ikea Bilresa --- drivers/SmartThings/matter-switch/fingerprints.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 26190c9c6a..66564683a8 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -572,6 +572,17 @@ matterManufacturer: vendorId: 0x1189 productId: 0x0633 deviceProfileName: plug-binary +#Ikea + - id: "4476/32768" + deviceLabel: BILRESA scroll wheel + vendorId: 0x117C + productId: 0x8000 + deviceProfileName: 2-button-battery + - id: "4476/32769" + deviceLabel: BILRESA dual button + vendorId: 0x117C + productId: 0x8001 + deviceProfileName: 2-button-battery #Innovation Matters - id: "4978/1" deviceLabel: M2D Bridge From 0804d59da7b1fd2e220c69783a21be6e767d94d0 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 15 Dec 2025 10:36:11 -0800 Subject: [PATCH 022/538] adjust base profile for ikea bilresa scroll (#2657) --- drivers/SmartThings/matter-switch/fingerprints.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 66564683a8..69f429bd6b 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -577,7 +577,7 @@ matterManufacturer: deviceLabel: BILRESA scroll wheel vendorId: 0x117C productId: 0x8000 - deviceProfileName: 2-button-battery + deviceProfileName: ikea-scroll - id: "4476/32769" deviceLabel: BILRESA dual button vendorId: 0x117C From de20a2c8ca5ccd82021a76dd4e011caf5338f727 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 11 Dec 2025 13:16:54 -0800 Subject: [PATCH 023/538] Merge pull request #2612 from SmartThingsCommunity/new_device/ikea_sensors WWSTCERT-9343 --- .../matter-sensor/fingerprints.yml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index 8836c276cd..0db00e11d6 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -84,6 +84,32 @@ matterManufacturer: 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 From 3633e423b07947600a25fb807f32e9b56bce87f8 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 11 Dec 2025 13:21:11 -0800 Subject: [PATCH 024/538] 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 --- .../matter-switch/fingerprints.yml | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 69f429bd6b..e35c16beb2 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -840,6 +840,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) From 6c4a2e3d0860bc666358b0ce75013ccb34189ef0 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 11 Dec 2025 13:21:47 -0800 Subject: [PATCH 025/538] Govee devices (#2631) --- .../matter-switch/fingerprints.yml | 274 +++++++++++++++++- 1 file changed, 272 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index e35c16beb2..8c04892566 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -467,7 +467,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 @@ -1136,7 +1406,7 @@ matterManufacturer: vendorId: 0x1189 productId: 0x0A9B deviceProfileName: light-level-colorTemperature - - id: 4489/2686 + - id: "4489/2686" deviceLabel: SMART MAT A53 DIM FILGD 824 E27 vendorId: 0x1189 productId: 0x0A7E From cdf161901cb30409b2da6f4acd0a15df8581dbba Mon Sep 17 00:00:00 2001 From: Steven Green Date: Fri, 12 Dec 2025 14:15:18 -0800 Subject: [PATCH 026/538] Merge pull request #2652 from SmartThingsCommunity/fixup/govee_duplicates comment out govee duplicate fingerprints --- .../matter-switch/fingerprints.yml | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 8c04892566..5e65aa24fe 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -508,11 +508,11 @@ matterManufacturer: 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/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 @@ -568,11 +568,11 @@ matterManufacturer: 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/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 @@ -618,11 +618,11 @@ matterManufacturer: 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/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 @@ -653,11 +653,11 @@ matterManufacturer: 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/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 @@ -713,11 +713,11 @@ matterManufacturer: 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/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 From ea51e6c2cb18f343410c34a63ef5f1b5d23a2a62 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:57:48 -0600 Subject: [PATCH 027/538] Merge pull request #2604 from SmartThingsCommunity/add/reset-energy-meter Matter Switch: Add ResetEnergyMeter command support --- .../SmartThings/matter-switch/src/init.lua | 3 + .../switch_handlers/attribute_handlers.lua | 5 + .../switch_handlers/capability_handlers.lua | 23 ++- .../matter-switch/src/switch_utils/fields.lua | 1 + .../matter-switch/src/switch_utils/utils.lua | 3 +- .../src/test/test_electrical_sensor_set.lua | 193 +++++++++++++++++- 6 files changed, 225 insertions(+), 3 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 3c6c6a302a..5436d57baf 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -274,6 +274,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 }, diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index 40b1f6a44c..756a402de6 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -284,6 +284,11 @@ function AttributeHandlers.energy_imported_factory(is_periodic_report) 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) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua index 77927ef1d2..2c5241de3a 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua @@ -168,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/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index ddd554069a..f0fd0166b4 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -155,6 +155,7 @@ 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 68d588f2dd..b258688234 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -76,7 +76,8 @@ function utils.mired_to_kelvin(value, minOrMax) end function utils.get_product_override_field(device, override_key) - if fields.vendor_overrides[device.manufacturer_info.vendor_id] + 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] 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 index 3df62056ca..412c773540 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -161,8 +161,17 @@ local periodic_report_val_23 = { 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 @@ -431,7 +440,6 @@ test.register_coroutine_test( 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)}) @@ -463,6 +471,189 @@ test.register_coroutine_test( { 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", { From cba8c07d0a530d1f877a57b1f1d35cc673513d79 Mon Sep 17 00:00:00 2001 From: haedo-doo Date: Tue, 16 Dec 2025 10:30:14 +0900 Subject: [PATCH 028/538] a follow-up PR for #2468 that removes the no longer needed code from init --- .../SmartThings/zigbee-switch/src/aqara-light/init.lua | 9 --------- .../zigbee-switch/src/test/test_aqara_led_bulb.lua | 3 +-- .../zigbee-switch/src/test/test_aqara_light.lua | 3 +-- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua b/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua index 06d4bf1cc7..2192716b21 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua @@ -54,18 +54,9 @@ 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 }, 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 b2f9359850..ce4bb13957 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 @@ -38,7 +38,6 @@ 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) @@ -52,7 +51,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 }))) + 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 940131b3e1..c521ba3e44 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua @@ -40,7 +40,6 @@ 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) @@ -54,7 +53,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 }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ minimum = 2700, maximum = 6000 }))) end ) From 5da406dc72153d4fe05ccce92ba99faa4cfe59b7 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 15 Dec 2025 15:22:12 -0600 Subject: [PATCH 029/538] CHAD-17152: Lazy loading of matter-appliance sub-drivers --- .../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 +- 58 files changed, 386 insertions(+), 471 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 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) From 50e0092e2267a5e94010e6bb836c20631d6140b4 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:22 -0600 Subject: [PATCH 030/538] CHAD-17098: zwave-window-treatment lazy loading of sub-drivers --- .../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 ++------- 34 files changed, 274 insertions(+), 303 deletions(-) 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 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 From 1251d573ea0049bee5c757bd67213add78976b96 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Thu, 8 Jan 2026 15:41:20 -0600 Subject: [PATCH 031/538] fixup: Fixed utf-8 errors in zigbee-motion-sensor tests --- .../src/test/test_ikea_motion.lua | 4 ++-- .../src/test/test_smartsense_motion_sensor.lua | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) 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 0173ac431d..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 @@ -123,7 +123,7 @@ test.register_coroutine_test( test.register_coroutine_test( "ZDO Message handler and adding hub to group", function() - local binding_table = mgmt_bind_response.BindingTableListRecord("j�Àþÿ^ÏÐ", 0x01, 0x0006, 0x01, 0xB9F2) + local binding_table = mgmt_bind_response.BindingTableListRecord("\x6A\x9D\xC0\xFE\xFF\x5E\xCF\xD0", 0x01, 0x0006, 0x01, 0xB9F2) local response = mgmt_bind_response.MgmtBindResponse({ status = 0x00, total_binding_table_entry_count = 0x01, @@ -144,7 +144,7 @@ test.register_coroutine_test( test.register_coroutine_test( "Request all binding table entries and fall back to group 0x0000", function() - local binding_table_long = mgmt_bind_response.BindingTableListRecord("j�Àþÿ^ÏÐ", 0x01, 0x0006, 0x03, "DEADBEEF", 0x01) + local binding_table_long = mgmt_bind_response.BindingTableListRecord("\x6A\x9D\xC0\xFE\xFF\x5E\xCF\xD0", 0x01, 0x0006, 0x03, "DEADBEEF", 0x01) local response = mgmt_bind_response.MgmtBindResponse({ status = 0x00, total_binding_table_entry_count = 0x02, 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 007c8448bf..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 @@ -61,7 +61,7 @@ test.register_coroutine_test( function() test.socket.zigbee:__queue_receive({ mock_device.id, - build_motion_status_message(mock_device, "|") + build_motion_status_message(mock_device, "\x7C") }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) @@ -75,7 +75,7 @@ test.register_coroutine_test( function() test.socket.zigbee:__queue_receive({ mock_device.id, - build_motion_status_message(mock_device, "~") + build_motion_status_message(mock_device, "\x7E") }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) @@ -89,7 +89,7 @@ test.register_coroutine_test( function() test.socket.zigbee:__queue_receive({ mock_device.id, - build_motion_status_message(mock_device, "X") + build_motion_status_message(mock_device, "\x58") }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(70))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) @@ -103,7 +103,7 @@ test.register_coroutine_test( function() test.socket.zigbee:__queue_receive({ mock_device.id, - build_motion_status_message(mock_device, "Z") + build_motion_status_message(mock_device, "\x5A") }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(70))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) @@ -117,7 +117,7 @@ test.register_coroutine_test( function() test.socket.zigbee:__queue_receive({ mock_device.id, - build_motion_status_message(mock_device, "½") + build_motion_status_message(mock_device, "\xBD") }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.lqi(50))) @@ -130,7 +130,7 @@ test.register_coroutine_test( function() test.socket.zigbee:__queue_receive({ mock_device.id, - build_motion_status_message(mock_device, "¿") + build_motion_status_message(mock_device, "\xBF") }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.lqi(50))) @@ -143,7 +143,7 @@ test.register_coroutine_test( function() test.socket.zigbee:__queue_receive({ mock_device.id, - build_motion_status_message(mock_device, "0") + build_motion_status_message(mock_device, "\x30") }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(0))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) From 3de35fd2f8cddbfade10a58d61fe38e41c9d2659 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Sat, 13 Dec 2025 22:00:05 -0600 Subject: [PATCH 032/538] update subscribe for main driver and camera subdriver --- .../SmartThings/matter-switch/src/init.lua | 44 +++- .../sub_drivers/camera/camera_utils/utils.lua | 79 +++--- .../src/sub_drivers/camera/init.lua | 2 +- .../switch_handlers/attribute_handlers.lua | 14 +- .../matter-switch/src/switch_utils/fields.lua | 131 +--------- .../matter-switch/src/switch_utils/utils.lua | 70 +++-- .../src/test/test_eve_energy.lua | 2 +- .../src/test/test_matter_bridge.lua | 19 -- .../src/test/test_matter_camera.lua | 242 +++++++++--------- .../test_matter_multi_button_switch_mcd.lua | 139 +++++----- .../src/test/test_matter_switch.lua | 1 + .../test/test_matter_switch_device_types.lua | 27 +- .../test_multi_switch_parent_child_lights.lua | 2 +- .../test_multi_switch_parent_child_plugs.lua | 2 +- 14 files changed, 348 insertions(+), 426 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 5a834c0385..6655f81e56 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -49,6 +49,10 @@ 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 @@ -68,7 +72,7 @@ function SwitchLifecycleHandlers.info_changed(driver, device, event, args) device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) ) elseif device.network_type == device_lib.NETWORK_TYPE_CHILD then - switch_utils.update_subscriptions(device:get_parent_device()) -- parent device required to scan through EPs and update subscriptions + device:get_parent_device():subscribe() -- parent device required to send subscription requests end end @@ -87,7 +91,8 @@ 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 - switch_utils.update_subscriptions(device) + 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. @@ -290,7 +295,40 @@ 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 = { switch_utils.lazy_load_if_possible("sub_drivers.aqara_cube"), switch_utils.lazy_load("sub_drivers.camera"), 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 index dcdd936950..f08e757553 100644 --- 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 @@ -193,7 +193,8 @@ function CameraUtils.subscribe(device) clusters.CameraAvStreamManagement.attributes.StatusLightBrightness }, [capabilities.switch.ID] = { - clusters.CameraAvStreamManagement.attributes.StatusLightEnabled + clusters.CameraAvStreamManagement.attributes.StatusLightEnabled, + clusters.OnOff.attributes.OnOff }, [capabilities.videoStreamSettings.ID] = { clusters.CameraAvStreamManagement.attributes.RateDistortionTradeOffPoints, @@ -223,8 +224,26 @@ function CameraUtils.subscribe(device) }, [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, @@ -238,56 +257,26 @@ function CameraUtils.subscribe(device) } } - for capability, attr_list in pairs(camera_subscribed_attributes) do - if device:supports_capability_by_id(capability) then - for _, attr in pairs(attr_list) do - device:add_subscribed_attribute(attr) - end - end - end - for capability, event_list in pairs(camera_subscribed_events) do - if device:supports_capability_by_id(capability) then - for _, event in pairs(event_list) do - device:add_subscribed_event(event) - end - end - end + 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 = {}, {}, {}, {} - -- match_profile is called from the CameraAvStreamManagement AttributeList handler, - -- so the subscription needs to be added here first if #device:get_endpoints(clusters.CameraAvStreamManagement.ID) > 0 then - device:add_subscribed_attribute(clusters.CameraAvStreamManagement.attributes.AttributeList) + local ib = im.InteractionInfoBlock(nil, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.attributes.AttributeList.ID) + subscribe_request:with_info_block(ib) end - -- Add subscription for attributes specific to child devices - if device:get_field(fields.IS_PARENT_CHILD_DEVICE) then - for _, ep in ipairs(device.endpoints or {}) do - local id = 0 - for _, dt in ipairs(ep.device_types or {}) do - if dt.device_type_id ~= fields.DEVICE_TYPE_ID.GENERIC_SWITCH then - id = math.max(id, dt.device_type_id) - end - end - for _, attr in pairs(fields.device_type_attribute_map[id] or {}) do - device:add_subscribed_attribute(attr) - 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 - local im = require "st.matter.interaction_model" - local subscribed_attributes = device:get_field("__subscribed_attributes") or {} - local subscribed_events = device:get_field("__subscribed_events") or {} - local subscribe_request = im.InteractionRequest(im.InteractionRequest.RequestType.SUBSCRIBE, {}) - for _, attributes in pairs(subscribed_attributes) do - for _, ib in pairs(attributes) do - subscribe_request:with_info_block(ib) - end - end - for _, events in pairs(subscribed_events) do - for _, ib in pairs(events) do - subscribe_request:with_info_block(ib) - end - end if #subscribe_request.info_blocks > 0 then device:send(subscribe_request) 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 index e8d5aad8fa..f13589ff41 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -49,10 +49,10 @@ 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 - device:subscribe() end end diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index 1a36001d6c..e8dfe3f120 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -139,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) @@ -157,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) @@ -173,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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index f5b85e1009..f670b154c8 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -1,27 +1,13 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 -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 @@ -192,119 +178,4 @@ SwitchFields.TRANSITION_TIME = 0 --1/10ths of a second SwitchFields.OPTIONS_MASK = 0x01 SwitchFields.OPTIONS_OVERRIDE = 0x01 - -SwitchFields.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 -} - -SwitchFields.device_type_attribute_map = { - [SwitchFields.DEVICE_TYPE_ID.LIGHT.ON_OFF] = { - clusters.OnOff.attributes.OnOff - }, - [SwitchFields.DEVICE_TYPE_ID.LIGHT.DIMMABLE] = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel - }, - [SwitchFields.DEVICE_TYPE_ID.LIGHT.COLOR_TEMPERATURE] = { - 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.DEVICE_TYPE_ID.LIGHT.EXTENDED_COLOR] = { - 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 - }, - [SwitchFields.DEVICE_TYPE_ID.ON_OFF_PLUG_IN_UNIT] = { - clusters.OnOff.attributes.OnOff - }, - [SwitchFields.DEVICE_TYPE_ID.DIMMABLE_PLUG_IN_UNIT] = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel - }, - [SwitchFields.DEVICE_TYPE_ID.SWITCH.ON_OFF_LIGHT] = { - clusters.OnOff.attributes.OnOff - }, - [SwitchFields.DEVICE_TYPE_ID.SWITCH.DIMMER] = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel - }, - [SwitchFields.DEVICE_TYPE_ID.SWITCH.COLOR_DIMMER] = { - 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 - }, - [SwitchFields.DEVICE_TYPE_ID.GENERIC_SWITCH] = { - clusters.PowerSource.attributes.BatPercentRemaining, - clusters.Switch.events.InitialPress, - clusters.Switch.events.LongPress, - clusters.Switch.events.ShortRelease, - clusters.Switch.events.MultiPressComplete - }, - [SwitchFields.DEVICE_TYPE_ID.ELECTRICAL_SENSOR] = { - clusters.ElectricalPowerMeasurement.attributes.ActivePower, - clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, - clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported - } -} - -return SwitchFields \ No newline at end of file +return SwitchFields diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 88c846501a..c6299fba95 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -433,27 +433,63 @@ function utils.lazy_load_if_possible(sub_driver_name) end end -function utils.update_subscriptions(device) - local default_endpoint_id = 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 ~= default_endpoint_id then - 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.DEVICE_TYPE_ID.GENERIC_SWITCH and - attr ~= clusters.PowerSource.attributes.BatPercentRemaining and - attr ~= clusters.PowerSource.attributes.BatChargeLevel then - device:add_subscribed_event(attr) - else - device:add_subscribed_attribute(attr) +--- 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..attr_id] then + local ib = im.InteractionInfoBlock(nil, cluster_id, attr_id) + subscribe_request:with_info_block(ib) + attributes_seen[cluster_id..attr_id] = true + 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..event_id] then + local ib = im.InteractionInfoBlock(nil, cluster_id, nil, event_id) + subscribe_request:with_info_block(ib) + events_seen[cluster_id..event_id] = true + end + end + capabilities_seen[capability.id] = true -- only loop through any capability once end end end - device:subscribe() +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 + + 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_eve_energy.lua b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua index 1598f953a4..a189b1e5fc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -55,7 +55,7 @@ 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("power-energy-powerConsumption.yml"), + profile = t_utils.get_profile_definition("plug-energy-powerConsumption.yml"), manufacturer_info = { vendor_id = 0x130A, product_id = 0x0050, 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 1aaef32d37..c696815a3e 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua @@ -59,34 +59,15 @@ 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() - 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.device_lifecycle:__queue_receive({ mock_bridge.id, "added" }) - test.socket.matter:__expect_send({mock_bridge.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_bridge.id, "init" }) - test.socket.matter:__expect_send({mock_bridge.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_bridge.id, "doConfigure" }) mock_bridge:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 95bef23644..a327c39c31 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -122,6 +122,19 @@ local mock_device = test.mock_device.build_test_matter_device({ 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() @@ -153,60 +166,125 @@ end test.set_test_init_function(test_init) -local function update_device_profile() - test.socket.matter:__set_channel_ordering("relaxed") - local uint32 = require "st.matter.data_types.Uint32" - local expected_metadata = { - optional_component_capabilities = { - { - "main", - { - "videoCapture2", - "cameraViewportSettings", - "localMediaStorage", - "audioRecording", - "cameraPrivacyMode", - "imageControl", - "hdr", - "nightVision", - "mechanicalPanTiltZoom", - "videoStreamSettings", - "zoneManagement", - "webrtc", - "motionSensor", - "sounds", - } - }, +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", { - "statusLed", - { - "switch", - "mode" - } - }, + "videoCapture2", + "cameraViewportSettings", + "localMediaStorage", + "audioRecording", + "cameraPrivacyMode", + "imageControl", + "hdr", + "nightVision", + "mechanicalPanTiltZoom", + "videoStreamSettings", + "zoneManagement", + "webrtc", + "motionSensor", + "sounds", + } + }, + { + "statusLed", { - "speaker", - { - "audioMute", - "audioVolume" - } - }, + "switch", + "mode" + } + }, + { + "speaker", { - "microphone", - { - "audioMute", - "audioVolume" - } - }, + "audioMute", + "audioVolume" + } + }, + { + "microphone", { - "doorbell", - { - "button" - } + "audioMute", + "audioVolume" } }, - profile = "camera" - } + { + "doorbell", + { + "button" + } + } + }, + profile = "camera" +} + +local function update_device_profile() + local uint32 = require "st.matter.data_types.Uint32" test.socket.matter:__queue_receive({ mock_device.id, clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device, CAMERA_EP, { @@ -219,6 +297,7 @@ local function update_device_profile() 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( @@ -258,77 +337,12 @@ local function update_device_profile() {"setSoftRecordingPrivacyMode", "setSoftLivestreamPrivacyMode"} )) ) - 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.ColorControl.attributes.ColorMode, - clusters.OccupancySensing.attributes.Occupancy, - clusters.Switch.server.events.InitialPress, - clusters.Switch.server.events.LongPress, - clusters.Switch.server.events.ShortRelease, - clusters.Switch.server.events.MultiPressComplete - } 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}))) - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) end -- Matter Handler UTs 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 49fd69d0ec..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 @@ -102,7 +102,7 @@ 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, @@ -166,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, @@ -199,10 +210,10 @@ end local function test_init() 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) - local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device) - for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do + -- added sets a bunch of fields on the device, and calls init + 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}) @@ -225,12 +236,6 @@ local function test_init() }) expect_configure_buttons() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) - - 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 @@ -350,63 +355,45 @@ 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 unsup_mock_device = mock_device_mcd_unsupported_switch_device_type - local subscribe_request = cluster_subscribe_list[1]:subscribe(unsup_mock_device) - for _, cluster in ipairs(cluster_subscribe_list) do + 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.device_lifecycle:__queue_receive({ unsup_mock_device.id, "added" }) @@ -423,14 +410,24 @@ test.register_coroutine_test( parent_device_id = unsup_mock_device.id, parent_assigned_child_key = string.format("%d", 7) }) + 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() 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 subscribe_request = cluster_subscribe_list[1]:subscribe(unsup_mock_device) - for i, cluster in ipairs(cluster_subscribe_list) do + + 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 @@ -449,9 +446,10 @@ 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[1]:subscribe(mock_device) - for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do + 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}) @@ -465,8 +463,8 @@ test.register_coroutine_test( test.register_coroutine_test( "Test info changed event with parent device profile update", function() - 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 local updated_device_profile = t_utils.get_profile_definition("light-level-3-button.yml") @@ -480,6 +478,7 @@ test.register_coroutine_test( 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" }) @@ -487,6 +486,22 @@ test.register_coroutine_test( 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 +) -- run the tests test.run_registered_tests() 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 25bc0fdfaa..6a5ecd2cc4 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -126,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) 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 f90855a308..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 @@ -439,14 +439,8 @@ 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, @@ -483,13 +477,10 @@ 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" }) @@ -597,25 +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.ColorControl.attributes.ColorMode, + 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}) 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 e218371df4..c0ed244d51 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 @@ -245,7 +245,7 @@ local function test_init_parent_child_endpoints_non_sequential() clusters.ColorControl.attributes.CurrentSaturation, clusters.ColorControl.attributes.CurrentX, clusters.ColorControl.attributes.CurrentY, - clusters.ColorControl.attributes.ColorMode + clusters.ColorControl.attributes.ColorMode, } local subscribe_request = cluster_subscribe_list[1]:subscribe(unsup_mock_device) for i, cluster in ipairs(cluster_subscribe_list) do 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 9ac3642a63..09fa39c4f6 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 @@ -239,7 +239,7 @@ local function test_init_parent_child_endpoints_non_sequential() clusters.ColorControl.attributes.CurrentSaturation, clusters.ColorControl.attributes.CurrentX, clusters.ColorControl.attributes.CurrentY, - clusters.ColorControl.attributes.ColorMode, + clusters.ColorControl.attributes.ColorMode, } local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_parent_child_endpoints_non_sequential) for i, cluster in ipairs(cluster_subscribe_list) do From 8422dd5f5fd2fef9f854d4a9b1c1fcf93f8300fa Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Mon, 12 Jan 2026 10:51:01 -0600 Subject: [PATCH 033/538] remove unused helper function, logic moved to generic child handler --- .../src/switch_utils/device_configuration.lua | 37 +------------------ 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 132cce2ae7..7ee4b6bf64 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -21,7 +21,7 @@ local SwitchDeviceConfiguration = {} local ButtonDeviceConfiguration = {} local FanDeviceConfiguration = {} -function ChildConfiguration.create_child_devices(driver, device, server_cluster_ep_ids, default_endpoint_id, assign_profile_fn) +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 @@ -99,39 +99,6 @@ function SwitchDeviceConfiguration.assign_profile_for_onoff_ep(device, server_on return generic_profile or "switch-binary" end -function SwitchDeviceConfiguration.create_or_update_child_devices(driver, device, server_onoff_ep_ids, default_endpoint_id) - if #server_onoff_ep_ids == 1 and server_onoff_ep_ids[1] == default_endpoint_id then -- no children will be created - return - end - - table.sort(server_onoff_ep_ids) - for device_num, ep_id in ipairs(server_onoff_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 = SwitchDeviceConfiguration.assign_profile_for_onoff_ep(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 - -- 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 @@ -245,7 +212,7 @@ function DeviceConfiguration.match_profile(driver, device) 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_child_devices(driver, device, server_onoff_ep_ids, default_endpoint_id, SwitchDeviceConfiguration.assign_profile_for_onoff_ep) + 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 From 1d911f954e93765a76ad1cdff00f8146539c5891 Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:34:50 -0600 Subject: [PATCH 034/538] 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. --- .../camera_utils/device_configuration.lua | 6 +- .../sub_drivers/camera/camera_utils/utils.lua | 51 +++++++++++----- .../src/test/test_matter_camera.lua | 59 ++++++++++++++++++- 3 files changed, 97 insertions(+), 19 deletions(-) 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 index 6626729c78..c84337736d 100644 --- 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 @@ -121,8 +121,6 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr 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) - 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 if status_light_enabled_present then table.insert(status_led_component_capabilities, capabilities.switch.ID) @@ -147,6 +145,10 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr 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 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 index f08e757553..a93e757c16 100644 --- 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 @@ -120,28 +120,49 @@ function CameraUtils.profile_changed(synced_components, prev_components) return false end -function CameraUtils.optional_capabilities_list_changed(optional_capabilities, prev_component_list) - local prev_optional_capabilities = {} - for idx, comp in pairs(prev_component_list or {}) do - local cap_list = {} - for _, capability in pairs(comp.capabilities or {}) do - table.insert(cap_list, capability.id) +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 - table.insert(prev_optional_capabilities, {idx, cap_list}) - end - if #optional_capabilities ~= #prev_optional_capabilities then - return true + previous_component_count = previous_component_count + 1 end - for _, capability in pairs(optional_capabilities or {}) do - if not switch_utils.tbl_contains(prev_optional_capabilities, capability) then + + 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 - end - for _, capability in pairs(prev_optional_capabilities or {}) do - if not switch_utils.tbl_contains(optional_capabilities, capability) then + + 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 diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index a327c39c31..1028197590 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -5,6 +5,7 @@ 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() @@ -284,7 +285,6 @@ local expected_metadata = { } local function update_device_profile() - local uint32 = require "st.matter.data_types.Uint32" test.socket.matter:__queue_receive({ mock_device.id, clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device, CAMERA_EP, { @@ -292,8 +292,9 @@ local function update_device_profile() uint32(clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) }) }) - test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)}) 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} ) @@ -1830,5 +1831,59 @@ test.register_coroutine_test( 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() From cdf4d494305f9c6a569457c367bc1abb1509aa7b Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 12 Jan 2026 14:48:40 -0800 Subject: [PATCH 035/538] WWSTCERT-9480 Sensereo MSC-1 --- drivers/SmartThings/matter-sensor/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index 0db00e11d6..4295c87a93 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -215,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 From 689a6a50f9802ade090bf4030666c7a75ed6a5e3 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 12 Jan 2026 14:59:35 -0800 Subject: [PATCH 036/538] WWSTCERT-9605 Kwikset Aura Reach --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 +++++ drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 6c7a223caf..71a3acd65f 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -62,6 +62,11 @@ matterManufacturer: 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/src/new-matter-lock/init.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua index 1d59b1ab14..3a2b78234b 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -85,6 +85,7 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x158B, 0x0001}, -- Deasino, DS-MT01 {0x10E1, 0x2002}, -- VDA {0x1421, 0x0042}, -- Kwikset Halo Select Plus + {0x1421, 0x0081}, -- Kwikset Aura Reach } local battery_support = { From 2a71e6a592d936e2a6de4d7113b95f32e0effe50 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 12 Jan 2026 15:11:31 -0800 Subject: [PATCH 037/538] WWSTCERT-9450 SONOFF Zigbee Temperature and Humidity Sensor --- drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml b/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml index 68359710fe..e5ad9e571a 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml +++ b/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml @@ -79,7 +79,7 @@ zigbeeManufacturer: model: TH01 deviceProfileName: humidity-temp-battery - id: eWeLink/SNZB-02P - deviceLabel: eWeLink Multipurpose Sensor + deviceLabel: SONOFF Zigbee Temperature and Humidity Sensor manufacturer: eWeLink model: SNZB-02P deviceProfileName: humidity-temp-battery From 40c934686454de1a2376e8af40ad95c6e186a988 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 13 Jan 2026 14:57:41 -0800 Subject: [PATCH 038/538] WWSTCERT-9799 Eve Thermostat (Europe) --- drivers/SmartThings/matter-thermostat/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-thermostat/fingerprints.yml b/drivers/SmartThings/matter-thermostat/fingerprints.yml index 6cd56d3a24..30e99caadb 100644 --- a/drivers/SmartThings/matter-thermostat/fingerprints.yml +++ b/drivers/SmartThings/matter-thermostat/fingerprints.yml @@ -33,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 From 05cce9cc7f3829677e93bee14faceac41674ca9c Mon Sep 17 00:00:00 2001 From: Taejun Park Date: Mon, 15 Dec 2025 20:33:41 +0900 Subject: [PATCH 039/538] Add the ReadAttrbitue or refresh code for the initial state --- .../zigbee-illuminance-sensor/src/init.lua | 8 +++ .../src/test/test_illuminance_sensor.lua | 2 + .../src/test/test_multi_switch_no_master.lua | 3 + ...metering_plug_power_consumption_report.lua | 69 ++++++++++++++++++- .../init.lua | 1 + .../zigbee-window-treatment/src/axis/init.lua | 1 + .../src/feibit/init.lua | 1 + .../test_zigbee_window_treatment_axis.lua | 2 + .../test_zigbee_window_treatment_feibit.lua | 2 + 9 files changed, 88 insertions(+), 1 deletion(-) 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-switch/src/test/test_multi_switch_no_master.lua b/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_no_master.lua index 6603a0831c..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 @@ -469,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_zigbee_metering_plug_power_consumption_report.lua b/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_metering_plug_power_consumption_report.lua index 4514a90cbb..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 @@ -6,6 +6,8 @@ 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" @@ -17,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 } } } } @@ -81,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/zigbee-metering-plug-power-consumption-report/init.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-metering-plug-power-consumption-report/init.lua index 17dc0ee2bd..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 @@ -24,6 +24,7 @@ end local do_configure = function(self, device) device:configure() + device:refresh() end local device_init = function(self, device) 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/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 ) From 8841dcaced89c52b06fd84d20a9cfd51c58d9fca Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Wed, 14 Jan 2026 15:05:52 -0600 Subject: [PATCH 040/538] Matter Camera: Add vision.clipAnalysis capability Add vision.clipAnalysis to the Matter Camera profile. --- drivers/SmartThings/matter-switch/profiles/camera.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/SmartThings/matter-switch/profiles/camera.yml b/drivers/SmartThings/matter-switch/profiles/camera.yml index 7a46099cf6..7f62319984 100644 --- a/drivers/SmartThings/matter-switch/profiles/camera.yml +++ b/drivers/SmartThings/matter-switch/profiles/camera.yml @@ -47,6 +47,9 @@ components: - id: motionSensor version: 1 optional: true + - id: vision.clipAnalysis + version: 1 + optional: true - id: firmwareUpdate version: 1 - id: refresh From a218cda82359844bee48039fa908b69f4eeef58e Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Thu, 15 Jan 2026 14:30:47 -0600 Subject: [PATCH 041/538] 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. --- .gitignore | 2 ++ tools/run_driver_tests.py | 34 +++++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) 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/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 9ba659d064eef34bcd87a23cc9fab2d63ed5f09a Mon Sep 17 00:00:00 2001 From: Steven Green Date: Fri, 5 Dec 2025 11:44:03 -0800 Subject: [PATCH 042/538] WWSTCERT-9289 eufy FamiLock E40 --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 +++++ drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 6c7a223caf..52734b703b 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -56,6 +56,11 @@ matterManufacturer: 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 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 1d59b1ab14..7e8c35ec9e 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -76,6 +76,7 @@ local NEW_MATTER_LOCK_PRODUCTS = { {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 From 3cae7e15e2972411289e09c60a8fe64422163b9a Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 15 Jan 2026 13:43:28 -0800 Subject: [PATCH 043/538] WWSTCERT-9960 HooRii Window Covering --- .../SmartThings/matter-window-covering/fingerprints.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-window-covering/fingerprints.yml b/drivers/SmartThings/matter-window-covering/fingerprints.yml index 532ba1f304..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 @@ -176,7 +182,7 @@ matterManufacturer: deviceLabel: WISTAR WSCMXF-LED Smart Vertical Blind Motor vendorId: 0x1457 productId: 0x0018 - deviceProfileName: window-covering-tilt + deviceProfileName: window-covering-tilt - id: "5207/20" deviceLabel: WISTAR WSCMQ Smart Curtain Motor vendorId: 0x1457 From e0805bafd3271ced84f5d5ef4470cd8485c67ede Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:49:26 +0900 Subject: [PATCH 044/538] Add vid/pid of the Aqara U400 to new-matter-lock (#2709) Signed-off-by: Hunsup Jung --- drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua | 1 + 1 file changed, 1 insertion(+) 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 1d59b1ab14..9d3f0357dc 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,7 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x115f, 0x2802}, -- AQARA, U200 {0x115f, 0x2801}, -- AQARA, U300 {0x115f, 0x2807}, -- AQARA, U200 Lite + {0x115f, 0x2804}, -- AQARA, U400 {0x147F, 0x0001}, -- U-tec {0x147F, 0x0008}, -- Ultraloq, Bolt Smart Matter Door Lock {0x144F, 0x4002}, -- Yale, Linus Smart Lock L2 From 17c337edc7d0e52cb78edc78edeb2310a1eb7bd9 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Fri, 16 Jan 2026 15:25:30 -0800 Subject: [PATCH 045/538] 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 --- .../matter-switch/fingerprints.yml | 407 +++++++++++++++++- 1 file changed, 406 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 9a2cbbaa89..32a4df6694 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1411,6 +1411,411 @@ matterManufacturer: 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 @@ -3604,7 +4009,7 @@ matterGeneric: deviceTypes: - id: 0x002B # Fan - id: 0x0100 # OnOff Light - deviceProfileName: fan-modular + deviceProfileName: fan-modular - id: "matter/dimmable/fan/light" deviceLabel: Matter Dimmable Fan Light deviceTypes: From 15b4d7bce5f234a3015533f7c1c90e1a59221320 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 13 Jan 2026 16:15:34 -0800 Subject: [PATCH 046/538] WWSTCERT-9840 Aqara U200 US --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 +++++ drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 71a3acd65f..dba88fb4dd 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 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 725f31bdab..bd8316cb01 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -65,6 +65,7 @@ local NEW_MATTER_LOCK_PRODUCTS = { {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 From 2192441df053cb9bd25e96c81e50e84f17c053dc Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:27:16 -0600 Subject: [PATCH 047/538] Revert "Exclude off from fanMode capability for Thermostats (#2673)" (#2715) This reverts commit b8b70686af6bfb80cc351a31c5e6d59653a88d72. --- .../attribute_handlers.lua | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua index fd5dca06a4..78f5ee3bf2 100644 --- a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua @@ -48,11 +48,7 @@ function AttributeHandlers.system_mode_handler(driver, device, ib, response) return end - local supported_modes = device:get_latest_state( - device:endpoint_to_component(ib.endpoint_id), - capabilities.thermostatMode.ID, - capabilities.thermostatMode.supportedThermostatModes.NAME - ) or {} + 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]()) @@ -329,19 +325,19 @@ function AttributeHandlers.fan_mode_handler(driver, device, ib, response) end function AttributeHandlers.fan_mode_sequence_handler(driver, device, ib, response) - local supported_fan_modes, supported_fan_modes_attribute + local supportedFanModes, supported_fan_modes_attribute if ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH then - supported_fan_modes = { "off", "low", "medium", "high" } + supportedFanModes = { "off", "low", "medium", "high" } elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH then - supported_fan_modes = { "off", "low", "high" } + supportedFanModes = { "off", "low", "high" } elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO then - supported_fan_modes = { "off", "low", "medium", "high", "auto" } + supportedFanModes = { "off", "low", "medium", "high", "auto" } elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH_AUTO then - supported_fan_modes = { "off", "low", "high", "auto" } + supportedFanModes = { "off", "low", "high", "auto" } elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_HIGH_AUTO then - supported_fan_modes = { "off", "high", "auto" } + supportedFanModes = { "off", "high", "auto" } else - supported_fan_modes = { "off", "high" } + supportedFanModes = { "off", "high" } end if device:supports_capability_by_id(capabilities.airPurifierFanMode.ID) then @@ -353,22 +349,16 @@ function AttributeHandlers.fan_mode_sequence_handler(driver, device, ib, respons -- 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 - supported_fan_modes = { "auto", "on" } + supportedFanModes = { "auto", "on" } else - supported_fan_modes = { "on" } + supportedFanModes = { "on" } end else supported_fan_modes_attribute = capabilities.fanMode.supportedFanModes end - -- remove 'off' as a supported fan mode for thermostat device types - if thermostat_utils.get_device_type(device) == fields.THERMOSTAT_DEVICE_TYPE_ID and - device:supports_capability_by_id(capabilities.fanMode.ID) then - -- per the definitions set above, the first index always contains "off" - table.remove(supported_fan_modes, 1) - end - - device:emit_event_for_endpoint(ib.endpoint_id, supported_fan_modes_attribute(supported_fan_modes, {visibility = {displayed = false}})) + 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) From cf0cf95f76da3ef7c5a8f0450a2e89f3be825a71 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Sat, 13 Dec 2025 11:53:28 -0800 Subject: [PATCH 048/538] WWSTCERT-9475 eWeLink Zigbee Motion Sensor --- drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) 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 From e5e6f5d8978181a0873effec83c0461862c9f30c Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 12 Jan 2026 14:48:40 -0800 Subject: [PATCH 049/538] WWSTCERT-9480 Sensereo MSC-1 --- drivers/SmartThings/matter-sensor/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index 0db00e11d6..4295c87a93 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -215,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 From d3dd9bd958baae72d7ba2ce2fc90d1f77da1edec Mon Sep 17 00:00:00 2001 From: Steven Green Date: Fri, 16 Jan 2026 15:25:30 -0800 Subject: [PATCH 050/538] 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 --- .../matter-switch/fingerprints.yml | 407 +++++++++++++++++- 1 file changed, 406 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 9a2cbbaa89..32a4df6694 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1411,6 +1411,411 @@ matterManufacturer: 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 @@ -3604,7 +4009,7 @@ matterGeneric: deviceTypes: - id: 0x002B # Fan - id: 0x0100 # OnOff Light - deviceProfileName: fan-modular + deviceProfileName: fan-modular - id: "matter/dimmable/fan/light" deviceLabel: Matter Dimmable Fan Light deviceTypes: From f7e4e8d68a430a79c95a183fbcc89b5d4077137a Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 13 Jan 2026 14:57:41 -0800 Subject: [PATCH 051/538] WWSTCERT-9799 Eve Thermostat (Europe) --- drivers/SmartThings/matter-thermostat/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-thermostat/fingerprints.yml b/drivers/SmartThings/matter-thermostat/fingerprints.yml index 6cd56d3a24..30e99caadb 100644 --- a/drivers/SmartThings/matter-thermostat/fingerprints.yml +++ b/drivers/SmartThings/matter-thermostat/fingerprints.yml @@ -33,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 From 877875775a610cffd3a6517cca69d719e391716b Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Wed, 14 Jan 2026 15:05:52 -0600 Subject: [PATCH 052/538] Matter Camera: Add vision.clipAnalysis capability Add vision.clipAnalysis to the Matter Camera profile. --- drivers/SmartThings/matter-switch/profiles/camera.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/SmartThings/matter-switch/profiles/camera.yml b/drivers/SmartThings/matter-switch/profiles/camera.yml index 7a46099cf6..7f62319984 100644 --- a/drivers/SmartThings/matter-switch/profiles/camera.yml +++ b/drivers/SmartThings/matter-switch/profiles/camera.yml @@ -47,6 +47,9 @@ components: - id: motionSensor version: 1 optional: true + - id: vision.clipAnalysis + version: 1 + optional: true - id: firmwareUpdate version: 1 - id: refresh From 2a95363080cedd8f16be852afb1e1df7369a2ef5 Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:27:16 -0600 Subject: [PATCH 053/538] Revert "Exclude off from fanMode capability for Thermostats (#2673)" (#2715) This reverts commit b8b70686af6bfb80cc351a31c5e6d59653a88d72. --- .../attribute_handlers.lua | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua index fd5dca06a4..78f5ee3bf2 100644 --- a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua @@ -48,11 +48,7 @@ function AttributeHandlers.system_mode_handler(driver, device, ib, response) return end - local supported_modes = device:get_latest_state( - device:endpoint_to_component(ib.endpoint_id), - capabilities.thermostatMode.ID, - capabilities.thermostatMode.supportedThermostatModes.NAME - ) or {} + 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]()) @@ -329,19 +325,19 @@ function AttributeHandlers.fan_mode_handler(driver, device, ib, response) end function AttributeHandlers.fan_mode_sequence_handler(driver, device, ib, response) - local supported_fan_modes, supported_fan_modes_attribute + local supportedFanModes, supported_fan_modes_attribute if ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH then - supported_fan_modes = { "off", "low", "medium", "high" } + supportedFanModes = { "off", "low", "medium", "high" } elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH then - supported_fan_modes = { "off", "low", "high" } + supportedFanModes = { "off", "low", "high" } elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO then - supported_fan_modes = { "off", "low", "medium", "high", "auto" } + supportedFanModes = { "off", "low", "medium", "high", "auto" } elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH_AUTO then - supported_fan_modes = { "off", "low", "high", "auto" } + supportedFanModes = { "off", "low", "high", "auto" } elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_HIGH_AUTO then - supported_fan_modes = { "off", "high", "auto" } + supportedFanModes = { "off", "high", "auto" } else - supported_fan_modes = { "off", "high" } + supportedFanModes = { "off", "high" } end if device:supports_capability_by_id(capabilities.airPurifierFanMode.ID) then @@ -353,22 +349,16 @@ function AttributeHandlers.fan_mode_sequence_handler(driver, device, ib, respons -- 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 - supported_fan_modes = { "auto", "on" } + supportedFanModes = { "auto", "on" } else - supported_fan_modes = { "on" } + supportedFanModes = { "on" } end else supported_fan_modes_attribute = capabilities.fanMode.supportedFanModes end - -- remove 'off' as a supported fan mode for thermostat device types - if thermostat_utils.get_device_type(device) == fields.THERMOSTAT_DEVICE_TYPE_ID and - device:supports_capability_by_id(capabilities.fanMode.ID) then - -- per the definitions set above, the first index always contains "off" - table.remove(supported_fan_modes, 1) - end - - device:emit_event_for_endpoint(ib.endpoint_id, supported_fan_modes_attribute(supported_fan_modes, {visibility = {displayed = false}})) + 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) From 91068085afbc5510925d0ac7d3fc1c73f02730e0 Mon Sep 17 00:00:00 2001 From: NoahCornell Date: Tue, 20 Jan 2026 16:52:42 -0600 Subject: [PATCH 054/538] CHAD-17218: Fix Sonos augmented driver store upserts This was broken in hubcore 59 due to IPC rework. --- .../SmartThings/sonos/src/sonos_driver.lua | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/sonos/src/sonos_driver.lua b/drivers/SmartThings/sonos/src/sonos_driver.lua index 1f63ef21d3..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" @@ -651,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() @@ -667,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, From 408a854d9dcde4ee1df0fefc7ab850483a5a113e Mon Sep 17 00:00:00 2001 From: NoahCornell Date: Tue, 20 Jan 2026 16:52:42 -0600 Subject: [PATCH 055/538] CHAD-17218: Fix Sonos augmented driver store upserts This was broken in hubcore 59 due to IPC rework. --- .../SmartThings/sonos/src/sonos_driver.lua | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/sonos/src/sonos_driver.lua b/drivers/SmartThings/sonos/src/sonos_driver.lua index 1f63ef21d3..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" @@ -651,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() @@ -667,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, From 6d0237dba0b1d6148e5094f11cd0dd8144350f61 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Wed, 21 Jan 2026 16:00:32 -0800 Subject: [PATCH 056/538] 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 --- drivers/SmartThings/matter-switch/fingerprints.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 32a4df6694..a61bc0f0b4 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -161,6 +161,16 @@ matterManufacturer: vendorId: 0x1396 productId: 0x1076 deviceProfileName: light-color-level + - id: "5014/4246" + deviceLabel: OREiN Matter smart Bathroom Fan + vendorId: 0x1396 + productId: 0x1096 + deviceProfileName: light-color-level-fan + - id: "5014/4247" + deviceLabel: OREiN Matter smart Bathroom Fan + vendorId: 0x1396 + productId: 0x1097 + deviceProfileName: fan-modular #Bosch Smart Home - id: "4617/12310" deviceLabel: Plug Compact [M] From c4a69144c153c68a4148146bdc23762f6a3ff947 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Wed, 21 Jan 2026 15:56:24 -0800 Subject: [PATCH 057/538] WWSTCERT-9989 Linkind Smart Light Bulb --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index a61bc0f0b4..f4b242a44f 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -171,6 +171,11 @@ matterManufacturer: 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] From c8f0de05162046491ba4625e6383046979e331d1 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Fri, 23 Jan 2026 16:08:22 -0600 Subject: [PATCH 058/538] Add vendor override for Ledvance --- drivers/SmartThings/matter-switch/src/switch_utils/fields.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index f670b154c8..2463201552 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -114,6 +114,8 @@ SwitchFields.switch_category_vendor_overrides = { {0x007D, 0x0074, 0x0075}, [0x1372] = -- Innovation Matters {0x0002}, + [0x1189] = -- Ledvance + {0x0891, 0x0892}, [0x1021] = -- Legrand {0x0005}, [0x109B] = -- Leviton From d908a4353cdcad197e9a0e5dc32a2c2adf4b42e1 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:14:12 -0600 Subject: [PATCH 059/538] Matter Thermostat: Set supportedThermostatOperatingStates if applicable (#2723) --- .../matter-thermostat/src/init.lua | 9 ++++---- .../src/test/test_matter_room_ac.lua | 11 +++++++++- .../src/test/test_matter_room_ac_modular.lua | 3 +++ .../src/test/test_matter_thermo_battery.lua | 4 ++++ .../test/test_matter_thermo_featuremap.lua | 9 +++----- ...st_matter_thermo_multiple_device_types.lua | 6 +++++ .../test_matter_thermo_setpoint_limits.lua | 3 +++ ...test_matter_thermo_setpoint_limits_rpc.lua | 7 ++++++ .../src/test/test_matter_thermostat.lua | 16 ++++++++++++-- ...est_matter_thermostat_composed_bridged.lua | 6 +++++ .../test/test_matter_thermostat_modular.lua | 4 ++++ .../src/test/test_matter_thermostat_rpc5.lua | 7 ++++++ .../src/thermostat_utils/utils.lua | 22 ++++++++++++++++++- 13 files changed, 92 insertions(+), 15 deletions(-) diff --git a/drivers/SmartThings/matter-thermostat/src/init.lua b/drivers/SmartThings/matter-thermostat/src/init.lua index 6697c80fb6..54cb3318f6 100644 --- a/drivers/SmartThings/matter-thermostat/src/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/init.lua @@ -90,6 +90,7 @@ function ThermostatLifecycleHandlers.device_init(driver, device) 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 @@ -100,14 +101,11 @@ function ThermostatLifecycleHandlers.device_init(driver, device) -- 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( + if #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 + ) == 0 then device:set_field(fields.CUMULATIVE_REPORTS_NOT_SUPPORTED, true, {persist = false}) end end @@ -119,6 +117,7 @@ function ThermostatLifecycleHandlers.info_changed(driver, device, event, args) end if device.profile.id ~= args.old_st_store.profile.id then + thermostat_utils.handle_thermostat_operating_state_info(device) device:subscribe() end end 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 35728e628d..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 @@ -21,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 } }, { @@ -32,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 } } } @@ -153,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 @@ -211,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 96577d72a3..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 @@ -87,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 9fb8536c23..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 @@ -4,6 +4,7 @@ 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) @@ -69,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 c898fab362..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 @@ -9,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, @@ -46,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, @@ -81,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, @@ -123,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, @@ -142,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, @@ -158,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 2660f75b6e..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 @@ -149,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) @@ -167,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 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 ae9c25940f..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 @@ -65,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 bbd084cb72..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 @@ -4,6 +4,7 @@ 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({ @@ -33,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 } } } @@ -62,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 e40572ff5d..3eed9709a2 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat.lua @@ -20,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 } }, { @@ -36,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 } } } @@ -70,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 } } } @@ -103,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) @@ -135,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 1f1a4e6cb4..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 @@ -35,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 } } } @@ -67,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 73d180da7e..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 @@ -2,6 +2,7 @@ -- 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" @@ -95,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 03e7df8ae2..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 @@ -2,6 +2,7 @@ -- 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" @@ -39,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 } } } @@ -70,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/thermostat_utils/utils.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua index 8194f08c48..7773c9ba02 100644 --- a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua @@ -3,8 +3,9 @@ local log = require "log" local capabilities = require "st.capabilities" -local embedded_cluster_utils = require "thermostat_utils.embedded_cluster_utils" +local clusters = require "st.matter.clusters" local fields = require "thermostat_utils.fields" +local embedded_cluster_utils = require "thermostat_utils.embedded_cluster_utils" local ThermostatUtils = {} @@ -85,6 +86,25 @@ function ThermostatUtils.get_endpoints_by_device_type(device, device_type) 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 From 1646987d4a9d12ceebf516575940a0e2a138f4fc Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Mon, 26 Jan 2026 10:18:09 -0600 Subject: [PATCH 060/538] allow override for non-plugs --- .../src/switch_utils/device_configuration.lua | 8 ++++---- .../src/test/test_multi_switch_parent_child_lights.lua | 2 +- .../src/test/test_multi_switch_parent_child_plugs.lua | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 7ee4b6bf64..ffea9efa2e 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -220,10 +220,10 @@ function DeviceConfiguration.match_profile(driver, device) 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 generic_profile("plug-binary") or generic_profile("plug-level") then - if switch_utils.check_switch_category_vendor_overrides(device) then - updated_profile = string.gsub(updated_profile, "plug", "switch") - end + 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. 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 c0ed244d51..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 @@ -265,7 +265,7 @@ local function test_init_parent_child_endpoints_non_sequential() 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 = "light-binary" }) + 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 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 09fa39c4f6..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 @@ -258,7 +258,7 @@ local function test_init_parent_child_endpoints_non_sequential() 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 = "light-binary" }) + 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_non_sequential) do From 2d6c2951bc6c68776a6431e0743530800cdf9343 Mon Sep 17 00:00:00 2001 From: Konrad Klimczuk Date: Mon, 26 Jan 2026 17:59:56 +0100 Subject: [PATCH 061/538] changes device label according to certification request WWSTCERT-9470 --- drivers/SmartThings/zigbee-button/fingerprints.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-button/fingerprints.yml b/drivers/SmartThings/zigbee-button/fingerprints.yml index c70cae5eac..c73109ebca 100644 --- a/drivers/SmartThings/zigbee-button/fingerprints.yml +++ b/drivers/SmartThings/zigbee-button/fingerprints.yml @@ -220,7 +220,7 @@ zigbeeManufacturer: model: WB01 deviceProfileName: one-button-battery - id: eWeLink/SNZB-01P - deviceLabel: eWeLink Button + deviceLabel: Zigbee Wireless Switch manufacturer: eWeLink model: SNZB-01P deviceProfileName: one-button-battery From 57fe19489fb2fd00faad6a7834c4aad8f4cf8ea6 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:42 -0600 Subject: [PATCH 062/538] CHAD-17046: zigbee-carbon-monoxide-detector subdriver lazy loading --- .../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 ++------- 8 files changed, 61 insertions(+), 67 deletions(-) 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 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" From 32f481e40969b011d872b996be3942f32b6747db Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:20 -0600 Subject: [PATCH 063/538] CHAD-17066: zigbee-dimmer-remote lazy loading subdrivers --- .../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 +++++++ 20 files changed, 188 insertions(+), 155 deletions(-) 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 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 From 3179673679e400e1528823c00024f70991c2b3ab Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 26 Jan 2026 09:49:46 -0800 Subject: [PATCH 064/538] Revert "Merge pull request #2662 from haedo-doo/Aqara-DimmerControllerT1-remove-unused-code" This reverts commit 3444bc31f35e684269966915912b42c9065fa78c, reversing changes made to 37f8536890432fa51645ab9dff3aa2be45565744. --- .../SmartThings/zigbee-switch/src/aqara-light/init.lua | 9 +++++++++ .../zigbee-switch/src/test/test_aqara_led_bulb.lua | 3 ++- .../zigbee-switch/src/test/test_aqara_light.lua | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua b/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua index 2192716b21..06d4bf1cc7 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua @@ -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 }, 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 ce4bb13957..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 @@ -38,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) @@ -51,7 +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 }))) + -- 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 c521ba3e44..940131b3e1 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua @@ -40,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) @@ -53,7 +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 }))) + -- test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ minimum = 2700, maximum = 6000 }))) end ) From 6ce8ae631da2d15bb16c78ec6f3de6f77ef41304 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Fri, 23 Jan 2026 10:01:21 -0600 Subject: [PATCH 065/538] Fix matter switch refresh capability command --- .../matter-switch/src/switch_utils/fields.lua | 2 ++ .../matter-switch/src/switch_utils/utils.lua | 14 ++++++++++---- .../matter-switch/src/test/test_matter_switch.lua | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 2463201552..ab878698ad 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -83,6 +83,8 @@ 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 }, diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index c6299fba95..e9809d4e46 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -449,19 +449,21 @@ function utils.populate_subscribe_request_for_device(checked_device, subscribe_r 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..attr_id] then + 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..attr_id] = true + 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..event_id] then + 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..event_id] = true + 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 @@ -486,6 +488,10 @@ function utils.subscribe(device) 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) 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 6a5ecd2cc4..b57c416434 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -1186,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() From f2853deb061e076416d30b2c9fa5810419ce77e1 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Wed, 21 Jan 2026 16:00:32 -0800 Subject: [PATCH 066/538] 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 --- drivers/SmartThings/matter-switch/fingerprints.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 32a4df6694..a61bc0f0b4 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -161,6 +161,16 @@ matterManufacturer: vendorId: 0x1396 productId: 0x1076 deviceProfileName: light-color-level + - id: "5014/4246" + deviceLabel: OREiN Matter smart Bathroom Fan + vendorId: 0x1396 + productId: 0x1096 + deviceProfileName: light-color-level-fan + - id: "5014/4247" + deviceLabel: OREiN Matter smart Bathroom Fan + vendorId: 0x1396 + productId: 0x1097 + deviceProfileName: fan-modular #Bosch Smart Home - id: "4617/12310" deviceLabel: Plug Compact [M] From f4218e180da6c0c1020f05db2ae52bdb99741136 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Fri, 23 Jan 2026 12:19:58 -0800 Subject: [PATCH 067/538] Merge pull request #2711 from SmartThingsCommunity/new_device/WWSTCERT-9960 WWSTCERT-9960 HooRii Window Covering --- .../SmartThings/matter-window-covering/fingerprints.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-window-covering/fingerprints.yml b/drivers/SmartThings/matter-window-covering/fingerprints.yml index 532ba1f304..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 @@ -176,7 +182,7 @@ matterManufacturer: deviceLabel: WISTAR WSCMXF-LED Smart Vertical Blind Motor vendorId: 0x1457 productId: 0x0018 - deviceProfileName: window-covering-tilt + deviceProfileName: window-covering-tilt - id: "5207/20" deviceLabel: WISTAR WSCMQ Smart Curtain Motor vendorId: 0x1457 From 973f299c9bf839a5debef2d334fcb575063ee779 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 26 Jan 2026 11:56:49 -0800 Subject: [PATCH 068/538] Merge pull request #2726 from SmartThingsCommunity/new_device/WWSTCERT-9989 WWSTCERT-9989 Linkind Smart Light Bulb --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index a61bc0f0b4..f4b242a44f 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -171,6 +171,11 @@ matterManufacturer: 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] From 234406444fee8952d1c1eec6d6620e8144f4234a Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 26 Jan 2026 10:11:41 -0800 Subject: [PATCH 069/538] Merge pull request #2732 from SmartThingsCommunity/revert/aqara_t1_update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "Merge pull request #2662 from haedo-doo/Aqara-DimmerControlle… --- .../SmartThings/zigbee-switch/src/aqara-light/init.lua | 9 +++++++++ .../zigbee-switch/src/test/test_aqara_led_bulb.lua | 3 ++- .../zigbee-switch/src/test/test_aqara_light.lua | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua b/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua index 2192716b21..06d4bf1cc7 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua @@ -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 }, 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 ce4bb13957..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 @@ -38,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) @@ -51,7 +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 }))) + -- 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 c521ba3e44..940131b3e1 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua @@ -40,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) @@ -53,7 +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 }))) + -- test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ minimum = 2700, maximum = 6000 }))) end ) From ca7b1592b544d154662877da6f23a2d2a4ee9559 Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:55:35 -0600 Subject: [PATCH 070/538] Merge pull request #2727 from SmartThingsCommunity/fix/matter-switch-refresh Fix matter switch refresh capability command --- .../matter-switch/src/switch_utils/fields.lua | 2 ++ .../matter-switch/src/switch_utils/utils.lua | 14 ++++++++++---- .../matter-switch/src/test/test_matter_switch.lua | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index f670b154c8..85e62c09fa 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -83,6 +83,8 @@ 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 }, diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index c6299fba95..e9809d4e46 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -449,19 +449,21 @@ function utils.populate_subscribe_request_for_device(checked_device, subscribe_r 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..attr_id] then + 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..attr_id] = true + 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..event_id] then + 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..event_id] = true + 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 @@ -486,6 +488,10 @@ function utils.subscribe(device) 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) 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 6a5ecd2cc4..b57c416434 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -1186,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() From 3dde32dece000274558d78032f7797348d375619 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 26 Jan 2026 12:14:27 -0800 Subject: [PATCH 071/538] fixup Orein fan profile --- drivers/SmartThings/matter-switch/fingerprints.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index f4b242a44f..0871a9c0c5 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -165,9 +165,9 @@ matterManufacturer: deviceLabel: OREiN Matter smart Bathroom Fan vendorId: 0x1396 productId: 0x1096 - deviceProfileName: light-color-level-fan + deviceProfileName: fan-modular - id: "5014/4247" - deviceLabel: OREiN Matter smart Bathroom Fan + deviceLabel: OREiN Matter smart Bathroom Fan vendorId: 0x1396 productId: 0x1097 deviceProfileName: fan-modular From 8f972803759ad1d49d6e173e77303b8747b264c3 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 26 Jan 2026 12:30:32 -0800 Subject: [PATCH 072/538] Merge pull request #2734 from SmartThingsCommunity/fixup/orein-fan fixup Orein fan profile --- drivers/SmartThings/matter-switch/fingerprints.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index f4b242a44f..0871a9c0c5 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -165,9 +165,9 @@ matterManufacturer: deviceLabel: OREiN Matter smart Bathroom Fan vendorId: 0x1396 productId: 0x1096 - deviceProfileName: light-color-level-fan + deviceProfileName: fan-modular - id: "5014/4247" - deviceLabel: OREiN Matter smart Bathroom Fan + deviceLabel: OREiN Matter smart Bathroom Fan vendorId: 0x1396 productId: 0x1097 deviceProfileName: fan-modular From 52ab8d347b684159d8b03339c7c6fe31ac4f9985 Mon Sep 17 00:00:00 2001 From: Jeff Page Date: Tue, 27 Jan 2026 13:38:11 -0600 Subject: [PATCH 073/538] 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 --- .../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 +++++++++++++++++++ 7 files changed, 283 insertions(+), 3 deletions(-) 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 diff --git a/drivers/SmartThings/zwave-sensor/fingerprints.yml b/drivers/SmartThings/zwave-sensor/fingerprints.yml index 727f02a2d6..502e5bd5a7 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 d92a09fe56..213aa8c389 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"), }, lifecycle_handlers = { diff --git a/drivers/SmartThings/zwave-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index 4921a996bf..9585b6ffe9 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 }, + }, + }, } local preferences = {} 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 From da4e8ea5f5b00c36e8bfc980dffc9ee0bd1980e2 Mon Sep 17 00:00:00 2001 From: Marcin Krystian Tyminski <81477027+marcintyminski@users.noreply.github.com> Date: Tue, 27 Jan 2026 20:41:58 +0100 Subject: [PATCH 074/538] 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 --- .../zigbee-switch/fingerprints.yml | 5 + .../profiles/frient-io-output-switch.yml | 25 + .../profiles/switch-4inputs-2outputs.yml | 107 +++ .../src/configurations/devices.lua | 61 ++ .../src/frient-IO/can_handle.lua | 12 + .../zigbee-switch/src/frient-IO/init.lua | 488 +++++++++++++ .../src/frient-IO/unbind_request.lua | 84 +++ .../SmartThings/zigbee-switch/src/init.lua | 3 +- .../src/test/test_frient_IO_module.lua | 683 ++++++++++++++++++ 9 files changed, 1467 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-switch/profiles/frient-io-output-switch.yml create mode 100644 drivers/SmartThings/zigbee-switch/profiles/switch-4inputs-2outputs.yml 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/test/test_frient_IO_module.lua diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 3d598372fa..a83204e5f6 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -505,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 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/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/configurations/devices.lua b/drivers/SmartThings/zigbee-switch/src/configurations/devices.lua index 9910dde229..d49d10e9c5 100644 --- a/drivers/SmartThings/zigbee-switch/src/configurations/devices.lua +++ b/drivers/SmartThings/zigbee-switch/src/configurations/devices.lua @@ -7,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 = { @@ -110,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/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/init.lua b/drivers/SmartThings/zigbee-switch/src/init.lua index 1b694d2a5e..a7be3f8801 100644 --- a/drivers/SmartThings/zigbee-switch/src/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/init.lua @@ -98,7 +98,8 @@ local zigbee_switch_driver_template = { 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/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() From 750cbdf7ea597ffa70730090e635e91c0e54e319 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Wed, 28 Jan 2026 11:51:16 -0800 Subject: [PATCH 075/538] WWSTCERT-9848 Netatmo Thermo Hub --- drivers/SmartThings/matter-switch/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 0871a9c0c5..e418d34d3b 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1354,6 +1354,12 @@ matterManufacturer: vendorId: 0x137F productId: 0x027B deviceProfileName: plug-binary +#Netatmo + - id: "4129/8" + deviceLabel: Netatmo Thermo Hub + vendorId: 0x1021 + productId: 0x0008 + deviceProfileName: matter-bridge #Onvis - id: "5181/4097" deviceLabel: Onvis Smart Plug S4EU From 30ecbf651e2274e656bfcba096f7cbc1b524ad87 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Wed, 28 Jan 2026 12:10:13 -0800 Subject: [PATCH 076/538] Revert "Merge pull request #2732 from SmartThingsCommunity/revert/aqara_t1_update" This reverts commit 8b56e69856cd0ef52161b3c1c00d418faf63b548, reversing changes made to 201887c11e3b59e2eb8fffcd7d1d8111957e7d1d. --- .../SmartThings/zigbee-switch/src/aqara-light/init.lua | 9 --------- .../zigbee-switch/src/test/test_aqara_led_bulb.lua | 3 +-- .../zigbee-switch/src/test/test_aqara_light.lua | 3 +-- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua b/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua index 06d4bf1cc7..2192716b21 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua @@ -54,18 +54,9 @@ 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 }, 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 b2f9359850..ce4bb13957 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 @@ -38,7 +38,6 @@ 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) @@ -52,7 +51,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 }))) + 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 940131b3e1..c521ba3e44 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua @@ -40,7 +40,6 @@ 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) @@ -54,7 +53,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 }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ minimum = 2700, maximum = 6000 }))) end ) From 0e3af5a31b4c1c5e02f5b8985af24a51941ed5cb Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 29 Jan 2026 19:56:23 -0800 Subject: [PATCH 077/538] WWSTCERT-10185 ubisys S1 --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index a83204e5f6..1c26d5d6c8 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -600,6 +600,11 @@ zigbeeManufacturer: manufacturer: ubisys model: "D1 (5503)" deviceProfileName: switch-level-power + - id: "ubisys/S1 (5501)" + deviceLabel: Switching Actuator S1 + manufacturer: ubisys + model: "S1 (5501)" + deviceProfileName: switch-power - id: "ubisys/S2 (5502)" deviceLabel: ubisys S2 manufacturer: ubisys @@ -2412,10 +2417,10 @@ zigbeeManufacturer: model: Y-K003-001 deviceProfileName: basic-switch - id: "JNL/Y-K001-001" - deviceLabel: Yanmi Switch (1 Way) + deviceLabel: Yanmi Switch (1 Way) manufacturer: JNL model: Y-K001-001 - deviceProfileName: basic-switch + deviceProfileName: basic-switch - id: "JNL/Y-K002-001" deviceLabel: Yanmi Switch (2 Way) 1 manufacturer: JNL From 117b1e967c07caee24375328af8b19ae7ec0877d Mon Sep 17 00:00:00 2001 From: JanJakubiszyn <101173721+JanJakubiszyn@users.noreply.github.com> Date: Mon, 2 Feb 2026 17:00:48 +0100 Subject: [PATCH 078/538] Adding attribute reading after Offset preference change for instant (#2737) update --- .../SmartThings/matter-switch/src/init.lua | 12 ++ .../test_matter_sensor_offset_preferences.lua | 123 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 6655f81e56..0bb9d57ab5 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -81,6 +81,18 @@ function SwitchLifecycleHandlers.info_changed(driver, device, event, args) device_cfg.match_profile(driver, device) end end + + -- instant update of values after offset preference change + for name, info in pairs(device.preferences or {}) do + if (device.preferences[name] ~= nil and args.old_st_store.preferences[name] ~= nil and args.old_st_store.preferences[name] ~= device.preferences[name]) then + if name == "tempOffset" then + device:send(clusters.TemperatureMeasurement.attributes.MeasuredValue:read(device)) + elseif name == "humidityOffset" then + device:send(clusters.RelativeHumidityMeasurement.attributes.MeasuredValue:read(device)) + end + end + end + end function SwitchLifecycleHandlers.device_init(driver, device) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua new file mode 100644 index 0000000000..de2afe3bd3 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua @@ -0,0 +1,123 @@ +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" + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("3-button-battery-temperature-humidity.yml"), + matter_version = {hardware = 1, software = 1}, + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + endpoints = { + { + endpoint_id = 1, + clusters = { + {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0302, device_type_revision = 1}, + } + }, + { + endpoint_id = 2, + clusters = { + {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "BOTH"}, + }, + device_types = { + {device_type_id = 0x0307, device_type_revision = 1}, + } + }, + } +}) + +local function test_init() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) + + local cluster_subscribe_list = { + clusters.Switch.events.InitialPress, + clusters.Switch.events.LongPress, + clusters.Switch.events.ShortRelease, + clusters.Switch.events.MultiPressComplete, + + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue, + + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue, + clusters.PowerSource.attributes.BatPercentRemaining + } + + 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}) + + 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" }) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + + local device_info_copy = utils.deep_copy(mock_device.raw_st_data) + device_info_copy.profile.id = "3-button-battery-temperature-humidity" + 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}) + +end + +test.register_coroutine_test("Read appropriate attribute values after tempOffset preference change", function() + local report = clusters.TemperatureMeasurement.attributes.MeasuredValue:build_test_report_data(mock_device,1, 2000) + mock_device.st_store.preferences = {tempOffset = "0"} + + test.socket.matter:__queue_receive({mock_device.id, report}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main",capabilities.temperatureMeasurement.temperature({ + value = 20.0, + unit = "C" + }))) + test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({preferences = {tempOffset = "5"}})) + test.socket.matter:__expect_send({mock_device.id, clusters.TemperatureMeasurement.attributes.MeasuredValue:read(mock_device)}) + + test.wait_for_events() + + test.socket.matter:__queue_receive({mock_device.id, report}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main",capabilities.temperatureMeasurement.temperature({ + value = 20.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Read appropriate attribute values after humidityOffset preference change", function() + local report = clusters.RelativeHumidityMeasurement.attributes.MeasuredValue:build_test_report_data(mock_device,2, 2000) + mock_device.st_store.preferences = {humidityOffset = "0"} + + test.socket.matter:__queue_receive({mock_device.id, report}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main",capabilities.relativeHumidityMeasurement.humidity({ + value = 20 + }))) + test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({preferences = {humidityOffset = "5"}})) + test.socket.matter:__expect_send({mock_device.id, clusters.RelativeHumidityMeasurement.attributes.MeasuredValue:read(mock_device)}) + + test.wait_for_events() + + test.socket.matter:__queue_receive({mock_device.id, report}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main",capabilities.relativeHumidityMeasurement.humidity({ + value = 20 + }))) +end) + +test.set_test_init_function(test_init) + +test.run_registered_tests() From 2039f55adfa86380a2335e442e9b3d6a795ddfef Mon Sep 17 00:00:00 2001 From: seojune79 <119141897+seojune79@users.noreply.github.com> Date: Tue, 3 Feb 2026 01:09:07 +0900 Subject: [PATCH 079/538] Fixing the Hub Error Log Related to Preference STSE Capability (#2512) --- .../src/aqara/curtain-driver-e1/init.lua | 12 ++++++------ .../zigbee-window-treatment/src/aqara/init.lua | 12 ++++++------ .../src/aqara/roller-shade/init.lua | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.lua index 5d4f4d3a37..6fe895ca7d 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.lua @@ -29,10 +29,10 @@ local PRIVATE_CURTAIN_LOCKING_SETTING_ATTRIBUTE_ID = 0x0427 local PRIVATE_CURTAIN_LOCKING_STATUS_ATTRIBUTE_ID = 0x0428 local initializedStateWithGuide = capabilities["stse.initializedStateWithGuide"] -local reverseCurtainDirection = capabilities["stse.reverseCurtainDirection"] +local reverseCurtainDirection = "stse.reverseCurtainDirection" local hookLockState = capabilities["stse.hookLockState"] local chargingState = capabilities["stse.chargingState"] -local softTouch = capabilities["stse.softTouch"] +local softTouch = "stse.softTouch" local hookUnlockCommandName = "hookUnlock" local hookLockCommandName = "hookLock" @@ -82,12 +82,12 @@ end local function device_info_changed(driver, device, event, args) if device.preferences ~= nil then - local reverseCurtainDirectionPrefValue = device.preferences[reverseCurtainDirection.ID] - local softTouchPrefValue = device.preferences[softTouch.ID] + local reverseCurtainDirectionPrefValue = device.preferences[reverseCurtainDirection] + local softTouchPrefValue = device.preferences[softTouch] -- reverse direction if reverseCurtainDirectionPrefValue ~= nil and - reverseCurtainDirectionPrefValue ~= args.old_st_store.preferences[reverseCurtainDirection.ID] then + reverseCurtainDirectionPrefValue ~= args.old_st_store.preferences[reverseCurtainDirection] then local raw_value = reverseCurtainDirectionPrefValue and 0x01 or 0x00 device:send(aqara_utils.custom_write_attribute(device, WindowCovering.ID, WindowCovering.attributes.Mode.ID, data_types.Bitmap8, raw_value, nil)) @@ -95,7 +95,7 @@ local function device_info_changed(driver, device, event, args) -- soft touch if softTouchPrefValue ~= nil and - softTouchPrefValue ~= args.old_st_store.preferences[softTouch.ID] then + softTouchPrefValue ~= args.old_st_store.preferences[softTouch] then device:send(cluster_base.write_manufacturer_specific_attribute(device, aqara_utils.PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_MANUAL_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Boolean, (not softTouchPrefValue))) end diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/init.lua index ae9513734c..87505d2e40 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/init.lua @@ -11,8 +11,8 @@ local AnalogOutput = clusters.AnalogOutput local Groups = clusters.Groups local deviceInitialization = capabilities["stse.deviceInitialization"] -local reverseCurtainDirection = capabilities["stse.reverseCurtainDirection"] -local softTouch = capabilities["stse.softTouch"] +local reverseCurtainDirection = "stse.reverseCurtainDirection" +local softTouch = "stse.softTouch" local setInitializedStateCommandName = "setInitializedState" local INIT_STATE = "initState" @@ -133,12 +133,12 @@ end local function device_info_changed(driver, device, event, args) if device.preferences ~= nil then - local reverseCurtainDirectionPrefValue = device.preferences[reverseCurtainDirection.ID] - local softTouchPrefValue = device.preferences[softTouch.ID] + local reverseCurtainDirectionPrefValue = device.preferences[reverseCurtainDirection] + local softTouchPrefValue = device.preferences[softTouch] -- reverse direction if reverseCurtainDirectionPrefValue ~= nil and - reverseCurtainDirectionPrefValue ~= args.old_st_store.preferences[reverseCurtainDirection.ID] then + reverseCurtainDirectionPrefValue ~= args.old_st_store.preferences[reverseCurtainDirection] then local raw_value = reverseCurtainDirectionPrefValue and aqara_utils.PREF_REVERSE_ON or aqara_utils.PREF_REVERSE_OFF device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.CharString, raw_value)) @@ -152,7 +152,7 @@ local function device_info_changed(driver, device, event, args) -- soft touch if softTouchPrefValue ~= nil and - softTouchPrefValue ~= args.old_st_store.preferences[softTouch.ID] then + softTouchPrefValue ~= args.old_st_store.preferences[softTouch] then local raw_value = softTouchPrefValue and PREF_SOFT_TOUCH_ON or PREF_SOFT_TOUCH_OFF device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.CharString, raw_value)) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua index c7b6b9a50a..12b84eb0c8 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua @@ -10,7 +10,7 @@ local Basic = clusters.Basic local WindowCovering = clusters.WindowCovering local initializedStateWithGuide = capabilities["stse.initializedStateWithGuide"] -local reverseRollerShadeDir = capabilities["stse.reverseRollerShadeDir"] +local reverseRollerShadeDir = "stse.reverseRollerShadeDir" local shadeRotateState = capabilities["stse.shadeRotateState"] local setRotateStateCommandName = "setRotateState" @@ -82,9 +82,9 @@ end local function device_info_changed(driver, device, event, args) if device.preferences ~= nil then - local reverseRollerShadeDirPrefValue = device.preferences[reverseRollerShadeDir.ID] + local reverseRollerShadeDirPrefValue = device.preferences[reverseRollerShadeDir] if reverseRollerShadeDirPrefValue ~= nil and - reverseRollerShadeDirPrefValue ~= args.old_st_store.preferences[reverseRollerShadeDir.ID] then + reverseRollerShadeDirPrefValue ~= args.old_st_store.preferences[reverseRollerShadeDir] then local raw_value = reverseRollerShadeDirPrefValue and aqara_utils.PREF_REVERSE_ON or aqara_utils.PREF_REVERSE_OFF device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.CharString, raw_value)) From 1f3f3891ef111192434acae61788a641d88c5ced Mon Sep 17 00:00:00 2001 From: Steven Green Date: Wed, 28 Jan 2026 12:07:32 -0800 Subject: [PATCH 080/538] Merge pull request #2738 from SmartThingsCommunity/new_device/WWSTCERT-9848 WWSTCERT-9848 Netatmo Thermo Hub --- drivers/SmartThings/matter-switch/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 0871a9c0c5..e418d34d3b 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1354,6 +1354,12 @@ matterManufacturer: vendorId: 0x137F productId: 0x027B deviceProfileName: plug-binary +#Netatmo + - id: "4129/8" + deviceLabel: Netatmo Thermo Hub + vendorId: 0x1021 + productId: 0x0008 + deviceProfileName: matter-bridge #Onvis - id: "5181/4097" deviceLabel: Onvis Smart Plug S4EU From b68fffff36f3ea048b14486cb1dc0c81a36749fd Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 29 Jan 2026 20:08:04 -0800 Subject: [PATCH 081/538] Merge pull request #2741 from SmartThingsCommunity/new_device/WWSTCERT-10185 WWSTCERT-10185 ubisys S1 --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 3d598372fa..cbccb8a277 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -595,6 +595,11 @@ zigbeeManufacturer: manufacturer: ubisys model: "D1 (5503)" deviceProfileName: switch-level-power + - id: "ubisys/S1 (5501)" + deviceLabel: Switching Actuator S1 + manufacturer: ubisys + model: "S1 (5501)" + deviceProfileName: switch-power - id: "ubisys/S2 (5502)" deviceLabel: ubisys S2 manufacturer: ubisys @@ -2407,10 +2412,10 @@ zigbeeManufacturer: model: Y-K003-001 deviceProfileName: basic-switch - id: "JNL/Y-K001-001" - deviceLabel: Yanmi Switch (1 Way) + deviceLabel: Yanmi Switch (1 Way) manufacturer: JNL model: Y-K001-001 - deviceProfileName: basic-switch + deviceProfileName: basic-switch - id: "JNL/Y-K002-001" deviceLabel: Yanmi Switch (2 Way) 1 manufacturer: JNL From b17cb987a64864c32872e88a5b09a03e0832c40c Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Wed, 21 Jan 2026 11:25:09 -0600 Subject: [PATCH 082/538] Matter Switch: Improve battery profiling This implements a few changes to the way that devices supporting batteries are profiled: * subscribe to PowerSource.AttributeList rather than reading this attribute, to help prevent failed reads from causing issues with device profiling * update the profile within match_profile rather than power_source_attribute_list_handler * use existing structure for handling waiting for profiling data before attempting a profile update --- .../SmartThings/matter-switch/src/init.lua | 5 +- .../switch_handlers/attribute_handlers.lua | 38 +- .../src/switch_utils/device_configuration.lua | 27 +- .../matter-switch/src/switch_utils/fields.lua | 9 +- .../matter-switch/src/switch_utils/utils.lua | 7 + .../test/test_aqara_climate_sensor_w100.lua | 40 +- .../test/test_light_illuminance_motion.lua | 75 +- .../src/test/test_matter_bridge.lua | 8 +- .../src/test/test_matter_button.lua | 555 +++++++----- .../src/test/test_matter_multi_button.lua | 812 +++++++++++------- .../src/test/test_matter_switch.lua | 11 + .../src/test/test_matter_water_valve.lua | 4 + .../src/test/test_multi_switch_mcd.lua | 10 + 13 files changed, 956 insertions(+), 645 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 0bb9d57ab5..e8d1b8330a 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -103,10 +103,13 @@ 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 + if #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) == 0 then + device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, {persist = true}) + end device:extend_device("subscribe", switch_utils.subscribe) device:subscribe() - -- device energy reporting must be handled cumulatively, periodically, or by both simulatanously. + -- device energy reporting must be handled cumulatively, periodically, or by both simultaneously. -- 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index e8dfe3f120..ae76709be8 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -319,10 +319,10 @@ function AttributeHandlers.available_endpoints_handler(driver, device, ib, respo 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 + -- since EP response 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 + for _, element in pairs(ib.data.elements or {}) 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. @@ -344,10 +344,10 @@ 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 + -- since EP response 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 + for _, element in pairs(ib.data.elements or {}) 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. @@ -382,29 +382,19 @@ function AttributeHandlers.bat_charge_level_handler(driver, device, ib, response end function AttributeHandlers.power_source_attribute_list_handler(driver, device, ib, response) - local profile_name = "" - - local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) - 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 - profile_name = "button-battery" - break - elseif attr.value == 0x0E then - profile_name = "button-batteryLevel" + local previous_battery_support = device:get_field(fields.profiling_data.BATTERY_SUPPORT) + device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, {persist=true}) + for _, attr in ipairs(ib.data.elements or {}) do + if attr.value == clusters.PowerSource.attributes.BatPercentRemaining.ID then + device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.BATTERY_PERCENTAGE, {persist=true}) break + elseif attr.value == clusters.PowerSource.attributes.BatChargeLevel.ID and + device:get_field(fields.profiling_data.BATTERY_SUPPORT) ~= fields.battery_support.BATTERY_PERCENTAGE then -- don't overwrite if percentage support is already detected + device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.BATTERY_LEVEL, {persist=true}) end end - if profile_name ~= "" then - if #button_eps > 1 then - profile_name = string.format("%d-", #button_eps) .. profile_name - end - - 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 }) + if not previous_battery_support or previous_battery_support ~= device:get_field(fields.profiling_data.BATTERY_SUPPORT) then + device_cfg.match_profile(driver, device) end end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index ffea9efa2e..8542972320 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -123,12 +123,16 @@ function ButtonDeviceConfiguration.update_button_profile(device, default_endpoin 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}) + local battery_support = device:get_field(fields.profiling_data.BATTERY_SUPPORT) + if battery_support == fields.battery_support.BATTERY_PERCENTAGE then + profile_name = profile_name .. "-battery" + elseif battery_support == fields.battery_support.BATTERY_LEVEL then + profile_name = profile_name .. "-batteryLevel" end + if switch_utils.get_product_override_field(device, "is_climate_sensor_w100") then + profile_name = "3-button-battery-temperature-humidity" + end + return profile_name end function ButtonDeviceConfiguration.update_button_component_map(device, default_endpoint_id, button_eps) @@ -238,13 +242,12 @@ function DeviceConfiguration.match_profile(driver, device) 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) + local momentary_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, #momentary_switch_ep_ids) then + updated_profile = ButtonDeviceConfiguration.update_button_profile(device, default_endpoint_id, #momentary_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 + ButtonDeviceConfiguration.update_button_component_map(device, default_endpoint_id, momentary_switch_ep_ids) + ButtonDeviceConfiguration.configure_buttons(device, momentary_switch_ep_ids) end device:try_update_metadata({ profile = updated_profile, optional_component_capabilities = optional_component_capabilities }) @@ -254,4 +257,4 @@ return { DeviceCfg = DeviceConfiguration, SwitchCfg = SwitchDeviceConfiguration, ButtonCfg = ButtonDeviceConfiguration -} \ No newline at end of file +} diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index ab878698ad..db66c2965c 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -139,13 +139,20 @@ SwitchFields.switch_category_vendor_overrides = { 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. +--- for an Electrical Sensor EP with a "primary" endpoint, used during device profiling. SwitchFields.ELECTRICAL_TAGS = "__electrical_tags" SwitchFields.MODULAR_PROFILE_UPDATED = "__modular_profile_updated" SwitchFields.profiling_data = { POWER_TOPOLOGY = "__power_topology", + BATTERY_SUPPORT = "__battery_support", +} + +SwitchFields.battery_support = { + NO_BATTERY = "NO_BATTERY", + BATTERY_LEVEL = "BATTERY_LEVEL", + BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE", } SwitchFields.ENERGY_METER_OFFSET = "__energy_meter_offset" diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index e9809d4e46..da5376f031 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -493,6 +493,13 @@ function utils.subscribe(device) -- attributes and not events. device:set_field(fields.SUBSCRIBED_ATTRIBUTES_KEY, attributes_seen) + -- If the type of battery support has not yet been determined, add the PowerSource AttributeList to the list of + -- subscribed attributes in order to determine which if any battery capability should be used. + if device:get_field(fields.profiling_data.BATTERY_SUPPORT) == nil then + local ib = im.InteractionInfoBlock(nil, clusters.PowerSource.ID, clusters.PowerSource.attributes.AttributeList.ID) + subscribe_request:with_info_block(ib) + end + if #subscribe_request.info_blocks > 0 then device:send(subscribe_request) end 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 6371c6be44..d47c666166 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,14 +1,11 @@ -- Copyright © 2024 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 utils = require "st.utils" -local dkjson = require "dkjson" -local uint32 = require "st.matter.data_types.Uint32" local clusters = require "st.matter.generated.zap_clusters" -local button_attr = capabilities.button.button +local t_utils = require "integration_test.utils" +local test = require "integration_test" +local uint32 = require "st.matter.data_types.Uint32" -- Mock a 3-button device with temperature and humidity sensor local aqara_mock_device = test.mock_device.build_test_matter_device({ @@ -100,19 +97,20 @@ local aqara_mock_device = test.mock_device.build_test_matter_device({ local function configure_buttons() test.socket.matter:__expect_send({aqara_mock_device.id, clusters.Switch.attributes.MultiPressMax:read(aqara_mock_device, 3)}) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button1", button_attr.pushed({state_change = false}))) + test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button1", capabilities.button.button.pushed({state_change = false}))) test.socket.matter:__expect_send({aqara_mock_device.id, clusters.Switch.attributes.MultiPressMax:read(aqara_mock_device, 4)}) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button2", button_attr.pushed({state_change = false}))) + test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button2", capabilities.button.button.pushed({state_change = false}))) test.socket.matter:__expect_send({aqara_mock_device.id, clusters.Switch.attributes.MultiPressMax:read(aqara_mock_device, 5)}) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button3", button_attr.pushed({state_change = false}))) + test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button3", capabilities.button.button.pushed({state_change = false}))) end local function test_init() test.disable_startup_messages() test.mock_device.add_test_device(aqara_mock_device) local cluster_subscribe_list = { + clusters.PowerSource.server.attributes.AttributeList, clusters.PowerSource.server.attributes.BatPercentRemaining, clusters.TemperatureMeasurement.attributes.MeasuredValue, clusters.TemperatureMeasurement.attributes.MinMeasuredValue, @@ -138,23 +136,16 @@ local function test_init() test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "doConfigure" }) - local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read() - test.socket.matter:__expect_send({aqara_mock_device.id, read_attribute_list}) - configure_buttons() aqara_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - - local device_info_copy = utils.deep_copy(aqara_mock_device.raw_st_data) - device_info_copy.profile.id = "3-button-battery-temperature-humidity" - local device_info_json = dkjson.encode(device_info_copy) - test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "infoChanged", device_info_json }) - test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) - configure_buttons() end test.set_test_init_function(test_init) local function update_profile() - test.socket.matter:__queue_receive({aqara_mock_device.id, clusters.PowerSource.attributes.AttributeList:build_test_report_data(aqara_mock_device, 6, {uint32(0x0C)})}) + test.socket.matter:__queue_receive({aqara_mock_device.id, clusters.PowerSource.attributes.AttributeList:build_test_report_data( + aqara_mock_device, 6, {uint32(clusters.PowerSource.attributes.BatPercentRemaining.ID)} + )}) + configure_buttons() aqara_mock_device:expect_metadata_update({ profile = "3-button-battery-temperature-humidity" }) end @@ -315,7 +306,7 @@ test.register_coroutine_test( clusters.Switch.events.MultiPressComplete:build_test_event_report(aqara_mock_device, 4, {new_position = 0, total_number_of_presses_counted = 2, previous_position = 1}) } ) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button2", button_attr.double({state_change = true}))) + test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button2", capabilities.button.button.double({state_change = true}))) end ) @@ -335,7 +326,7 @@ test.register_coroutine_test( clusters.Switch.events.LongPress:build_test_event_report(aqara_mock_device, 3, {new_position = 1}) } ) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button1", button_attr.held({state_change = true}))) + test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button1", capabilities.button.button.held({state_change = true}))) test.socket.matter:__queue_receive( { aqara_mock_device.id, @@ -361,7 +352,7 @@ test.register_coroutine_test( clusters.Switch.events.LongPress:build_test_event_report(aqara_mock_device, 5, {new_position = 1}) } ) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button3", button_attr.held({state_change = true}))) + test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button3", capabilities.button.button.held({state_change = true}))) test.socket.matter:__queue_receive( { aqara_mock_device.id, @@ -382,7 +373,7 @@ test.register_coroutine_test( } ) test.socket.capability:__expect_send( - aqara_mock_device:generate_test_message("button1", button_attr.double({state_change = true})) + aqara_mock_device:generate_test_message("button1", capabilities.button.button.double({state_change = true})) ) end ) @@ -466,4 +457,3 @@ test.register_coroutine_test( ) test.run_registered_tests() - 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 24987079fc..a6d660de4e 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 @@ -81,29 +81,35 @@ local function set_color_mode(device, endpoint, color_mode) test.socket.matter:__expect_send({device.id, read_req}) end -local function test_init() - local cluster_subscribe_list = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel, - clusters.ColorControl.attributes.CurrentHue, - clusters.ColorControl.attributes.CurrentSaturation, - clusters.ColorControl.attributes.CurrentX, - clusters.ColorControl.attributes.CurrentY, - clusters.ColorControl.attributes.ColorMode, - clusters.ColorControl.attributes.ColorTemperatureMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, - clusters.IlluminanceMeasurement.attributes.MeasuredValue, - clusters.OccupancySensing.attributes.Occupancy - } - 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 +local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation, + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorMode, + clusters.ColorControl.attributes.ColorTemperatureMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, + clusters.IlluminanceMeasurement.attributes.MeasuredValue, + clusters.OccupancySensing.attributes.Occupancy +} + +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 + +local function test_init() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + + -- the following subscribe is due to the init event sent by the test framework. test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) set_color_mode(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) @@ -111,28 +117,9 @@ end test.set_test_init_function(test_init) local function test_init_x_y_color_mode() - local cluster_subscribe_list = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel, - clusters.ColorControl.attributes.CurrentHue, - clusters.ColorControl.attributes.CurrentSaturation, - clusters.ColorControl.attributes.CurrentX, - clusters.ColorControl.attributes.CurrentY, - clusters.ColorControl.attributes.ColorMode, - clusters.ColorControl.attributes.ColorTemperatureMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, - clusters.IlluminanceMeasurement.attributes.MeasuredValue, - clusters.OccupancySensing.attributes.Occupancy - } - 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}) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) set_color_mode(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) 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 c696815a3e..20e2545349 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua @@ -61,15 +61,15 @@ local mock_bridge = test.mock_device.build_test_matter_device({ local function test_init_mock_bridge() test.mock_device.add_test_device(mock_bridge) + 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.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 58ba831074..f84c29c4c5 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua @@ -4,15 +4,43 @@ local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" - local clusters = require "st.matter.clusters" local button_attr = capabilities.button.button local utils = require "st.utils" local dkjson = require "dkjson" local uint32 = require "st.matter.data_types.Uint32" ---mock the actual device local mock_device = test.mock_device.build_test_matter_device({ + 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, + 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, + cluster_type = "SERVER", + } + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + } + } +}) + +local mock_device_battery = 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}, @@ -47,27 +75,34 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) --- add device for each mock device -local CLUSTER_SUBSCRIBE_LIST ={ - clusters.PowerSource.server.attributes.BatPercentRemaining, - clusters.Switch.server.events.InitialPress, - clusters.Switch.server.events.LongPress, - clusters.Switch.server.events.ShortRelease, - clusters.Switch.server.events.MultiPressComplete, -} - -local function 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}))) +local function expect_configure_buttons(device) + test.socket.capability:__expect_send(device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(device:generate_test_message("main", button_attr.pushed({state_change = false}))) +end + +local function update_profile() + test.socket.matter:__queue_receive({mock_device_battery.id, clusters.PowerSource.attributes.AttributeList:build_test_report_data( + mock_device_battery, 1, {uint32(clusters.PowerSource.attributes.BatPercentRemaining.ID)} + )}) + expect_configure_buttons(mock_device_battery) + mock_device_battery:expect_metadata_update({ profile = "button-battery" }) end local function test_init() - test.disable_startup_messages() - test.mock_device.add_test_device(mock_device) + 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) for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end end + + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) @@ -75,154 +110,223 @@ local function test_init() test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + expect_configure_buttons(mock_device) + mock_device:expect_metadata_update({ profile = "button" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read() - test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) - configure_buttons() - - local device_info_copy = utils.deep_copy(mock_device.raw_st_data) - device_info_copy.profile.id = "buttons-battery" - local device_info_json = dkjson.encode(device_info_copy) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json }) - configure_buttons() - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) +end + +local function test_init_battery() + local CLUSTER_SUBSCRIBE_LIST_BATTERY = { + clusters.PowerSource.server.attributes.AttributeList, + clusters.PowerSource.server.attributes.BatPercentRemaining, + 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_BATTERY[1]:subscribe(mock_device_battery) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST_BATTERY) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_battery)) end + end + + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device_battery) + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "added" }) + test.socket.matter:__expect_send({mock_device_battery.id, subscribe_request}) + + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "init" }) + test.socket.matter:__expect_send({mock_device_battery.id, subscribe_request}) + + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "doConfigure" }) + mock_device_battery:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end test.set_test_init_function(test_init) +test.register_coroutine_test( + "Simulate the profile change update taking affect and the device info changing", + function() + test.socket.matter:__set_channel_ordering("relaxed") + update_profile() + test.wait_for_events() + local device_info_copy = utils.deep_copy(mock_device_battery.raw_st_data) + device_info_copy.profile.id = "buttons-battery" + local device_info_json = dkjson.encode(device_info_copy) + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "infoChanged", device_info_json }) + -- due to the AttributeList being processed in update_profile, setting profiling_data.BATTERY_SUPPORT, + -- subsequent subscriptions will not include AttributeList. + local UPDATED_CLUSTER_SUBSCRIBE_LIST = { + clusters.PowerSource.server.attributes.BatPercentRemaining, + clusters.Switch.server.events.InitialPress, + clusters.Switch.server.events.LongPress, + clusters.Switch.server.events.ShortRelease, + clusters.Switch.server.events.MultiPressComplete, + } + local updated_subscribe_request = UPDATED_CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device_battery) + for i, clus in ipairs(UPDATED_CLUSTER_SUBSCRIBE_LIST) do + if i > 1 then updated_subscribe_request:merge(clus:subscribe(mock_device_battery)) end + end + test.socket.matter:__expect_send({mock_device_battery.id, updated_subscribe_request}) + expect_configure_buttons(mock_device_battery) + end, + { test_init = test_init_battery } +) + +test.register_coroutine_test( + "Handle received BatPercentRemaining from device.", + function() + update_profile() + test.socket.matter:__queue_receive( + { + mock_device_battery.id, + clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data( + mock_device_battery, 1, 150 + ) + } + ) + test.socket.capability:__expect_send( + mock_device_battery:generate_test_message( + "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) + ) + ) + end, + { test_init = test_init_battery } +) + 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, 1, {new_position = 1} --move to position 1? - ), + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 1, {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 } - }, - { - 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, with hold", { - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.Switch.events.InitialPress:build_test_event_report( - mock_device, 1, {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, 1, {new_position = 1} - ), + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 1, {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, 1, {new_position = 1} + ), + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.held({state_change = true})) } - }, - { - 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, 1, {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, 1, {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})) - }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 1, {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, 1, {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, 1, {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, 1, {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, 1, {previous_position = 1} - ) - } - }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 1, {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, 1, {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, 1, {previous_position = 1} + ) + } + }, } ) @@ -291,124 +395,129 @@ test.register_message_test( 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, 1, {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, 1, {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})) - }, - -} + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 1, {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, 1, {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, 1, {new_position = 1, total_number_of_presses_counted = 1, previous_position = 0} - ) - } - }, - { -- again, on a device that reports that it supports double press, this event - -- will not be generated. See the 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, 1, {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( - "Handle received BatPercentRemaining from device.", { { channel = "matter", direction = "receive", message = { mock_device.id, - clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data( - mock_device, 1, 150 - ), - }, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 1, {new_position = 1, total_number_of_presses_counted = 1, previous_position = 0} + ) + } + }, + { -- again, on a device that reports that it supports double press, this event + -- will not be generated. See the 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, 1, {new_position = 1, total_number_of_presses_counted = 4, previous_position = 0} + ) + } }, { channel = "capability", direction = "send", - message = mock_device:generate_test_message( - "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) - ), + message = mock_device:generate_test_message("main", button_attr.pushed_4x({state_change = true})) }, } ) +local function reset_battery_profiling_info() + local fields = require "switch_utils.fields" + mock_device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, {persist=true}) +end + test.register_coroutine_test( - "Test profile change to button-battery when battery percent remaining attribute (attribute ID 12) is available", + "Test profile does not change to button-battery when battery percent remaining attribute (attribute ID 12) is not available", function() + reset_battery_profiling_info() + test.wait_for_events() test.socket.matter:__queue_receive( { mock_device.id, - clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, {uint32(12)}) + clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, {uint32(10)}) } ) - mock_device:expect_metadata_update({ profile = "button-battery" }) end ) test.register_coroutine_test( - "Test profile does not change to button-battery when battery percent remaining attribute (attribute ID 12) is not available", + "Test profile change to button-batteryLevel when battery percent remaining attribute (attribute ID 14) is available", function() + reset_battery_profiling_info() + test.wait_for_events() test.socket.matter:__queue_receive( { mock_device.id, - clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, {uint32(10)}) + clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, {uint32( + clusters.PowerSource.attributes.BatChargeLevel.ID + )}) + } + ) + expect_configure_buttons(mock_device) + mock_device:expect_metadata_update({ profile = "button-batteryLevel" }) + end +) + +test.register_coroutine_test( + "Test profile change to button-battery when battery percent remaining attribute (attribute ID 12) is available", + function() + reset_battery_profiling_info() + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, {uint32( + clusters.PowerSource.attributes.BatPercentRemaining.ID + )}) } ) + expect_configure_buttons(mock_device) + mock_device:expect_metadata_update({ profile = "button-battery" }) end ) --- run the tests test.run_registered_tests() 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 301fb36cf0..33002a8203 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 @@ -6,16 +6,15 @@ 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 uint32 = require "st.matter.data_types.Uint32" --- 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"), + profile = t_utils.get_profile_definition("5-button.yml"), manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, - matter_version = {hardware = 1, sofrware = 1}, + matter_version = {hardware = 1, software = 1}, endpoints = { { endpoint_id = 0, @@ -32,7 +31,6 @@ local mock_device = test.mock_device.build_test_matter_device( feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, cluster_type = "SERVER" }, - {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER", feature_map = clusters.PowerSource.types.PowerSourceFeature.BATTERY} }, device_types = { {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch @@ -95,44 +93,135 @@ local mock_device = test.mock_device.build_test_matter_device( }, }) --- add device for each mock device -local CLUSTER_SUBSCRIBE_LIST ={ - clusters.PowerSource.server.attributes.BatPercentRemaining, - clusters.Switch.server.events.InitialPress, - clusters.Switch.server.events.LongPress, - clusters.Switch.server.events.ShortRelease, - clusters.Switch.server.events.MultiPressComplete, -} +local mock_device_battery = test.mock_device.build_test_matter_device( + { + profile = t_utils.get_profile_definition("5-button-battery.yml"), + manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, + matter_version = {hardware = 1, software = 1}, + endpoints = { + { + endpoint_id = 0, + clusters = {}, + device_types = { + {device_type_id = 0x0016, device_type_revision = 1}, -- RootNode + } + }, + { + endpoint_id = 10, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + cluster_type = "SERVER" + }, + {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER", feature_map = clusters.PowerSource.types.PowerSourceFeature.BATTERY} + }, + 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 = 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 + } + }, + }, + }) -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}))) +local function expect_configure_buttons(device) + test.socket.capability:__expect_send(device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(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(device:generate_test_message("button2", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(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(device:generate_test_message("button3", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(device:generate_test_message("button3", 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("button4", button_attr.pushed({state_change = false}))) + test.socket.matter:__expect_send({device.id, clusters.Switch.attributes.MultiPressMax:read(device, 50)}) + test.socket.capability:__expect_send(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, 60)}) - test.socket.capability:__expect_send(mock_device:generate_test_message("button5", button_attr.pushed({state_change = false}))) + test.socket.matter:__expect_send({device.id, clusters.Switch.attributes.MultiPressMax:read(device, 60)}) + test.socket.capability:__expect_send(device:generate_test_message("button5", button_attr.pushed({state_change = false}))) +end + +local function update_profile() + test.socket.matter:__queue_receive({mock_device_battery.id, clusters.PowerSource.attributes.AttributeList:build_test_report_data( + mock_device_battery, 10, {uint32(clusters.PowerSource.attributes.BatPercentRemaining.ID)} + )}) + expect_configure_buttons(mock_device_battery) + mock_device_battery:expect_metadata_update({ profile = "5-button-battery" }) end -- All messages queued and expectations set are done before the driver is actually run local function test_init() + 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) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end + end + -- 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" }) @@ -141,69 +230,140 @@ local function test_init() 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.PowerSource.attributes.AttributeList:read()}) - mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - expect_configure_buttons() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + expect_configure_buttons(mock_device) + mock_device:expect_metadata_update({ profile = "5-button" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end - -- 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() +local function test_init_battery() + local CLUSTER_SUBSCRIBE_LIST = { + clusters.PowerSource.server.attributes.AttributeList, + clusters.PowerSource.server.attributes.BatPercentRemaining, + 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_battery) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_battery)) end + end + + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device_battery) + + test.socket.matter:__expect_send({mock_device_battery.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "added" }) + + test.socket.matter:__expect_send({mock_device_battery.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "init" }) + + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "doConfigure" }) + mock_device_battery:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end + test.set_test_init_function(test_init) +test.register_coroutine_test( + "Simulate the profile change update taking affect and the device info changing", + function() + test.socket.matter:__set_channel_ordering("relaxed") + update_profile() + test.wait_for_events() + local device_info_copy = utils.deep_copy(mock_device_battery.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_battery.id, "infoChanged", device_info_json }) + -- due to the AttributeList being processed in update_profile, setting profiling_data.BATTERY_SUPPORT, + -- subsequent subscriptions will not include AttributeList. + local UPDATED_CLUSTER_SUBSCRIBE_LIST = { + clusters.PowerSource.server.attributes.BatPercentRemaining, + clusters.Switch.server.events.InitialPress, + clusters.Switch.server.events.LongPress, + clusters.Switch.server.events.ShortRelease, + clusters.Switch.server.events.MultiPressComplete, + } + local updated_subscribe_request = UPDATED_CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device_battery) + for i, clus in ipairs(UPDATED_CLUSTER_SUBSCRIBE_LIST) do + if i > 1 then updated_subscribe_request:merge(clus:subscribe(mock_device_battery)) end + end + test.socket.matter:__expect_send({mock_device_battery.id, updated_subscribe_request}) + expect_configure_buttons(mock_device_battery) + end, + { test_init = test_init_battery } +) + +test.register_coroutine_test( + "Handle received BatPercentRemaining from device.", + function() + update_profile() + test.socket.matter:__queue_receive( + { + mock_device_battery.id, + clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data( + mock_device_battery, 10, 150 + ) + } + ) + test.socket.capability:__expect_send( + mock_device_battery:generate_test_message( + "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) + ) + ) + end, + { test_init = test_init_battery } +) + 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 = "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 } - }, - { - 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 = "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 } - }, - { - 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( @@ -360,117 +520,117 @@ test.register_coroutine_test( 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 = "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})) } - }, - { - 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})) - }, + { + 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} - ) - } - }, + { + 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} + ) + } + }, } ) @@ -539,161 +699,136 @@ test.register_message_test( 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})) - }, - -} + { + 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("button4", - capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) - }, - } + { + 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( - "Handle received BatPercentRemaining from device.", { + "Receiving a max press attribute of 2 should emit correct event", { { channel = "matter", direction = "receive", message = { mock_device.id, - clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data( - mock_device, 10, 150 - ), + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + mock_device, 50, 2 + ) }, }, { channel = "capability", direction = "send", - message = mock_device:generate_test_message( - "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) - ), + message = mock_device:generate_test_message("button4", + 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("button5", 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} - ) + { + 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("button5", 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 } - -- no double event -} ) test.register_message_test( @@ -704,7 +839,7 @@ test.register_message_test( message = { mock_device.id, clusters.Switch.events.InitialPress:build_test_event_report( - mock_device, 60, {new_position = 1} + mock_device, 60, {new_position = 1} ) } }, @@ -714,7 +849,7 @@ test.register_message_test( message = { mock_device.id, clusters.Switch.events.LongPress:build_test_event_report( - mock_device, 60, {new_position = 1} + mock_device, 60, {new_position = 1} ) } }, @@ -729,7 +864,7 @@ test.register_message_test( message = { mock_device.id, clusters.Switch.events.InitialPress:build_test_event_report( - mock_device, 60, {new_position = 1} + mock_device, 60, {new_position = 1} ) } }, @@ -739,7 +874,7 @@ test.register_message_test( 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} + mock_device, 60, {new_position = 0, total_number_of_presses_counted = 1, previous_position=0} ) } }, @@ -750,5 +885,60 @@ test.register_message_test( } } ) --- run the tests + +local function reset_battery_profiling_info() + local fields = require "switch_utils.fields" + mock_device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, {persist=true}) +end + +test.register_coroutine_test( + "Test profile does not change to button-battery when battery percent remaining attribute (attribute ID 12) is not available", + function() + reset_battery_profiling_info() + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 10, {uint32(10)}) + } + ) + end +) + +test.register_coroutine_test( + "Test profile change to button-batteryLevel when battery percent remaining attribute (attribute ID 14) is available", + function() + reset_battery_profiling_info() + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 10, {uint32( + clusters.PowerSource.attributes.BatChargeLevel.ID + )}) + } + ) + expect_configure_buttons(mock_device) + mock_device:expect_metadata_update({ profile = "5-button-batteryLevel" }) + end +) + +test.register_coroutine_test( + "Test profile change to button-battery when battery percent remaining attribute (attribute ID 12) is available", + function() + reset_battery_profiling_info() + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 10, {uint32( + clusters.PowerSource.attributes.BatPercentRemaining.ID + )}) + } + ) + expect_configure_buttons(mock_device) + mock_device:expect_metadata_update({ profile = "5-button-battery" }) + end +) + test.run_registered_tests() 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 b57c416434..86edf45403 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -153,6 +153,11 @@ local function test_init() 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}) + + -- note that since disable_startup_messages is not explicitly called here, + -- the following subscribe is due to the init event sent by the test framework. test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) set_color_mode(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) @@ -166,6 +171,9 @@ local function test_init_x_y_color_mode() 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}) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) set_color_mode(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) @@ -178,6 +186,9 @@ local function test_init_no_hue_sat() subscribe_request:merge(cluster:subscribe(mock_device_no_hue_sat)) end end + test.socket.device_lifecycle:__queue_receive({ mock_device_no_hue_sat.id, "added" }) + test.socket.matter:__expect_send({mock_device_no_hue_sat.id, subscribe_request}) + test.socket.matter:__expect_send({mock_device_no_hue_sat.id, subscribe_request}) test.mock_device.add_test_device(mock_device_no_hue_sat) set_color_mode(mock_device_no_hue_sat, 1, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) 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 6129dfd116..da74443896 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 @@ -57,6 +57,10 @@ local function test_init() 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}) + + -- the following subscribe is due to the init event sent by the test framework. 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-switch/src/test/test_multi_switch_mcd.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua index 75e88b5fce..774e6bfa52 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 @@ -145,6 +145,10 @@ local function test_init_mock_3switch() } test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch) + test.socket.device_lifecycle:__queue_receive({ mock_3switch.id, "added" }) + test.socket.matter:__expect_send({mock_3switch.id, subscribe_request}) + + -- the following subscribe is due to the init event sent by the test framework. test.socket.matter:__expect_send({mock_3switch.id, subscribe_request}) test.mock_device.add_test_device(mock_3switch) end @@ -157,6 +161,9 @@ local function test_init_mock_2switch() } test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_2switch) + test.socket.device_lifecycle:__queue_receive({ mock_2switch.id, "added" }) + test.socket.matter:__expect_send({mock_2switch.id, subscribe_request}) + test.socket.matter:__expect_send({mock_2switch.id, subscribe_request}) test.mock_device.add_test_device(mock_2switch) end @@ -169,6 +176,9 @@ local function test_init_mock_3switch_non_sequential() } test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch_non_sequential) + test.socket.device_lifecycle:__queue_receive({ mock_3switch_non_sequential.id, "added" }) + test.socket.matter:__expect_send({mock_3switch_non_sequential.id, subscribe_request}) + test.socket.matter:__expect_send({mock_3switch_non_sequential.id, subscribe_request}) test.mock_device.add_test_device(mock_3switch_non_sequential) end From 3bbeb209cfcc8cab687b25f5aa7e312fc565eedc Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Wed, 4 Feb 2026 11:58:53 -0600 Subject: [PATCH 083/538] Add Ledvance child device vendor override --- .../matter-switch/src/switch_utils/fields.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index db66c2965c..d23c588b7c 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -93,10 +93,6 @@ SwitchFields.updated_fields = { } 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) @@ -105,6 +101,14 @@ SwitchFields.vendor_overrides = { [0x117C] = { -- IKEA_MANUFACTURER_ID [0x8000] = { is_ikea_scroll = true } }, + [0x1189] = { -- LEDVANCE_MANUFACTURER_ID + [0x0891] = { target_profile = "switch-binary", initial_profile = "light-binary" }, + [0x0892] = { target_profile = "switch-binary", initial_profile = "light-binary" }, + }, + [0x1321] = { -- SONOFF_MANUFACTURER_ID + [0x000C] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, + [0x000D] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, + }, } SwitchFields.switch_category_vendor_overrides = { From 935c0018c108bb324be2b39fee96711f05070735 Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:22:16 -0600 Subject: [PATCH 084/538] Remove unneeded override --- drivers/SmartThings/matter-switch/src/switch_utils/fields.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index d23c588b7c..dcc3403780 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -103,7 +103,6 @@ SwitchFields.vendor_overrides = { }, [0x1189] = { -- LEDVANCE_MANUFACTURER_ID [0x0891] = { target_profile = "switch-binary", initial_profile = "light-binary" }, - [0x0892] = { target_profile = "switch-binary", initial_profile = "light-binary" }, }, [0x1321] = { -- SONOFF_MANUFACTURER_ID [0x000C] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, From 1ecc63b37bac58b66d051ba511d54e37ca49cbad Mon Sep 17 00:00:00 2001 From: Marcin Krystian Tyminski <81477027+marcintyminski@users.noreply.github.com> Date: Wed, 4 Feb 2026 21:49:18 +0100 Subject: [PATCH 085/538] Merge pull request #2431 from marcintyminski/Add-support-for-frient-Smart-Sirens WWSTCERT-10049/10052/10055 Add support to smart sirens --- .../SmartThings/zigbee-siren/fingerprints.yml | 20 +- .../frient-siren-battery-source-tamper.yml | 53 + .../profiles/frient-siren-battery-source.yml | 51 + .../zigbee-siren/src/frient/init.lua | 406 ++++++- drivers/SmartThings/zigbee-siren/src/init.lua | 25 +- .../src/test/test_frient_siren.lua | 1046 +++++++++++++++-- .../src/test/test_frient_siren_tamper.lua | 1029 ++++++++++++++++ .../src/test/test_zigbee_siren.lua | 23 + 8 files changed, 2517 insertions(+), 136 deletions(-) create mode 100644 drivers/SmartThings/zigbee-siren/profiles/frient-siren-battery-source-tamper.yml create mode 100644 drivers/SmartThings/zigbee-siren/profiles/frient-siren-battery-source.yml create mode 100644 drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua diff --git a/drivers/SmartThings/zigbee-siren/fingerprints.yml b/drivers/SmartThings/zigbee-siren/fingerprints.yml index d40b7efb9a..57b192b294 100644 --- a/drivers/SmartThings/zigbee-siren/fingerprints.yml +++ b/drivers/SmartThings/zigbee-siren/fingerprints.yml @@ -4,16 +4,26 @@ zigbeeManufacturer : manufacturer: ClimaxTechnology model: SRAC_00.00.00.16TC deviceProfileName: switch-alarm-generic-siren-7 + - id: "frient/SIRZB-110" + deviceLabel: frient Smart Siren + manufacturer: frient A/S + model: SIRZB-110 + deviceProfileName: frient-siren-battery-source-tamper + - id: "frient/SIRZB-111" + deviceLabel: frient Smart Siren + manufacturer: frient A/S + model: SIRZB-111 + deviceProfileName: frient-siren-battery-source + - id: "frient/SIRZB-112" + deviceLabel: frient Smart Siren + manufacturer: frient A/S + model: SIRZB-112 + deviceProfileName: frient-siren-battery-source-tamper - id : Heiman/WarningDevice deviceLabel : HEIMAN Siren manufacturer : Heiman model : WarningDevice deviceProfileName : switch-alarm-generic-siren-7 - - id : frient/SIRZB-110 - deviceLabel : frient Siren - manufacturer : frient A/S - model : SIRZB-110 - deviceProfileName : switch-alarm-generic-siren-7 - id: "Sercomm Corp./SZ-SRN12N" deviceLabel: SmartThings Siren manufacturer: Sercomm Corp. diff --git a/drivers/SmartThings/zigbee-siren/profiles/frient-siren-battery-source-tamper.yml b/drivers/SmartThings/zigbee-siren/profiles/frient-siren-battery-source-tamper.yml new file mode 100644 index 0000000000..6c7e991e45 --- /dev/null +++ b/drivers/SmartThings/zigbee-siren/profiles/frient-siren-battery-source-tamper.yml @@ -0,0 +1,53 @@ +name: frient-siren-battery-source-tamper +components: + - id: main + capabilities: + - id: alarm + version: 1 + - id: tone + version: 1 + - id: battery + version: 1 + - id: powerSource + version: 1 + - id: tamperAlert + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Siren + - id: SirenVolume + capabilities: + - id: mode + version: 1 + - id: SirenVoice + capabilities: + - id: mode + version: 1 + - id: SquawkVolume + capabilities: + - id: mode + version: 1 + - id: SquawkVoice + capabilities: + - id: mode + version: 1 + - id: WarningDuration + capabilities: + - id: mode + version: 1 +preferences: + - title: "Max alarm duration (s)" + name: maxWarningDuration + description: "After how many seconds should the alarm turn off" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 65534 + default: 240 +metadata: + mnmn: SmartThings + vid: SmartThings-smartthings-frient_Siren_Tamper \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-siren/profiles/frient-siren-battery-source.yml b/drivers/SmartThings/zigbee-siren/profiles/frient-siren-battery-source.yml new file mode 100644 index 0000000000..5248db2b54 --- /dev/null +++ b/drivers/SmartThings/zigbee-siren/profiles/frient-siren-battery-source.yml @@ -0,0 +1,51 @@ +name: frient-siren-battery-source +components: + - id: main + capabilities: + - id: alarm + version: 1 + - id: tone + version: 1 + - id: battery + version: 1 + - id: powerSource + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Siren + - id: SirenVolume + capabilities: + - id: mode + version: 1 + - id: SirenVoice + capabilities: + - id: mode + version: 1 + - id: SquawkVolume + capabilities: + - id: mode + version: 1 + - id: SquawkVoice + capabilities: + - id: mode + version: 1 + - id: WarningDuration + capabilities: + - id: mode + version: 1 +preferences: + - title: "Max alarm duration (s)" + name: maxWarningDuration + description: "After how many seconds should the alarm turn off" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 65534 + default: 240 +metadata: + mnmn: SmartThings + vid: SmartThings-smartthings-frient_Siren \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-siren/src/frient/init.lua b/drivers/SmartThings/zigbee-siren/src/frient/init.lua index 085b8ecd79..78738ca9a8 100644 --- a/drivers/SmartThings/zigbee-siren/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-siren/src/frient/init.lua @@ -11,87 +11,417 @@ -- WITHOUT 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 battery_defaults = require "st.zigbee.defaults.battery_defaults" --ZCL local zcl_clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local Basic = zcl_clusters.Basic local IASWD = zcl_clusters.IASWD -local SirenConfiguration = IASWD.types.SirenConfiguration +local IASZone = zcl_clusters.IASZone local IaswdLevel = IASWD.types.IaswdLevel +local SirenConfiguration = IASWD.types.SirenConfiguration +local SquawkConfiguration = IASWD.types.SquawkConfiguration +local SquawkMode = IASWD.types.SquawkMode +local WarningMode = IASWD.types.WarningMode +local PowerConfiguration = zcl_clusters.PowerConfiguration --capability local capabilities = require "st.capabilities" local alarm = capabilities.alarm -local switch = capabilities.switch local ALARM_COMMAND = "alarmCommand" -local ALARM_LAST_DURATION = "lastDuration" -local ALARM_MAX_DURATION = "maxDuration" +local ALARM_DURATION = "warningDuration" +local SIREN_FIXED_ENDIAN_SW_VERSION = "010903" -local ALARM_DEFAULT_MAX_DURATION = 0x00B4 -local ALARM_STROBE_DUTY_CYCLE = 00 +local DEFAULT_MAX_WARNING_DURATION = 0x00F0 +local PRIMARY_SW_VERSION = "primary_sw_version" +local DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR = 0x8000 +local DEVELCO_MANUFACTURER_CODE = 0x1015 +local IASZONE_ENDPOINT = 0x2B local alarm_command = { OFF = 0, SIREN = 1, - STROBE = 2, - BOTH = 3 } -local send_siren_command = function(device) - local max_duration = device:get_field(ALARM_MAX_DURATION) - local warning_duration = max_duration and max_duration or ALARM_DEFAULT_MAX_DURATION - local duty_cycle = ALARM_STROBE_DUTY_CYCLE +local IASZone_configuration = { + { + cluster = IASZone.ID, + attribute = IASZone.attributes.ZoneStatus.ID, + minimum_interval = 0, + maximum_interval = 6*60*60, + data_type = IASZone.attributes.ZoneStatus.base_type, + reportable_change = 1 + } +} - device:set_field(ALARM_LAST_DURATION, warning_duration, {persist = true}) +local SQUAWK_VOICE_MAP = { + ["Armed"] = 0, + ["Disarmed"] = 1 +} - local siren_configuration = SirenConfiguration(0xC1) +local VOLUME_MAP = { + ["Low"] = 0, + ["Medium"] = 1, + ["High"] = 2, + ["Very High"] = 3 +} +local SIREN_VOICE_MAP = { + ["Burglar"] = 1, + ["Fire"] = 2, + ["Emergency"] = 3, + ["Panic"] = 4, + ["Panic Fire"] = 5, + ["Panic Emergency"] = 6 +} - device:send( - IASWD.server.commands.StartWarning( - device, - siren_configuration, - data_types.Uint16(warning_duration), - data_types.Uint8(duty_cycle), - data_types.Enum8(IaswdLevel.LOW_LEVEL) +local WARNING_DURATION_MAP = { + ["5 seconds"] = 5, + ["10 seconds"] = 10, + ["15 seconds"] = 15, + ["20 seconds"] = 20, + ["25 seconds"] = 25, + ["30 seconds"] = 30, + ["40 seconds"] = 40, + ["50 seconds"] = 50, + ["1 minute"] = 60, + ["2 minutes"] = 120, + ["3 minutes"] = 180, + ["4 minutes"] = 240, + ["5 minutes"] = 300, + ["10 minutes"] = 600 +} + +local MODEL_DEVICE_PROFILE_MAP = { + ["SIRZB-110"] = "frient-siren-battery-source-tamper", + ["SIRZB-111"] = "frient-siren-battery-source" +} + +local BATTERY_CONFIG_APPLIED_KEY = "_frient_battery_config_applied" + +local function get_current_max_warning_duration(device) + return device.preferences.maxWarningDuration == nil and DEFAULT_MAX_WARNING_DURATION or device.preferences.maxWarningDuration +end + +local function get_warning_duration(device) + -- User may select one of predefine modes on the UI or input duration in preferences + -- every time ALARM_DURATION is updated + local selected_duration = device:get_field(ALARM_DURATION) + local current_max_warning_duration = get_current_max_warning_duration(device) + + local warning_duration + if selected_duration == nil or selected_duration > current_max_warning_duration then + warning_duration = current_max_warning_duration + else + warning_duration = selected_duration + end + return warning_duration +end + +local function configure_battery_handling_based_on_fw(driver, device) + local sw_version = device:get_field(PRIMARY_SW_VERSION) + local applied_state = device:get_field(BATTERY_CONFIG_APPLIED_KEY) + + if sw_version and sw_version < SIREN_FIXED_ENDIAN_SW_VERSION then + if applied_state ~= "voltage" then + -- Old firmware - does not support BatteryPercentageRemaining attribute, use battery defaults (voltage-based) + battery_defaults.build_linear_voltage_init(3.3, 4.1)(driver, device) + device:set_field(BATTERY_CONFIG_APPLIED_KEY, "voltage", { persist = true }) + return true + end + else + if applied_state ~= "percentage" then + -- New firmware - supports BatteryPercentageRemaining, remove voltage monitoring + device:remove_configured_attribute(PowerConfiguration.ID, PowerConfiguration.attributes.BatteryVoltage.ID) + device:remove_monitored_attribute(PowerConfiguration.ID, PowerConfiguration.attributes.BatteryVoltage.ID) + device:set_field(BATTERY_CONFIG_APPLIED_KEY, "percentage", { persist = true }) + return true + end + end + + return false +end + +local function device_init(driver, device) + for _, attribute in ipairs(IASZone_configuration) do + device:add_configured_attribute(attribute) + device:add_monitored_attribute(attribute) + end +end + +local function device_added (driver, device) + for comp_name, comp in pairs(device.profile.components) do + if comp_name ~= "main" then + if comp_name == "SirenVoice" then + device:emit_component_event(comp, capabilities.mode.supportedModes({"Burglar", "Fire", "Emergency", "Panic","Panic Fire","Panic Emergency" }, {visibility = {displayed = false}})) + device:emit_component_event(comp, capabilities.mode.supportedArguments({"Burglar", "Fire", "Emergency", "Panic","Panic Fire","Panic Emergency" }, {visibility = {displayed = false}})) + device:emit_component_event(comp, capabilities.mode.mode("Burglar")) + elseif comp_name == "SquawkVoice" then + device:emit_component_event(comp, capabilities.mode.supportedModes({"Armed", "Disarmed"}, {visibility = {displayed = false}})) + device:emit_component_event(comp, capabilities.mode.supportedArguments({"Armed", "Disarmed"}, {visibility = {displayed = false}})) + device:emit_component_event(comp, capabilities.mode.mode("Armed")) + elseif comp_name == "WarningDuration" then + device:emit_component_event(comp, capabilities.mode.supportedModes({ + "5 seconds", "10 seconds", "15 seconds", "20 seconds", "25 seconds", "30 seconds", "40 seconds", "50 seconds", + "1 minute", "2 minutes", "3 minutes", "4 minutes", "5 minutes", "10 minutes" + }, {visibility = {displayed = false}})) + device:emit_component_event(comp, capabilities.mode.supportedArguments({ + "5 seconds", "10 seconds", "15 seconds", "20 seconds", "25 seconds", "30 seconds", "40 seconds", "50 seconds", + "1 minute", "2 minutes", "3 minutes", "4 minutes", "5 minutes", "10 minutes" + }, {visibility = {displayed = false}})) + device:emit_component_event(comp, capabilities.mode.mode("4 minutes")) + else + device:emit_component_event(comp, capabilities.mode.supportedModes({"Low", "Medium", "High", "Very High"}, {visibility = {displayed = false}})) + device:emit_component_event(comp, capabilities.mode.supportedArguments({"Low", "Medium", "High", "Very High"}, {visibility = {displayed = false}})) + device:emit_component_event(comp, capabilities.mode.mode("Very High")) + end + end + end + + device:emit_event(capabilities.alarm.alarm.off()) + + if(device:supports_capability(capabilities.tamperAlert)) then + device:emit_event(capabilities.tamperAlert.tamper.clear()) + end +end + +local function do_refresh(driver, device) + device:refresh() + device:send(IASZone.attributes.ZoneStatus:read(device):to_endpoint(IASZONE_ENDPOINT)) + + -- Check if we have the software version + local sw_version = device:get_field(PRIMARY_SW_VERSION) + if ((sw_version == nil) or (sw_version == "")) then + device:send(cluster_base.read_manufacturer_specific_attribute(device, Basic.ID, DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR, DEVELCO_MANUFACTURER_CODE)) + end +end + +local function do_configure(driver, device) + local maxWarningDuration = get_current_max_warning_duration(device) + device:set_field(ALARM_DURATION, maxWarningDuration , { persist = true}) + device:send(IASWD.attributes.MaxDuration:write(device, maxWarningDuration):to_endpoint(0x2B)) + + -- Check if we have the software version + local sw_version = device:get_field(PRIMARY_SW_VERSION) + if ((sw_version == nil) or (sw_version == "")) then + device:send(cluster_base.read_manufacturer_specific_attribute(device, Basic.ID, DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR, DEVELCO_MANUFACTURER_CODE)) + else + configure_battery_handling_based_on_fw(driver, device) + end + + -- handle frient sirens that were already connected and using old device profile + if (not device:supports_capability_by_id(capabilities.mode.ID)) then + device:try_update_metadata({profile = MODEL_DEVICE_PROFILE_MAP[device:get_model()]}) + end + + device.thread:call_with_delay(5, function() + do_refresh(driver, device) + end) + device:configure() +end + +local function primary_sw_version_attr_handler(driver, device, value, zb_rx) + local primary_sw_version = value.value:gsub('.', function (c) return string.format('%02x', string.byte(c)) end) + device:set_field(PRIMARY_SW_VERSION, primary_sw_version, {persist = true}) + local config_changed = configure_battery_handling_based_on_fw(driver, device) + if config_changed then + device.thread:call_with_delay(1, function() + device:configure() + end) + end + device.thread:call_with_delay(config_changed and 2 or 1, function() + do_refresh(driver, device) + end) +end + +local function generate_event_from_zone_status(driver, device, zone_status, zb_rx) + if device:supports_capability(capabilities.tamperAlert) then + device:emit_event_for_endpoint( + zb_rx.address_header.src_endpoint.value, + zone_status:is_tamper_set() and capabilities.tamperAlert.tamper.detected() or capabilities.tamperAlert.tamper.clear() ) + end + device:emit_event_for_endpoint( + zb_rx.address_header.src_endpoint.value, + zone_status:is_ac_mains_fault_set() and capabilities.powerSource.powerSource.battery() or capabilities.powerSource.powerSource.mains() + ) +end + +local function ias_zone_status_attr_handler(driver, device, zone_status, zb_rx) + generate_event_from_zone_status(driver, device, zone_status, zb_rx) +end + +local function ias_zone_status_change_handler(driver, device, zb_rx) + local zone_status = zb_rx.body.zcl_body.zone_status + generate_event_from_zone_status(driver, device, zone_status, zb_rx) +end + +local function send_siren_command(device, warning_mode, warning_siren_level) + -- Check if we have the software version first + local sw_version = device:get_field(PRIMARY_SW_VERSION) + if ((sw_version == nil) or (sw_version == "")) then + device:send(cluster_base.read_manufacturer_specific_attribute(device, Basic.ID, DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR, DEVELCO_MANUFACTURER_CODE)) + end + + local warning_duration = get_warning_duration(device) + + local siren_configuration + + if (sw_version and sw_version < SIREN_FIXED_ENDIAN_SW_VERSION) then + -- Old frient firmware, the endian format is reversed + local siren_config_value = (warning_siren_level << 6) | warning_mode + siren_configuration = SirenConfiguration(siren_config_value) + else + siren_configuration = SirenConfiguration(0x00) + siren_configuration:set_warning_mode(warning_mode) + siren_configuration:set_siren_level(warning_siren_level) + end + + device:send( + IASWD.server.commands.StartWarning( + device, + siren_configuration, + data_types.Uint16(warning_duration), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) ) end -local siren_switch_both_handler = function(driver, device, command) - device:set_field(ALARM_COMMAND, alarm_command.BOTH, {persist = true}) - send_siren_command(device) +local function siren_switch_off_handler(driver, device, command) + device:set_field(ALARM_COMMAND, alarm_command.OFF, {persist = true}) + send_siren_command(device, WarningMode.STOP, IaswdLevel.LOW_LEVEL) end -local siren_alarm_siren_handler = function(driver, device, command) +local function siren_alarm_siren_handler(driver, device, command) device:set_field(ALARM_COMMAND, alarm_command.SIREN, {persist = true}) - send_siren_command(device) + + -- delay is needed to allow st automations get updated fields when mode, volume, voice is set sequentially + device.thread:call_with_delay(1, function() + local sirenVoice_msg = device:get_latest_state("SirenVoice", capabilities.mode.ID, capabilities.mode.mode.NAME) + local sirenVolume_msg = device:get_latest_state("SirenVolume", capabilities.mode.ID, capabilities.mode.mode.NAME) + send_siren_command(device,sirenVoice_msg == nil and WarningMode.BURGLAR or SIREN_VOICE_MAP[sirenVoice_msg] , sirenVolume_msg == nil and IaswdLevel.VERY_HIGH_LEVEL or VOLUME_MAP[sirenVolume_msg]) + end) + + local warningDurationDelay = get_warning_duration(device) + + device.thread:call_with_delay(warningDurationDelay, function() -- Send command to switch from siren to off in the app when the siren is done + if(device:get_field(ALARM_COMMAND) == alarm_command.SIREN) then + siren_switch_off_handler(driver, device, command) + end + end) +end + +local function send_squawk_command(device, squawk_mode, squawk_siren_level) + -- Check if we have the software version first + local sw_version = device:get_field(PRIMARY_SW_VERSION) + + if ((sw_version == nil) or (sw_version == "")) then + device:send(cluster_base.read_manufacturer_specific_attribute(device, Basic.ID, DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR, DEVELCO_MANUFACTURER_CODE)) + end + + local squawk_configuration + + if (sw_version and sw_version < SIREN_FIXED_ENDIAN_SW_VERSION) then + -- Old frient firmware, the endian format is reversed + local squawk_config_value = (squawk_siren_level << 6) | squawk_mode + squawk_configuration = SquawkConfiguration(squawk_config_value) + else + squawk_configuration = SquawkConfiguration(0x00) + squawk_configuration:set_squawk_mode(squawk_mode) + squawk_configuration:set_squawk_level(squawk_siren_level) + end + + device:send( + IASWD.server.commands.Squawk( + device, + squawk_configuration + ) + ) +end + +local function siren_tone_beep_handler(driver, device, command) + device.thread:call_with_delay(1, function () + local squawkVolume_msg = device:get_latest_state("SquawkVolume", capabilities.mode.ID, capabilities.mode.mode.NAME) + local squawkVoice_msg = device:get_latest_state("SquawkVoice", capabilities.mode.ID, capabilities.mode.mode.NAME) + + send_squawk_command(device, SQUAWK_VOICE_MAP[squawkVoice_msg] or SquawkMode.SOUND_FOR_SYSTEM_IS_ARMED,VOLUME_MAP[squawkVolume_msg] or IaswdLevel.VERY_HIGH_LEVEL) + end ) end -local siren_alarm_strobe_handler = function(driver, device, command) - device:set_field(ALARM_COMMAND, alarm_command.STROBE, {persist = true}) - send_siren_command(device) +local function info_changed(driver, device, event, args) + for name, info in pairs(device.preferences) do + if (device.preferences[name] ~= nil and args.old_st_store.preferences[name] ~= device.preferences[name]) then + local input = device.preferences[name] + if (name == "maxWarningDuration") then + device:send(IASWD.attributes.MaxDuration:write(device, tonumber(input))) + end + end + end end -local siren_switch_on_handler = function(driver, device, command) - siren_switch_both_handler(driver, device, command) +local function siren_mode_handler(driver, device, command) + local mode_set = command.args.mode + local component = command.component + local compObj = device.profile.components[component] + + if compObj then + if component == "WarningDuration" then + local warning_duration = WARNING_DURATION_MAP[mode_set] or DEFAULT_MAX_WARNING_DURATION + device:set_field(ALARM_DURATION, warning_duration, {persist = true}) + end + end + + device.thread:call_with_delay(2,function() + device:emit_component_event( + compObj, + capabilities.mode.mode(mode_set)) + end) end local frient_siren_driver = { NAME = "frient A/S", + lifecycle_handlers = { + added = device_added, + init = device_init, + doConfigure = do_configure, + infoChanged = info_changed, + }, capability_handlers = { [alarm.ID] = { - [alarm.commands.both.NAME] = siren_switch_both_handler, + [alarm.commands.off.NAME] = siren_switch_off_handler, [alarm.commands.siren.NAME] = siren_alarm_siren_handler, - [alarm.commands.strobe.NAME] = siren_alarm_strobe_handler + [alarm.commands.both.NAME] = siren_alarm_siren_handler + }, + [capabilities.tone.ID] = { + [capabilities.tone.commands.beep.NAME] = siren_tone_beep_handler + }, + [capabilities.mode.ID] = { + [capabilities.mode.commands.setMode.NAME] = siren_mode_handler + }, + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zigbee_handlers = { + cluster = { + [IASZone.ID] = { + [IASZone.client.commands.ZoneStatusChangeNotification.ID] = ias_zone_status_change_handler + } }, - [switch.ID] = { - [switch.commands.on.NAME] = siren_switch_on_handler, + attr = { + [IASZone.ID] = { + [IASZone.attributes.ZoneStatus.ID] = ias_zone_status_attr_handler + }, + [Basic.ID] = { + [DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR] = primary_sw_version_attr_handler + } } }, can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "frient A/S" + return device:get_manufacturer() == "frient A/S" and (device:get_model() == "SIRZB-110" or device:get_model() == "SIRZB-111" or device:get_model() == "SIRZB-112") end } -return frient_siren_driver +return frient_siren_driver \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-siren/src/init.lua b/drivers/SmartThings/zigbee-siren/src/init.lua index 0f08b067fb..21b1865b07 100644 --- a/drivers/SmartThings/zigbee-siren/src/init.lua +++ b/drivers/SmartThings/zigbee-siren/src/init.lua @@ -32,7 +32,9 @@ local IaswdLevel = IASWD.types.IaswdLevel local capabilities = require "st.capabilities" local alarm = capabilities.alarm local switch = capabilities.switch - +local mode = capabilities.mode +local battery = capabilities.battery +local refresh = capabilities.refresh -- Constants local ALARM_COMMAND = "alarmCommand" local ALARM_LAST_DURATION = "lastDuration" @@ -81,13 +83,13 @@ local send_siren_command = function(device, warning_mode, warning_siren_level, s siren_configuration:set_siren_level(warning_siren_level) device:send( - IASWD.server.commands.StartWarning( - device, - siren_configuration, - data_types.Uint16(warning_duration), - data_types.Uint8(duty_cycle), - data_types.Enum8(strobe_level) - ) + IASWD.server.commands.StartWarning( + device, + siren_configuration, + data_types.Uint16(warning_duration), + data_types.Uint8(duty_cycle), + data_types.Enum8(strobe_level) + ) ) end @@ -157,7 +159,10 @@ end local zigbee_siren_driver_template = { supported_capabilities = { alarm, - switch + switch, + mode, + battery, + refresh }, ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, zigbee_handlers = { @@ -206,4 +211,4 @@ local zigbee_siren_driver_template = { defaults.register_for_default_handlers(zigbee_siren_driver_template, zigbee_siren_driver_template.supported_capabilities) local zigbee_siren = ZigbeeDriver("zigbee-siren", zigbee_siren_driver_template) -zigbee_siren:run() +zigbee_siren:run() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua index 098b2ab1bd..f8951d3b31 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua @@ -15,110 +15,990 @@ -- Mock out globals local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local IasEnrollResponseCode = require "st.zigbee.generated.zcl_clusters.IASZone.types.EnrollResponseCode" +local OnOff = clusters.OnOff +local Scenes = clusters.Scenes +local Basic = clusters.Basic +local Identify = clusters.Identify +local PowerConfiguration = clusters.PowerConfiguration +local Groups = clusters.Groups +local IASZone = clusters.IASZone local IASWD = clusters.IASWD +local IaswdLevel = IASWD.types.IaswdLevel +local WarningMode = IASWD.types.WarningMode +local SquawkMode = IASWD.types.SquawkMode +local SirenConfiguration = IASWD.types.SirenConfiguration +local SquawkConfiguration = IASWD.types.SquawkConfiguration +local ZoneStatusAttribute = IASZone.attributes.ZoneStatus + +local PRIMARY_SW_VERSION = "primary_sw_version" +local SIREN_ENDIAN = "siren_endian" +local ALARM_DURATION = "warningDuration" +local ALARM_DEFAULT_MAX_DURATION = 0x00F0 +local ALARM_DURATION_TEST_VALUE = 5 +local DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR = 0x8000 +local DEVELCO_MANUFACTURER_CODE = 0x1015 + +local capabilities = require "st.capabilities" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local data_types = require "st.zigbee.data_types" -local SirenConfiguration = require "st.zigbee.generated.zcl_clusters.IASWD.types.SirenConfiguration" local t_utils = require "integration_test.utils" + local mock_device = test.mock_device.build_test_zigbee_device( - { - profile = t_utils.get_profile_definition("switch-alarm.yml"), - zigbee_endpoints = { - [1] = { - id = 1, - manufacturer = "frient A/S", - model = "SIRZB-110", - server_clusters = {0x0502} + { + profile = t_utils.get_profile_definition("frient-siren-battery-source.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "frient A/S", + model = "SIRZB-111", + server_clusters = { Scenes.ID, OnOff.ID} + }, + [0x2B] = { + id = 0x2B, + server_clusters = { Basic.ID, Identify.ID, PowerConfiguration.ID, Groups.ID, IASZone.ID, IASWD.ID } + } + } } - } - } ) zigbee_test_utils.prepare_zigbee_env_info() local function test_init() - test.mock_device.add_test_device(mock_device)end + test.mock_device.add_test_device(mock_device) +end test.set_test_init_function(test_init) +local function set_new_firmware_and_defaults() + -- set the firmware version and endian format for testing + mock_device:set_field(PRIMARY_SW_VERSION, "010903", {persist = true}) + mock_device:set_field(SIREN_ENDIAN, nil, {persist = true}) + -- set test durations and parameters + mock_device:set_field(ALARM_DURATION, ALARM_DURATION_TEST_VALUE, {persist = true}) +end + +local function set_older_firmware_and_defaults() + -- set the firmware version and endian format for testing + mock_device:set_field(PRIMARY_SW_VERSION, "010901", {persist = true}) + mock_device:set_field(SIREN_ENDIAN, nil, {persist = true}) + -- set test durations and parameters + mock_device:set_field(ALARM_DURATION, ALARM_DURATION_TEST_VALUE, {persist = true}) +end + +local function set_no_firmware_and_defaults() + -- set the firmware version and endian format for testing + mock_device:set_field(PRIMARY_SW_VERSION, nil, {persist = true}) + mock_device:set_field(SIREN_ENDIAN, nil, {persist = true}) + -- set test durations and parameters + mock_device:set_field(ALARM_DURATION, ALARM_DURATION_TEST_VALUE, {persist = true}) +end + +local function get_siren_commands_new_fw( warningMode, sirenLevel, duration ) + local expectedSirenONConfiguration = SirenConfiguration(0x00) + expectedSirenONConfiguration:set_warning_mode(warningMode) --WarningMode.BURGLAR + expectedSirenONConfiguration:set_siren_level(sirenLevel) --IaswdLevel.VERY_HIGH_LEVEL + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning( + mock_device, + expectedSirenONConfiguration, + data_types.Uint16(duration), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) +end + +local function get_siren_commands_old_fw(warningMode, sirenLevel) + local expectedSirenONConfiguration + local siren_config_value = (sirenLevel << 6) | warningMode + expectedSirenONConfiguration = SirenConfiguration(siren_config_value) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning( + mock_device, + expectedSirenONConfiguration, + data_types.Uint16(ALARM_DURATION_TEST_VALUE), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) +end + +local function get_siren_OFF_commands(duration) + local expectedSirenOFFConfiguration = SirenConfiguration(0x00) + expectedSirenOFFConfiguration:set_warning_mode(WarningMode.STOP) + expectedSirenOFFConfiguration:set_siren_level(IaswdLevel.LOW_LEVEL) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning( + mock_device, + expectedSirenOFFConfiguration, + data_types.Uint16(duration and duration or ALARM_DURATION_TEST_VALUE), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) +end + +local function get_squawk_command_new_fw(squawk_mode, squawk_siren_level) + local expected_squawk_configuration = SquawkConfiguration(0x00) + expected_squawk_configuration:set_squawk_mode(squawk_mode) + expected_squawk_configuration:set_squawk_level(squawk_siren_level) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.Squawk( + mock_device, + expected_squawk_configuration + ) + }) +end + +local function get_squawk_command_older_fw(squawk_mode, squawk_siren_level) + local expected_squawk_configuration + local squawk_config_value = (squawk_siren_level << 6) | squawk_mode + expected_squawk_configuration = SquawkConfiguration(squawk_config_value) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.Squawk( + mock_device, + expected_squawk_configuration + ) + }) +end + +test.register_coroutine_test( + "lifecycles - init and doConfigure test", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + + 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) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request( + mock_device, + zigbee_test_utils.mock_hub_eui, + PowerConfiguration.ID, + 0x2B + ):to_endpoint(0x2B) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining: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, + IASZone.ID, + 0x2B + ):to_endpoint(0x2B) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.ZoneStatus:configure_reporting( + mock_device, + 0, + 21600, + 1 + ) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.IASCIEAddress:write( + mock_device, + zigbee_test_utils.mock_hub_eui + ) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.server.commands.ZoneEnrollResponse( + mock_device, + IasEnrollResponseCode.SUCCESS, + 0x00 + ) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + + end +) + +test.register_coroutine_test( + "lifecycle - added test", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SirenVoice", + capabilities.mode.supportedModes({ "Burglar", "Fire", "Emergency", "Panic", "Panic Fire", "Panic Emergency" }, { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SirenVoice", + capabilities.mode.supportedArguments({ "Burglar", "Fire", "Emergency", "Panic", "Panic Fire", "Panic Emergency" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SirenVoice", + capabilities.mode.mode("Burglar") + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SirenVolume", + capabilities.mode.supportedModes({ "Low", "Medium", "High", "Very High" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SirenVolume", + capabilities.mode.supportedArguments({ "Low", "Medium", "High", "Very High" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SirenVolume", + capabilities.mode.mode({value = "Very High"}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SquawkVoice", + capabilities.mode.supportedModes({ "Armed", "Disarmed" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SquawkVoice", + capabilities.mode.supportedArguments({ "Armed", "Disarmed" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SquawkVoice", + capabilities.mode.mode("Armed") + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SquawkVolume", + capabilities.mode.supportedModes({ "Low", "Medium", "High", "Very High" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SquawkVolume", + capabilities.mode.supportedArguments({ "Low", "Medium", "High", "Very High" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SquawkVolume", + capabilities.mode.mode("Very High") + ) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "WarningDuration", + capabilities.mode.supportedModes( + { + "5 seconds", "10 seconds", "15 seconds", "20 seconds", "25 seconds", "30 seconds", "40 seconds", "50 seconds", + "1 minute", "2 minutes", "3 minutes", "4 minutes", "5 minutes", "10 minutes" + }, + { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "WarningDuration", + capabilities.mode.supportedArguments( + { + "5 seconds", "10 seconds", "15 seconds", "20 seconds", "25 seconds", "30 seconds", "40 seconds", "50 seconds", + "1 minute", "2 minutes", "3 minutes", "4 minutes", "5 minutes", "10 minutes" + }, + { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "WarningDuration", + capabilities.mode.mode("4 minutes") + ) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.alarm.alarm.off() + ) + ) + end +) + +test.register_coroutine_test( + "Should detect newer firmware version and use correct endian format to turn on the siren (test with default settings)", + function() + set_new_firmware_and_defaults() + + -- Verify fields are set correctly + assert(mock_device:get_field(PRIMARY_SW_VERSION) >= "010903", "PRIMARY_SW_VERSION should be greater than or equal to '010903'") + + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + + -- Test the siren command with reversed endian + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_siren_commands_new_fw(WarningMode.BURGLAR,IaswdLevel.VERY_HIGH_LEVEL,ALARM_DURATION_TEST_VALUE) + test.mock_time.advance_time(ALARM_DURATION_TEST_VALUE+1) + -- stop the siren + -- Expect the OFF command + get_siren_OFF_commands() + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Should detect older firmware version and use correct endian format to turn on the siren (test with default settings)", + function() + set_older_firmware_and_defaults() + -- Verify fields are set correctly + assert(mock_device:get_field(PRIMARY_SW_VERSION) < "010903", "PRIMARY_SW_VERSION should be lower than '010903'") + + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + + -- Test the siren command with reversed endian + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_siren_commands_old_fw(WarningMode.BURGLAR,IaswdLevel.VERY_HIGH_LEVEL) + test.mock_time.advance_time(ALARM_DURATION_TEST_VALUE) + -- stop the siren + -- Expect the OFF command + get_siren_OFF_commands() + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Alarm OFF should be handled", + function() + set_new_firmware_and_defaults() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "off", args = {} } + }) + get_siren_OFF_commands() + test.wait_for_events() + end +) + +test.register_coroutine_test( + "SirenVoice mode 'Fire' and SirenVolume mode 'LOW' should be handled", + function() + set_new_firmware_and_defaults() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SirenVoice", command = "setMode", args = {"Fire"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SirenVoice", capabilities.mode.mode("Fire")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SirenVolume", command = "setMode", args = {"Low"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SirenVolume", capabilities.mode.mode("Low")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "WarningDuration", command = "setMode", args = {"5 seconds"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("WarningDuration", capabilities.mode.mode("5 seconds")) + ) + test.wait_for_events() + -- Test siren with update configuration + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_siren_commands_new_fw(WarningMode.FIRE,IaswdLevel.LOW_LEVEL,ALARM_DURATION_TEST_VALUE) + test.mock_time.advance_time(5) + -- stop the siren + -- Expect the OFF command + get_siren_OFF_commands() + end +) + +test.register_coroutine_test( + "SirenVoice mode 'Fire' and SirenVolume mode 'LOW' should be handled - in case of NO FW version was reported before", + function() + set_no_firmware_and_defaults() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SirenVoice", command = "setMode", args = {"Fire"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SirenVoice", capabilities.mode.mode("Fire")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SirenVolume", command = "setMode", args = {"Low"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SirenVolume", capabilities.mode.mode("Low")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "WarningDuration", command = "setMode", args = {"5 seconds"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("WarningDuration", capabilities.mode.mode("5 seconds")) + ) + + test.wait_for_events() + -- Test siren with update configuration + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + test.mock_time.advance_time(1) + -- Read the FW version first + 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 + ):to_endpoint(0x2B) + } + ) + -- Expect the command with given configuration + get_siren_commands_new_fw(WarningMode.FIRE,IaswdLevel.LOW_LEVEL,ALARM_DURATION_TEST_VALUE) + + test.mock_time.advance_time(5) + -- stop the siren + -- Read the FW version first + 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 + ):to_endpoint(0x2B) + } + ) + -- Expect the OFF command + get_siren_OFF_commands() + end +) + +test.register_coroutine_test( + "SirenVoice mode 'Fire' and SirenVolume mode 'LOW' should be handled - when maxWarningDuration is shorted then selected mode", + function() + local expectedWarningDuration = 7 + set_new_firmware_and_defaults() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(expectedWarningDuration, "oneshot") + + test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed( + { + preferences = { + maxWarningDuration = expectedWarningDuration + } + } + )) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.attributes.MaxDuration:write( + mock_device, + expectedWarningDuration + ) + }) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SirenVoice", command = "setMode", args = {"Fire"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SirenVoice", capabilities.mode.mode("Fire")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SirenVolume", command = "setMode", args = {"Low"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SirenVolume", capabilities.mode.mode("Low")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "WarningDuration", command = "setMode", args = {"50 seconds"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("WarningDuration", capabilities.mode.mode("50 seconds")) + ) + + test.wait_for_events() + -- Test siren with update configuration + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_siren_commands_new_fw(WarningMode.FIRE,IaswdLevel.LOW_LEVEL, expectedWarningDuration) + + test.mock_time.advance_time(expectedWarningDuration) + -- stop the siren + -- Expect the OFF command + get_siren_OFF_commands(expectedWarningDuration) + end +) + +test.register_coroutine_test( + "Should detect newer firmware version and use correct endian format to turn on squawk (test with default settings)", + function() + + set_new_firmware_and_defaults() + + -- Verify fields are set correctly + assert(mock_device:get_field(PRIMARY_SW_VERSION) >= "010903", "PRIMARY_SW_VERSION should be greater than or equal to '010903'") + + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Test the siren command with reversed endian + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "tone", component = "main", command = "beep", args = {} } + }) + + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_squawk_command_new_fw( SquawkMode.SOUND_FOR_SYSTEM_IS_ARMED, IaswdLevel.VERY_HIGH_LEVEL ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Should detect older firmware version and use correct endian format to turn on squawk (test with default settings)", + function() + set_older_firmware_and_defaults() + + -- Verify fields are set correctly + assert(mock_device:get_field(PRIMARY_SW_VERSION) < "010903", "PRIMARY_SW_VERSION should be lower than '010903'") + + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Test the siren command with reversed endian + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "tone", component = "main", command = "beep", args = {} } + }) + + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_squawk_command_older_fw( SquawkMode.SOUND_FOR_SYSTEM_IS_ARMED, IaswdLevel.VERY_HIGH_LEVEL ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "SquawkVoice mode 'Disarmed' and SquawkVolume mode 'Medium' should be handled", + function() + set_new_firmware_and_defaults() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SquawkVoice", command = "setMode", args = { "Disarmed" } } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SquawkVoice", capabilities.mode.mode("Disarmed")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SquawkVolume", command = "setMode", args = { "Medium" } } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SquawkVolume", capabilities.mode.mode("Medium")) + ) + + test.wait_for_events() + -- Test siren with update configuration + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "tone", component = "main", command = "beep", args = {} } + }) + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_squawk_command_new_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) + end +) + +test.register_coroutine_test( + "SquawkVoice mode 'Disarmed' and SquawkVolume mode 'Medium' should be handled - in case of OLD FW version was reported before", + function() + set_older_firmware_and_defaults() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SquawkVoice", command = "setMode", args = { "Disarmed" } } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SquawkVoice", capabilities.mode.mode("Disarmed")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SquawkVolume", command = "setMode", args = { "Medium" } } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SquawkVolume", capabilities.mode.mode("Medium")) + ) + + test.wait_for_events() + -- Test siren with update configuration + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "tone", component = "main", command = "beep", args = {} } + }) + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_squawk_command_older_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) + end +) + +test.register_coroutine_test( + "SquawkVoice mode 'Disarmed' and SquawkVolume mode 'Medium' should be handled - in case of NO FW version was reported before", + function() + set_no_firmware_and_defaults() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SquawkVoice", command = "setMode", args = { "Disarmed" } } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SquawkVoice", capabilities.mode.mode("Disarmed")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SquawkVolume", command = "setMode", args = { "Medium" } } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SquawkVolume", capabilities.mode.mode("Medium")) + ) + + test.wait_for_events() + -- Test siren with update configuration + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "tone", component = "main", command = "beep", args = {} } + }) + test.mock_time.advance_time(1) + -- Read the FW version first + 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 + ):to_endpoint(0x2B) + } + ) + -- Expect the command with given configuration + get_squawk_command_new_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) + end +) + +test.register_coroutine_test( + "Refresh should be handled - new FW", + function() + set_new_firmware_and_defaults() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } + }) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + IASZone.attributes.ZoneStatus:read(mock_device) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + IASZone.attributes.ZoneStatus:read(mock_device) + } + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Refresh should be handled - FW not known", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } + }) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) + } + ) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + IASZone.attributes.ZoneStatus:read(mock_device) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + IASZone.attributes.ZoneStatus:read(mock_device) + } + ) + + 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_message_test( + "Power source / mains should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ZoneStatusAttribute:build_test_attr_report(mock_device, 0x0000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) + } + } +) + test.register_message_test( - "Capability(switch) command(on) on should be handled", - { - { - channel = "capability", - direction = "receive", - message = { mock_device.id, { capability = "switch", command = "on", args = { } } } - }, - { - channel = "zigbee", - direction = "send", - message = { mock_device.id, IASWD.server.commands.StartWarning(mock_device, - SirenConfiguration(0xC1), - data_types.Uint16(0x00B4), - data_types.Uint8(00), - data_types.Enum8(00)) } - } - } + "Power source / battery and tamper clear should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ZoneStatusAttribute:build_test_attr_report(mock_device, 0x0081) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.battery()) + } + } ) test.register_message_test( - "Capability(alarm) command(both) on should be handled", - { - { - channel = "capability", - direction = "receive", - message = { mock_device.id, { capability = "alarm", component = "main", command = "both", args = { } } } - }, - { - channel = "zigbee", - direction = "send", - message = { mock_device.id, IASWD.server.commands.StartWarning(mock_device, - SirenConfiguration(0xC1), - data_types.Uint16(0x00B4), - data_types.Uint8(00), - data_types.Enum8(00)) } - } - } + "Min battery voltage report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device, 0xC8) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) + } + } ) test.register_message_test( - "Capability(alarm) command(siren) on should be handled", - { - { - channel = "capability", - direction = "receive", - message = { mock_device.id, { capability = "alarm", component = "main", command = "siren", args = { } } } - }, - { - channel = "zigbee", - direction = "send", - message = { mock_device.id, IASWD.server.commands.StartWarning(mock_device, - SirenConfiguration(0xC1), - data_types.Uint16(0x00B4), - data_types.Uint8(00), - data_types.Enum8(00)) } - } - } + "Medium battery voltage report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device, 0x64) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(50)) + } + } ) test.register_message_test( - "Capability(alarm) command(strobe) on should be handled", - { - { - channel = "capability", - direction = "receive", - message = { mock_device.id, { capability = "alarm", component = "main", command = "strobe", args = { } } } - }, - { - channel = "zigbee", - direction = "send", - message = { mock_device.id, IASWD.server.commands.StartWarning(mock_device, - SirenConfiguration(0xC1), - data_types.Uint16(0x00B4), - data_types.Uint8(00), - data_types.Enum8(00)) } - } - } -) - -test.run_registered_tests() + "Max battery voltage report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device, 0) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) + } + } +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua new file mode 100644 index 0000000000..810e691849 --- /dev/null +++ b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua @@ -0,0 +1,1029 @@ +-- 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 cluster_base = require "st.zigbee.cluster_base" +local IasEnrollResponseCode = require "st.zigbee.generated.zcl_clusters.IASZone.types.EnrollResponseCode" +local OnOff = clusters.OnOff +local Scenes = clusters.Scenes +local Basic = clusters.Basic +local Identify = clusters.Identify +local PowerConfiguration = clusters.PowerConfiguration +local Groups = clusters.Groups +local IASZone = clusters.IASZone +local IASWD = clusters.IASWD +local IaswdLevel = IASWD.types.IaswdLevel +local WarningMode = IASWD.types.WarningMode +local SquawkMode = IASWD.types.SquawkMode +local SirenConfiguration = IASWD.types.SirenConfiguration +local SquawkConfiguration = IASWD.types.SquawkConfiguration +local ZoneStatusAttribute = IASZone.attributes.ZoneStatus + +local PRIMARY_SW_VERSION = "primary_sw_version" +local SIREN_ENDIAN = "siren_endian" +local ALARM_DURATION = "warningDuration" +local ALARM_DEFAULT_MAX_DURATION = 0x00F0 +local ALARM_DURATION_TEST_VALUE = 5 +local DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR = 0x8000 +local DEVELCO_MANUFACTURER_CODE = 0x1015 + +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local data_types = require "st.zigbee.data_types" +local t_utils = require "integration_test.utils" + + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("frient-siren-battery-source-tamper.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "frient A/S", + model = "SIRZB-111", + server_clusters = { Scenes.ID, OnOff.ID} + }, + [0x2B] = { + id = 0x2B, + server_clusters = { Basic.ID, Identify.ID, PowerConfiguration.ID, Groups.ID, IASZone.ID, IASWD.ID } + } + } + } +) + +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) + +local function set_new_firmware_and_defaults() + -- set the firmware version and endian format for testing + mock_device:set_field(PRIMARY_SW_VERSION, "010903", {persist = true}) + mock_device:set_field(SIREN_ENDIAN, nil, {persist = true}) + -- set test durations and parameters + mock_device:set_field(ALARM_DURATION, ALARM_DURATION_TEST_VALUE, {persist = true}) +end + +local function set_older_firmware_and_defaults() + -- set the firmware version and endian format for testing + mock_device:set_field(PRIMARY_SW_VERSION, "010901", {persist = true}) + mock_device:set_field(SIREN_ENDIAN, nil, {persist = true}) + -- set test durations and parameters + mock_device:set_field(ALARM_DURATION, ALARM_DURATION_TEST_VALUE, {persist = true}) +end + +local function set_no_firmware_and_defaults() + -- set the firmware version and endian format for testing + mock_device:set_field(PRIMARY_SW_VERSION, nil, {persist = true}) + mock_device:set_field(SIREN_ENDIAN, nil, {persist = true}) + -- set test durations and parameters + mock_device:set_field(ALARM_DURATION, ALARM_DURATION_TEST_VALUE, {persist = true}) +end + +local function get_siren_commands_new_fw(warningMode, sirenLevel, duration) + local expectedSirenONConfiguration = SirenConfiguration(0x00) + expectedSirenONConfiguration:set_warning_mode(warningMode) --WarningMode.BURGLAR + expectedSirenONConfiguration:set_siren_level(sirenLevel) --IaswdLevel.VERY_HIGH_LEVEL + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning( + mock_device, + expectedSirenONConfiguration, + data_types.Uint16(duration), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) +end + +local function get_siren_commands_old_fw(warningMode, sirenLevel) + local expectedSirenONConfiguration + local siren_config_value = (sirenLevel << 6) | warningMode + expectedSirenONConfiguration = SirenConfiguration(siren_config_value) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning( + mock_device, + expectedSirenONConfiguration, + data_types.Uint16(ALARM_DURATION_TEST_VALUE), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) +end + +local function get_siren_OFF_commands(duration) + local expectedSirenOFFConfiguration = SirenConfiguration(0x00) + expectedSirenOFFConfiguration:set_warning_mode(WarningMode.STOP) + expectedSirenOFFConfiguration:set_siren_level(IaswdLevel.LOW_LEVEL) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning( + mock_device, + expectedSirenOFFConfiguration, + data_types.Uint16(duration and duration or ALARM_DURATION_TEST_VALUE), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) +end + +local function get_squawk_command_new_fw(squawk_mode, squawk_siren_level) + local expected_squawk_configuration = SquawkConfiguration(0x00) + expected_squawk_configuration:set_squawk_mode(squawk_mode) + expected_squawk_configuration:set_squawk_level(squawk_siren_level) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.Squawk( + mock_device, + expected_squawk_configuration + ) + }) +end + +local function get_squawk_command_older_fw(squawk_mode, squawk_siren_level) + local expected_squawk_configuration + local squawk_config_value = (squawk_siren_level << 6) | squawk_mode + expected_squawk_configuration = SquawkConfiguration(squawk_config_value) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.Squawk( + mock_device, + expected_squawk_configuration + ) + }) +end + +test.register_coroutine_test( + "lifecycles - init and doConfigure test", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + + 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) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request( + mock_device, + zigbee_test_utils.mock_hub_eui, + PowerConfiguration.ID, + 0x2B + ):to_endpoint(0x2B) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining: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, + IASZone.ID, + 0x2B + ):to_endpoint(0x2B) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.ZoneStatus:configure_reporting( + mock_device, + 0, + 21600, + 1 + ) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.IASCIEAddress:write( + mock_device, + zigbee_test_utils.mock_hub_eui + ) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.server.commands.ZoneEnrollResponse( + mock_device, + IasEnrollResponseCode.SUCCESS, + 0x00 + ) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + + end +) + +test.register_coroutine_test( + "lifecycle - added test", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SirenVoice", + capabilities.mode.supportedModes({ "Burglar", "Fire", "Emergency", "Panic", "Panic Fire", "Panic Emergency" }, { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SirenVoice", + capabilities.mode.supportedArguments({ "Burglar", "Fire", "Emergency", "Panic", "Panic Fire", "Panic Emergency" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SirenVoice", + capabilities.mode.mode("Burglar") + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SirenVolume", + capabilities.mode.supportedModes({ "Low", "Medium", "High", "Very High" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SirenVolume", + capabilities.mode.supportedArguments({ "Low", "Medium", "High", "Very High" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SirenVolume", + capabilities.mode.mode({value = "Very High"}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SquawkVoice", + capabilities.mode.supportedModes({ "Armed", "Disarmed" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SquawkVoice", + capabilities.mode.supportedArguments({ "Armed", "Disarmed" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SquawkVoice", + capabilities.mode.mode("Armed") + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SquawkVolume", + capabilities.mode.supportedModes({ "Low", "Medium", "High", "Very High" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SquawkVolume", + capabilities.mode.supportedArguments({ "Low", "Medium", "High", "Very High" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "SquawkVolume", + capabilities.mode.mode("Very High") + ) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "WarningDuration", + capabilities.mode.supportedModes( + { + "5 seconds", "10 seconds", "15 seconds", "20 seconds", "25 seconds", "30 seconds", "40 seconds", "50 seconds", + "1 minute", "2 minutes", "3 minutes", "4 minutes", "5 minutes", "10 minutes" + }, + { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "WarningDuration", + capabilities.mode.supportedArguments( + { + "5 seconds", "10 seconds", "15 seconds", "20 seconds", "25 seconds", "30 seconds", "40 seconds", "50 seconds", + "1 minute", "2 minutes", "3 minutes", "4 minutes", "5 minutes", "10 minutes" + }, + { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "WarningDuration", + capabilities.mode.mode("4 minutes") + ) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.alarm.alarm.off() + ) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.tamperAlert.tamper.clear() + ) + ) + end +) + +test.register_coroutine_test( + "Should detect newer firmware version and use correct endian format to turn on the siren (test with default settings)", + function() + set_new_firmware_and_defaults() + + -- Verify fields are set correctly + assert(mock_device:get_field(PRIMARY_SW_VERSION) >= "010903", "PRIMARY_SW_VERSION should be greater than or equal to '010903'") + + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + + -- Test the siren command with reversed endian + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_siren_commands_new_fw(WarningMode.BURGLAR,IaswdLevel.VERY_HIGH_LEVEL,ALARM_DURATION_TEST_VALUE) + test.mock_time.advance_time(ALARM_DURATION_TEST_VALUE) + -- stop the siren + -- Expect the OFF command + get_siren_OFF_commands() + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Should detect older firmware version and use correct endian format to turn on the siren (test with default settings)", + function() + set_older_firmware_and_defaults() + -- Verify fields are set correctly + assert(mock_device:get_field(PRIMARY_SW_VERSION) < "010903", "PRIMARY_SW_VERSION should be lower than '010903'") + + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + + -- Test the siren command with reversed endian + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_siren_commands_old_fw(WarningMode.BURGLAR,IaswdLevel.VERY_HIGH_LEVEL) + test.mock_time.advance_time(ALARM_DURATION_TEST_VALUE) + -- stop the siren + -- Expect the OFF command + get_siren_OFF_commands() + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Alarm OFF should be handled", + function() + set_new_firmware_and_defaults() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "off", args = {} } + }) + get_siren_OFF_commands() + test.wait_for_events() + end +) + +test.register_coroutine_test( + "SirenVoice mode 'Fire' and SirenVolume mode 'LOW' should be handled", + function() + set_new_firmware_and_defaults() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SirenVoice", command = "setMode", args = {"Fire"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SirenVoice", capabilities.mode.mode("Fire")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SirenVolume", command = "setMode", args = {"Low"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SirenVolume", capabilities.mode.mode("Low")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "WarningDuration", command = "setMode", args = {"5 seconds"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("WarningDuration", capabilities.mode.mode("5 seconds")) + ) + + test.wait_for_events() + -- Test siren with update configuration + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_siren_commands_new_fw(WarningMode.FIRE,IaswdLevel.LOW_LEVEL,ALARM_DURATION_TEST_VALUE) + test.mock_time.advance_time(5) + -- stop the siren + -- Expect the OFF command + get_siren_OFF_commands() + end +) + +test.register_coroutine_test( + "SirenVoice mode 'Fire' and SirenVolume mode 'LOW' should be handled - in case of NO FW version was reported before", + function() + set_no_firmware_and_defaults() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SirenVoice", command = "setMode", args = {"Fire"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SirenVoice", capabilities.mode.mode("Fire")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SirenVolume", command = "setMode", args = {"Low"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SirenVolume", capabilities.mode.mode("Low")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "WarningDuration", command = "setMode", args = {"5 seconds"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("WarningDuration", capabilities.mode.mode("5 seconds")) + ) + + test.wait_for_events() + -- Test siren with update configuration + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + test.mock_time.advance_time(1) + -- Read the FW version first + 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 + ):to_endpoint(0x2B) + } + ) + -- Expect the command with given configuration + get_siren_commands_new_fw(WarningMode.FIRE,IaswdLevel.LOW_LEVEL,ALARM_DURATION_TEST_VALUE) + + test.mock_time.advance_time(5) + -- stop the siren + -- Read the FW version first + 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 + ):to_endpoint(0x2B) + } + ) + -- Expect the OFF command + get_siren_OFF_commands() + end +) + +test.register_coroutine_test( + "SirenVoice mode 'Fire' and SirenVolume mode 'LOW' should be handled - when maxWarningDuration is shorted then selected mode", + function() + local expectedWarningDuration = 7 + set_new_firmware_and_defaults() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(expectedWarningDuration, "oneshot") + + test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed( + { + preferences = { + maxWarningDuration = expectedWarningDuration + } + } + )) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.attributes.MaxDuration:write( + mock_device, + expectedWarningDuration + ) + }) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SirenVoice", command = "setMode", args = {"Fire"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SirenVoice", capabilities.mode.mode("Fire")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SirenVolume", command = "setMode", args = {"Low"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SirenVolume", capabilities.mode.mode("Low")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "WarningDuration", command = "setMode", args = {"50 seconds"} } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("WarningDuration", capabilities.mode.mode("50 seconds")) + ) + + test.wait_for_events() + -- Test siren with update configuration + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_siren_commands_new_fw(WarningMode.FIRE,IaswdLevel.LOW_LEVEL, expectedWarningDuration) + + test.mock_time.advance_time(expectedWarningDuration) + -- stop the siren + -- Expect the OFF command + get_siren_OFF_commands(expectedWarningDuration) + end +) + +test.register_coroutine_test( + "Should detect newer firmware version and use correct endian format to turn on squawk (test with default settings)", + function() + set_new_firmware_and_defaults() + + -- Verify fields are set correctly + assert(mock_device:get_field(PRIMARY_SW_VERSION) >= "010903", "PRIMARY_SW_VERSION should be greater than or equal to '010903'") + + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Test the siren command with reversed endian + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "tone", component = "main", command = "beep", args = {} } + }) + + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_squawk_command_new_fw( SquawkMode.SOUND_FOR_SYSTEM_IS_ARMED, IaswdLevel.VERY_HIGH_LEVEL ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Should detect older firmware version and use correct endian format to turn on squawk (test with default settings)", + function() + set_older_firmware_and_defaults() + + -- Verify fields are set correctly + assert(mock_device:get_field(PRIMARY_SW_VERSION) < "010903", "PRIMARY_SW_VERSION should be lower than '010903'") + + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Test the siren command with reversed endian + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "tone", component = "main", command = "beep", args = {} } + }) + + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_squawk_command_older_fw( SquawkMode.SOUND_FOR_SYSTEM_IS_ARMED, IaswdLevel.VERY_HIGH_LEVEL ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "SquawkVoice mode 'Disarmed' and SquawkVolume mode 'Medium' should be handled", + function() + set_new_firmware_and_defaults() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SquawkVoice", command = "setMode", args = { "Disarmed" } } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SquawkVoice", capabilities.mode.mode("Disarmed")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SquawkVolume", command = "setMode", args = { "Medium" } } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SquawkVolume", capabilities.mode.mode("Medium")) + ) + + test.wait_for_events() + -- Test siren with update configuration + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "tone", component = "main", command = "beep", args = {} } + }) + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_squawk_command_new_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) + end +) + +test.register_coroutine_test( + "SquawkVoice mode 'Disarmed' and SquawkVolume mode 'Medium' should be handled - in case of OLD FW version was reported before", + function() + set_older_firmware_and_defaults() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SquawkVoice", command = "setMode", args = { "Disarmed" } } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SquawkVoice", capabilities.mode.mode("Disarmed")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SquawkVolume", command = "setMode", args = { "Medium" } } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SquawkVolume", capabilities.mode.mode("Medium")) + ) + + test.wait_for_events() + -- Test siren with update configuration + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "tone", component = "main", command = "beep", args = {} } + }) + test.mock_time.advance_time(1) + -- Expect the command with given configuration + get_squawk_command_older_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) + end +) + +test.register_coroutine_test( + "SquawkVoice mode 'Disarmed' and SquawkVolume mode 'Medium' should be handled - in case of NO FW version was reported before", + function() + set_no_firmware_and_defaults() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SquawkVoice", command = "setMode", args = { "Disarmed" } } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SquawkVoice", capabilities.mode.mode("Disarmed")) + ) + + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "SquawkVolume", command = "setMode", args = { "Medium" } } + }) + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("SquawkVolume", capabilities.mode.mode("Medium")) + ) + + test.wait_for_events() + -- Test siren with update configuration + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "tone", component = "main", command = "beep", args = {} } + }) + test.mock_time.advance_time(1) + -- Read the FW version first + 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 + ):to_endpoint(0x2B) + } + ) + -- Expect the command with given configuration + get_squawk_command_new_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) + end +) + +test.register_coroutine_test( + "Refresh should be handled - new FW", + function() + set_new_firmware_and_defaults() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } + }) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + IASZone.attributes.ZoneStatus:read(mock_device) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + IASZone.attributes.ZoneStatus:read(mock_device) + } + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Refresh should be handled - FW not known", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } + }) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) + } + ) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + IASZone.attributes.ZoneStatus:read(mock_device) + } + ) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + IASZone.attributes.ZoneStatus:read(mock_device) + } + ) + + 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_message_test( + "Power source / mains should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ZoneStatusAttribute:build_test_attr_report(mock_device, 0x0005) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + inner_block_ordering = "relaxed" + } +) + +test.register_message_test( + "Power source / battery should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ZoneStatusAttribute:build_test_attr_report(mock_device, 0x0081) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.battery()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + }, + { + 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.BatteryPercentageRemaining:build_test_attr_report(mock_device, 0xC8) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) + } + } +) + +test.register_message_test( + "Medium battery voltage report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device, 0x64) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(50)) + } + } +) + +test.register_message_test( + "Max battery voltage report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device, 0) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) + } + } +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua index e8de7eb958..3dc707a297 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua @@ -19,6 +19,7 @@ local zcl_cmds = require "st.zigbee.zcl.global_commands" local IASZone = clusters.IASZone local IASWD = clusters.IASWD local OnOff = clusters.OnOff +local PowerConfiguration = clusters.PowerConfiguration local capabilities = require "st.capabilities" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local data_types = require "st.zigbee.data_types" @@ -212,6 +213,20 @@ test.register_coroutine_test( mock_device.id, OnOff.attributes.OnOff: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, PowerConfiguration.ID) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:configure_reporting(mock_device, 30, 21600, 1) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) + }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -265,6 +280,14 @@ test.register_message_test( OnOff.attributes.OnOff:read(mock_device) } }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) + } + } }, { inner_block_ordering = "relaxed" From a963bb5e9a4358cd5d4444924048ccc562faba5e Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 5 Feb 2026 10:38:51 -0800 Subject: [PATCH 086/538] WWSTCERT-10212 Osram SMART WIFI MATTER WALL SWITCH 1G --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index e418d34d3b..948279c3f3 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1837,6 +1837,11 @@ matterManufacturer: vendorId: 0x1189 productId: 0x0B93 deviceProfileName: light-color-level + - id: "4489/2194" + deviceLabel: SMART WIFI MATTER WALL SWITCH 1G + vendorId: 0x1189 + productId: 0x0892 + deviceProfileName: switch-binary #Shelly - id: "5264/1" deviceLabel: Shelly Plug S MTR Gen3 From 6f4e5d2730ec27799d3966f8842cbcb42e409fab Mon Sep 17 00:00:00 2001 From: Marcin Krystian Tyminski <81477027+marcintyminski@users.noreply.github.com> Date: Thu, 5 Feb 2026 19:49:54 +0100 Subject: [PATCH 087/538] Merge pull request #2454 from marcintyminski/Add-support-to-frient-vibration-sensor-WISZB-137 WWSTCERT-8745 Add support to frient vibration sensor wiszb 137 --- .../zigbee-contact/fingerprints.yml | 5 + ...cceleration-motion-temperature-battery.yml | 50 ++ ...ion-motion-temperature-contact-battery.yml | 89 +++ .../zigbee-contact/src/configurations.lua | 16 + .../src/frient/frient-vibration/init.lua | 225 ++++++++ .../zigbee-contact/src/frient/init.lua | 5 +- .../src/test/test_frient_vibration_sensor.lua | 510 ++++++++++++++++++ 7 files changed, 899 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-contact/profiles/acceleration-motion-temperature-battery.yml create mode 100644 drivers/SmartThings/zigbee-contact/profiles/acceleration-motion-temperature-contact-battery.yml create mode 100644 drivers/SmartThings/zigbee-contact/src/frient/frient-vibration/init.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua diff --git a/drivers/SmartThings/zigbee-contact/fingerprints.yml b/drivers/SmartThings/zigbee-contact/fingerprints.yml index f35ca2f7ba..c30c3296a2 100644 --- a/drivers/SmartThings/zigbee-contact/fingerprints.yml +++ b/drivers/SmartThings/zigbee-contact/fingerprints.yml @@ -154,6 +154,11 @@ zigbeeManufacturer: manufacturer: frient A/S model: WISZB-131 deviceProfileName: frient-contact-battery-temperature + - id: "frient A/S/WISZB-137" + deviceLabel: frient Vibration Sensor + manufacturer: frient A/S + model: WISZB-137 + deviceProfileName: acceleration-motion-temperature-battery - id: "Compacta/ZBWDS" deviceLabel: Smartenit Open/Closed Sensor manufacturer: Compacta diff --git a/drivers/SmartThings/zigbee-contact/profiles/acceleration-motion-temperature-battery.yml b/drivers/SmartThings/zigbee-contact/profiles/acceleration-motion-temperature-battery.yml new file mode 100644 index 0000000000..4f90f8a11e --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/profiles/acceleration-motion-temperature-battery.yml @@ -0,0 +1,50 @@ +name: acceleration-motion-temperature-battery +components: +- id: main + capabilities: + - id: accelerationSensor + version: 1 + - id: motionSensor + version: 1 + - id: threeAxis + version: 1 + - id: temperatureMeasurement + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: ContactSensor +preferences: + - 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 + - title: "Sensitivity level" + name: sensitivityLevel + description: "How sensitivite the device is to vibrations" + required: false + preferenceType: integer + definition: + minimum: 1 + maximum: 15 + default: 10 + - title: "Use with Contact Sensor" + name: garageSensor + required: false + preferenceType: enumeration + definition: + options: + "Yes": "Yes" + "No": "No" + default: "No" \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-contact/profiles/acceleration-motion-temperature-contact-battery.yml b/drivers/SmartThings/zigbee-contact/profiles/acceleration-motion-temperature-contact-battery.yml new file mode 100644 index 0000000000..c40eb49b9e --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/profiles/acceleration-motion-temperature-contact-battery.yml @@ -0,0 +1,89 @@ +name: acceleration-motion-temperature-contact-battery +components: +- id: main + capabilities: + - id: accelerationSensor + version: 1 + - id: motionSensor + version: 1 + - id: threeAxis + version: 1 + - id: contactSensor + version: 1 + - id: temperatureMeasurement + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: ContactSensor +preferences: + - 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 + - title: "Sensitivity level" + name: sensitivityLevel + description: "How sensitivite the device is to vibrations" + required: false + preferenceType: integer + definition: + minimum: 1 + maximum: 15 + default: 10 + - title: "Use with Contact Sensor" + name: garageSensor + required: false + preferenceType: enumeration + definition: + options: + "Yes": "Yes" + "No": "No" + default: "Yes" + - title: "Axis to activate Contact Sensor" + name: contactSensorAxis + required: false + preferenceType: enumeration + definition: + options: + "X": "X" + "Y": "Y" + "Z": "Z" + default: "Z" + - title: "Initial position (closed state)" + name: sensorInitialPosition + description: "Initial position of the device in the chosen axis" + required: false + preferenceType: number + definition: + minimum: -2000 + maximum: 2000 + default: 0 + - title: "Contact Sensor threshold (open)" + name: contactSensorValue + description: "Value change required to trigger contact sensor" + required: false + preferenceType: number + definition: + minimum: 20 + maximum: 4000 + default: 900 + - title: "Measurement tolerance" + name: tolerance + description: "Set the tolerance in percentage of the threshold" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 20 + default: 0 \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-contact/src/configurations.lua b/drivers/SmartThings/zigbee-contact/src/configurations.lua index aa28e1e43f..1e08ecffd4 100644 --- a/drivers/SmartThings/zigbee-contact/src/configurations.lua +++ b/drivers/SmartThings/zigbee-contact/src/configurations.lua @@ -129,6 +129,22 @@ local devices = { } } }, + FRIENT_VIBRATION_SENSOR_WISZB_137 = { + FINGERPRINTS = { + { mfr = "frient A/S", model = "WISZB-137" } + }, + CONFIGURATION = { + { + cluster = IASZone.ID, + attribute = IASZone.attributes.ZoneStatus.ID, + minimum_interval = 0, + maximum_interval = 3600, + data_type = IASZone.attributes.ZoneStatus.base_type, + reportable_change = 1, + endpoint = 0x2D + } + } + } } local configurations = {} diff --git a/drivers/SmartThings/zigbee-contact/src/frient/frient-vibration/init.lua b/drivers/SmartThings/zigbee-contact/src/frient/frient-vibration/init.lua new file mode 100644 index 0000000000..2514bfb63b --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/frient/frient-vibration/init.lua @@ -0,0 +1,225 @@ +-- 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 zcl_commands = require "st.zigbee.zcl.global_commands" +local zcl_clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local battery_defaults = require "st.zigbee.defaults.battery_defaults" +local device_management = require "st.zigbee.device_management" +local data_types = require "st.zigbee.data_types" +local threeAxis = capabilities.threeAxis + +local TemperatureMeasurement = zcl_clusters.TemperatureMeasurement +local IASZone = zcl_clusters.IASZone +local PowerConfiguration = zcl_clusters.PowerConfiguration +local POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT = 0x2D +local TEMPERATURE_ENDPOINT = 0x26 + +local FRIENT_DEVICE_FINGERPRINTS = { + { mfr = "frient A/S", model = "WISZB-137", }, +} + +local function can_handle_vibration_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 Frient_AccelerationMeasurementCluster = { + ID = 0xFC04, + ManufacturerSpecificCode = 0x1015, + attributes = { + MeasuredValueX = { ID = 0x0000, data_type = data_types.Int16 }, + MeasuredValueY = { ID = 0x0001, data_type = data_types.Int16 }, + MeasuredValueZ = { ID = 0x0002, data_type = data_types.Int16 } + }, +} + +local function acceleration_measure_report_handler(driver, device, zb_rx) + local measured_x, measured_y, measured_z + + for _, attribute_record in ipairs(zb_rx.body.zcl_body.attr_records) do + local attribute_id = attribute_record.attr_id.value + local axis_value = attribute_record.data.value + + if attribute_id == Frient_AccelerationMeasurementCluster.attributes.MeasuredValueX.ID then + measured_x = axis_value + elseif attribute_id == Frient_AccelerationMeasurementCluster.attributes.MeasuredValueY.ID then + measured_y = axis_value + elseif attribute_id == Frient_AccelerationMeasurementCluster.attributes.MeasuredValueZ.ID then + measured_z = axis_value + end + end + + if measured_x and measured_y and measured_z then + device:emit_event(threeAxis.threeAxis({measured_x, measured_y, measured_z})) + + if device:supports_capability(capabilities.contactSensor) then + local garageAxis = measured_x + if device.preferences.contactSensorAxis == "Y" then + garageAxis = measured_y + elseif device.preferences.contactSensorAxis == "Z" then + garageAxis = measured_z + end + local initial_position = device.preferences.sensorInitialPosition or 0 + if math.abs(initial_position - garageAxis) >= device.preferences.contactSensorValue - device.preferences.contactSensorValue * (device.preferences.tolerance / 100) then + device:emit_event(capabilities.contactSensor.contact.open()) + else + device:emit_event(capabilities.contactSensor.contact.closed()) + end + end + end +end + +local function get_cluster_configurations() + return { + { + cluster = Frient_AccelerationMeasurementCluster.ID, + attribute = Frient_AccelerationMeasurementCluster.attributes.MeasuredValueX.ID, + minimum_interval = 0, + maximum_interval = 300, + reportable_change = 0x0001, + data_type = data_types.Int16, + mfg_code = Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode + }, + { + cluster = Frient_AccelerationMeasurementCluster.ID, + attribute = Frient_AccelerationMeasurementCluster.attributes.MeasuredValueY.ID, + minimum_interval = 0, + maximum_interval = 300, + reportable_change = 0x0001, + data_type = data_types.Int16, + mfg_code = Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode + }, + { + cluster = Frient_AccelerationMeasurementCluster.ID, + attribute = Frient_AccelerationMeasurementCluster.attributes.MeasuredValueZ.ID, + minimum_interval = 0, + maximum_interval = 300, + reportable_change = 0x0001, + data_type = data_types.Int16, + mfg_code = Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode + } + } +end + +local function generate_event_from_zone_status(driver, device, zone_status, zb_rx) + device:emit_event(zone_status:is_alarm1_set() and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) + device:emit_event(zone_status:is_alarm2_set() and capabilities.accelerationSensor.acceleration.active() or capabilities.accelerationSensor.acceleration.inactive()) +end + +local function ias_zone_status_attr_handler(driver, device, attr_val, zb_rx) + generate_event_from_zone_status(driver, device, attr_val, zb_rx) +end + +local function ias_zone_status_change_handler(driver, device, zb_rx) + generate_event_from_zone_status(driver, device, zb_rx.body.zcl_body.zone_status, zb_rx) +end + +local function device_init(driver, device) + battery_defaults.build_linear_voltage_init(2.3, 3.0)(driver, device) + --Add the manufacturer-specific attributes to generate their configure reporting and bind requests + for _, config in pairs(get_cluster_configurations()) do + device:add_configured_attribute(config) + end +end + +local function do_refresh(driver, device) + device:send(IASZone.attributes.ZoneStatus:read(device):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT)) + device:send(cluster_base.read_manufacturer_specific_attribute(device, Frient_AccelerationMeasurementCluster.ID, Frient_AccelerationMeasurementCluster.attributes.MeasuredValueX.ID, Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT)) + device:send(cluster_base.read_manufacturer_specific_attribute(device, Frient_AccelerationMeasurementCluster.ID, Frient_AccelerationMeasurementCluster.attributes.MeasuredValueY.ID, Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT)) + device:send(cluster_base.read_manufacturer_specific_attribute(device, Frient_AccelerationMeasurementCluster.ID, Frient_AccelerationMeasurementCluster.attributes.MeasuredValueZ.ID, Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT)) + device:send(TemperatureMeasurement.attributes.MeasuredValue:read(device):to_endpoint(TEMPERATURE_ENDPOINT)) + device:send(PowerConfiguration.attributes.BatteryVoltage:read(device)) +end + +local function do_configure(driver, device, event, args) + device:configure() + + device:send(device_management.build_bind_request(device, zcl_clusters.IASZone.ID, driver.environment_info.hub_zigbee_eui, POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT)) + device:send(IASZone.attributes.ZoneStatus:configure_reporting(device, 0, 1*60*60, 1):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT)) + device:send(device_management.build_bind_request(device, Frient_AccelerationMeasurementCluster.ID, driver.environment_info.hub_zigbee_eui, POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT)) + + local sensitivityLevel = device.preferences.sensitivityLevel or 10 + device:send(IASZone.attributes.CurrentZoneSensitivityLevel:write(device, sensitivityLevel):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT)) + + local sensitivity = math.floor((device.preferences.temperatureSensitivity or 0.1) * 100 + 0.5) + device:send(TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(device, 30, 1 * 60 * 60, sensitivity):to_endpoint(TEMPERATURE_ENDPOINT)) + + device.thread:call_with_delay(5, function() + device:refresh() + end) +end + +local function info_changed(driver, device, event, args) + if args and args.old_st_store then + if args.old_st_store.preferences.sensitivityLevel ~= device.preferences.sensitivityLevel then + local sensitivityLevel = device.preferences.sensitivityLevel or 10 + device:send(IASZone.attributes.CurrentZoneSensitivityLevel:write(device, sensitivityLevel):to_endpoint(0x2D)) + end + if args.old_st_store.preferences.temperatureSensitivity ~= device.preferences.temperatureSensitivity then + local sensitivity = math.floor((device.preferences.temperatureSensitivity or 0.1)*100 + 0.5) + device:send(TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(device, 30, 1*60*60, sensitivity):to_endpoint(0x26)) + end + if args.old_st_store.preferences.garageSensor ~= device.preferences.garageSensor then + if device.preferences.garageSensor == "Yes" then + device:try_update_metadata({profile = "acceleration-motion-temperature-contact-battery"}) + elseif device.preferences.garageSensor == "No" then + device:try_update_metadata({profile = "acceleration-motion-temperature-battery"}) + end + end + device.thread:call_with_delay(5, function() + device:refresh() + end) + end +end + +local frient_vibration_driver_template = { + NAME = "frient vibration driver", + lifecycle_handlers = { + init = device_init, + doConfigure = do_configure, + infoChanged = info_changed + }, + zigbee_handlers = { + global = { + [Frient_AccelerationMeasurementCluster.ID] = { + [zcl_commands.ReportAttribute.ID] = acceleration_measure_report_handler, + [zcl_commands.ReadAttributeResponse.ID] = acceleration_measure_report_handler + } + }, + cluster = { + [IASZone.ID] = { + [IASZone.client.commands.ZoneStatusChangeNotification.ID] = ias_zone_status_change_handler + } + }, + attr = { + [IASZone.ID] = { + [IASZone.attributes.ZoneStatus.ID] = ias_zone_status_attr_handler + } + } + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh, + } + }, + can_handle = can_handle_vibration_sensor +} + +return frient_vibration_driver_template \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-contact/src/frient/init.lua b/drivers/SmartThings/zigbee-contact/src/frient/init.lua index e732664d91..1379d0a8c0 100644 --- a/drivers/SmartThings/zigbee-contact/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/frient/init.lua @@ -80,6 +80,9 @@ local frient_sensor = { doConfigure = do_configure, infoChanged = info_changed }, + sub_drivers = { + require("frient/frient-vibration"), + }, zigbee_handlers = { cluster = { [IASZone.ID] = { @@ -93,7 +96,7 @@ local frient_sensor = { } }, can_handle = function(opts, driver, device, ...) - return (device:get_manufacturer() == "frient A/S" and (device:get_model() == "WISZB-120" or device:get_model() == "WISZB-121" or device:get_model() == "WISZB-131")) + return (device:get_manufacturer() == "frient A/S") and (device:get_model() == "WISZB-120" or device:get_model() == "WISZB-121" or device:get_model() == "WISZB-131" or device:get_model() == "WISZB-137") end } diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua new file mode 100644 index 0000000000..2e704a61ca --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua @@ -0,0 +1,510 @@ + +-- 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 capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local IasEnrollResponseCode = require "st.zigbee.generated.zcl_clusters.IASZone.types.EnrollResponseCode" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" + +local IASZone = clusters.IASZone +local PowerConfiguration = clusters.PowerConfiguration +local TemperatureMeasurement = clusters.TemperatureMeasurement +local POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT = 0x2D +local TEMPERATURE_ENDPOINT = 0x26 + +local base64 = require "base64" +local mock_device = test.mock_device.build_test_zigbee_device( + { profile = t_utils.get_profile_definition("acceleration-motion-temperature-battery.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "frient A/S", + model = "WISZB-137", + server_clusters = { 0x0003, 0x0005, 0x0006 } + }, + [0x2D] = { + id = 0x2D, + server_clusters = { 0x0000, 0x0001, 0x0003, 0x0020, 0x0500, 0xFC04 } + }, + [0x26] = { + id = 0x26, + server_clusters = { 0x0402 } + } + } + } +) + +local mock_device_contact = test.mock_device.build_test_zigbee_device( + { profile = t_utils.get_profile_definition("acceleration-motion-temperature-contact-battery.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "frient A/S", + model = "WISZB-137", + server_clusters = { 0x0003, 0x0005, 0x0006 } + }, + [0x2D] = { + id = 0x2D, + server_clusters = { 0x0000, 0x0001, 0x0003, 0x0020, 0x0500, 0xFC04 } + }, + [0x26] = { + id = 0x26, + server_clusters = { 0x0402 } + } + } + } +) + +local Frient_AccelerationMeasurementCluster = { + ID = 0xFC04, + ManufacturerSpecificCode = 0x1015, + attributes = { + MeasuredValueX = { ID = 0x0000, data_type = data_types.name_to_id_map["Int16"] }, + MeasuredValueY = { ID = 0x0001, data_type = data_types.name_to_id_map["Int16"] }, + MeasuredValueZ = { ID = 0x0002, data_type = data_types.name_to_id_map["Int16"] } + }, +} + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_device) + test.mock_device.add_test_device(mock_device_contact) +end + +test.set_test_init_function(test_init) + +local function custom_configure_reporting(device, cluster, attribute, data_type, min_interval, max_interval, reportable_change, mfg_code) + local message = cluster_base.configure_reporting(device, + data_types.ClusterId(cluster), + data_types.AttributeId(attribute), + data_type, + min_interval, + max_interval, + reportable_change) + + -- Set the manufacturer-specific bit and add the manufacturer code + message.body.zcl_header.frame_ctrl:set_mfg_specific() + message.body.zcl_header.mfg_code = data_types.validate_or_build_type(mfg_code, data_types.Uint16, "mfg_code") + + return message +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_contact.id, "init" }) + + test.wait_for_events() + + --test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_contact.id, "doConfigure" }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + zigbee_test_utils.build_bind_request( + mock_device_contact, + zigbee_test_utils.mock_hub_eui, + PowerConfiguration.ID, + POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + PowerConfiguration.attributes.BatteryVoltage:configure_reporting( + mock_device_contact, + 30, + 21600, + 1 + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + zigbee_test_utils.build_bind_request( + mock_device_contact, + zigbee_test_utils.mock_hub_eui, + IASZone.ID, + POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + zigbee_test_utils.build_bind_request( + mock_device_contact, + zigbee_test_utils.mock_hub_eui, + TemperatureMeasurement.ID, + TEMPERATURE_ENDPOINT + ):to_endpoint(TEMPERATURE_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + IASZone.attributes.ZoneStatus:configure_reporting( + mock_device_contact, + 0x001E, + 0x012C, + 1 + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting( + mock_device_contact, + 30, + 600, + 100 + ):to_endpoint(TEMPERATURE_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + IASZone.attributes.IASCIEAddress:write( + mock_device_contact, + zigbee_test_utils.mock_hub_eui + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + zigbee_test_utils.build_bind_request( + mock_device_contact, + zigbee_test_utils.mock_hub_eui, + Frient_AccelerationMeasurementCluster.ID, + POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + custom_configure_reporting( + mock_device_contact, + Frient_AccelerationMeasurementCluster.ID, + Frient_AccelerationMeasurementCluster.attributes.MeasuredValueY.ID, + Frient_AccelerationMeasurementCluster.attributes.MeasuredValueY.data_type, + 0x0000, + 0x012C, + 0x0001, + Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + custom_configure_reporting( + mock_device_contact, + Frient_AccelerationMeasurementCluster.ID, + Frient_AccelerationMeasurementCluster.attributes.MeasuredValueX.ID, + Frient_AccelerationMeasurementCluster.attributes.MeasuredValueX.data_type, + 0x0000, + 0x012C, + 0x0001, + Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + custom_configure_reporting( + mock_device_contact, + Frient_AccelerationMeasurementCluster.ID, + Frient_AccelerationMeasurementCluster.attributes.MeasuredValueZ.ID, + Frient_AccelerationMeasurementCluster.attributes.MeasuredValueZ.data_type, + 0x0000, + 0x012C, + 0x0001, + Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + IASZone.server.commands.ZoneEnrollResponse( + mock_device_contact, + IasEnrollResponseCode.SUCCESS, + 0x00 + ) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + zigbee_test_utils.build_bind_request( + mock_device_contact, + zigbee_test_utils.mock_hub_eui, + IASZone.ID, + POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT + ) + }) + + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + IASZone.attributes.CurrentZoneSensitivityLevel:write( + mock_device_contact, + 0x000A + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting( + mock_device_contact, + 0x001E, + 0x0E10, + 100 + ):to_endpoint(TEMPERATURE_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + IASZone.attributes.ZoneStatus:configure_reporting( + mock_device_contact, + 0, + 3600, + 0 + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device_contact.id, + zigbee_test_utils.build_bind_request( + mock_device_contact, + zigbee_test_utils.mock_hub_eui, + Frient_AccelerationMeasurementCluster.ID, + POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT + ) + }) + + mock_device_contact:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_message_test( + "Temperature report should be handled (C) for the temperature measurement cluster", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:build_test_attr_report(mock_device, 2300)} + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 23.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( + "Battery min 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( + "Battery max 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( +"Refresh necessary attributes", +function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) }) + test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) }) + test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(TEMPERATURE_ENDPOINT) }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_manufacturer_specific_attribute( + mock_device, + Frient_AccelerationMeasurementCluster.ID, + Frient_AccelerationMeasurementCluster.attributes.MeasuredValueX.ID, + Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_manufacturer_specific_attribute( + mock_device, + Frient_AccelerationMeasurementCluster.ID, + Frient_AccelerationMeasurementCluster.attributes.MeasuredValueY.ID, + Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_manufacturer_specific_attribute( + mock_device, + Frient_AccelerationMeasurementCluster.ID, + Frient_AccelerationMeasurementCluster.attributes.MeasuredValueZ.ID, + Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode + ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) + }) +end +) + +test.register_message_test( + "Reported ZoneStatus change should be handled: active motion and inactive acceleration", + { + { + channel = "zigbee", + direction = "receive", + message = {mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0001)} + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active(mock_device)) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive(mock_device)) + } + } +) + +test.register_message_test( + "Reported ZoneStatus change should be handled: inactive motion and active acceleration", + { + { + channel = "zigbee", + direction = "receive", + message = {mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0002)} + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive(mock_device)) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active(mock_device)) + } + } +) + +test.register_coroutine_test( + "Three Axis report should be correctly handled", + function() + local attr_report_data = { + { 0x0000, data_types.Int16.ID, 300}, + { 0x0001, data_types.Int16.ID, 200}, + { 0x0002, data_types.Int16.ID, 100}, + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, Frient_AccelerationMeasurementCluster.ID, attr_report_data, 0x1015) + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({300, 200, 100})) + ) + end +) + +test.register_coroutine_test( + "Contact sensor open events should be correctly handled when preference is set", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + local attr_report_data = { + { 0x0000, data_types.Int16.ID, 300}, + { 0x0001, data_types.Int16.ID, 200}, + { 0x0002, data_types.Int16.ID, -902}, + } + test.socket.zigbee:__queue_receive({ + mock_device_contact.id, + zigbee_test_utils.build_attribute_report(mock_device_contact, Frient_AccelerationMeasurementCluster.ID, attr_report_data, 0x1015) + }) + + test.socket.capability:__expect_send( + mock_device_contact:generate_test_message("main", capabilities.threeAxis.threeAxis({300, 200, -902})) + ) + + test.socket.capability:__expect_send( + mock_device_contact:generate_test_message("main", capabilities.contactSensor.contact.open()) + ) + end +) + +test.register_coroutine_test( + "Contact sensor close events should be correctly handled when preference is set", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + local attr_report_data = { + { 0x0000, data_types.Int16.ID, 300}, + { 0x0001, data_types.Int16.ID, 200}, + { 0x0002, data_types.Int16.ID, 100}, + } + test.socket.zigbee:__queue_receive({ + mock_device_contact.id, + zigbee_test_utils.build_attribute_report(mock_device_contact, Frient_AccelerationMeasurementCluster.ID, attr_report_data, 0x1015) + }) + + test.socket.capability:__expect_send( + mock_device_contact:generate_test_message("main", capabilities.threeAxis.threeAxis({300, 200, 100})) + ) + + test.socket.capability:__expect_send( + mock_device_contact:generate_test_message("main", capabilities.contactSensor.contact.closed()) + ) + end +) + +test.run_registered_tests() \ No newline at end of file From 828187bb9bf68984ace6ee1d25bcc39b4557dfc5 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 5 Feb 2026 11:02:54 -0800 Subject: [PATCH 088/538] WWSTCERT-10019 WINDOWSTORY GATEWAY-MT --- drivers/SmartThings/matter-window-covering/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-window-covering/fingerprints.yml b/drivers/SmartThings/matter-window-covering/fingerprints.yml index 0de1e91af8..531dc8c36f 100644 --- a/drivers/SmartThings/matter-window-covering/fingerprints.yml +++ b/drivers/SmartThings/matter-window-covering/fingerprints.yml @@ -107,6 +107,12 @@ matterManufacturer: vendorId: 0x139C productId: 0xFA17 deviceProfileName: window-covering +#WINDOWSTORY + - id: "5496/6657" + deviceLabel: GATEWAY-MT + vendorId: 0x1578 + productId: 0x1A01 + deviceProfileName: window-covering-tilt #WISTAR - id: "5207/3" deviceLabel: WISTAR WSERD16-B Smart Tubular Motor From 454b00ff016c9427beadb5ea6d733b27a69677ba Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 15 Dec 2025 15:27:02 -0600 Subject: [PATCH 089/538] CHAD-17156: Lazy loading of matter-lock sub-drivers --- .../matter-lock/src/DoorLock/init.lua | 6 +- .../matter-lock/src/DoorLock/types/init.lua | 5 +- drivers/SmartThings/matter-lock/src/init.lua | 20 ++---- .../matter-lock/src/lazy_load_subdriver.lua | 14 +++++ .../matter-lock/src/lock_utils.lua | 15 +---- .../src/new-matter-lock/can_handle.lua | 19 ++++++ .../src/new-matter-lock/fingerprints.lua | 35 +++++++++++ .../matter-lock/src/new-matter-lock/init.lua | 63 ++----------------- .../matter-lock/src/sub_drivers.lua | 8 +++ .../src/test/test_aqara_matter_lock.lua | 16 +---- .../src/test/test_bridged_matter_lock.lua | 16 +---- .../matter-lock/src/test/test_matter_lock.lua | 16 +---- .../src/test/test_matter_lock_battery.lua | 16 +---- .../test/test_matter_lock_batteryLevel.lua | 16 +---- .../src/test/test_matter_lock_codes.lua | 16 +---- .../src/test/test_matter_lock_cota.lua | 16 +---- .../src/test/test_matter_lock_modular.lua | 16 +---- .../src/test/test_matter_lock_unlatch.lua | 16 +---- .../src/test/test_new_matter_lock.lua | 16 +---- .../src/test/test_new_matter_lock_battery.lua | 16 +---- 20 files changed, 129 insertions(+), 232 deletions(-) create mode 100644 drivers/SmartThings/matter-lock/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/matter-lock/src/new-matter-lock/can_handle.lua create mode 100644 drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua create mode 100644 drivers/SmartThings/matter-lock/src/sub_drivers.lua diff --git a/drivers/SmartThings/matter-lock/src/DoorLock/init.lua b/drivers/SmartThings/matter-lock/src/DoorLock/init.lua index b9675ebf77..20cf12d0ea 100644 --- a/drivers/SmartThings/matter-lock/src/DoorLock/init.lua +++ b/drivers/SmartThings/matter-lock/src/DoorLock/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 DoorLockServerAttributes = require "DoorLock.server.attributes" local DoorLockServerCommands = require "DoorLock.server.commands" @@ -255,4 +259,4 @@ setmetatable(DoorLock.events, event_helper_mt) setmetatable(DoorLock, {__index = cluster_base}) -return DoorLock \ No newline at end of file +return DoorLock diff --git a/drivers/SmartThings/matter-lock/src/DoorLock/types/init.lua b/drivers/SmartThings/matter-lock/src/DoorLock/types/init.lua index 461d914f84..f4b2939d61 100644 --- a/drivers/SmartThings/matter-lock/src/DoorLock/types/init.lua +++ b/drivers/SmartThings/matter-lock/src/DoorLock/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) @@ -11,4 +14,4 @@ local DoorLockTypes = {} setmetatable(DoorLockTypes, types_mt) -return DoorLockTypes \ No newline at end of file +return DoorLockTypes diff --git a/drivers/SmartThings/matter-lock/src/init.lua b/drivers/SmartThings/matter-lock/src/init.lua index 6133b74e45..b3403863ec 100755 --- a/drivers/SmartThings/matter-lock/src/init.lua +++ b/drivers/SmartThings/matter-lock/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 MatterDriver = require "st.matter.driver" local clusters = require "st.matter.clusters" @@ -717,9 +707,7 @@ local matter_lock_driver = { capabilities.battery, capabilities.batteryLevel, }, - sub_drivers = { - require("new-matter-lock"), - }, + sub_drivers = require("sub_drivers"), lifecycle_handlers = { init = device_init, added = device_added, diff --git a/drivers/SmartThings/matter-lock/src/lazy_load_subdriver.lua b/drivers/SmartThings/matter-lock/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..a04740d267 --- /dev/null +++ b/drivers/SmartThings/matter-lock/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-lock/src/lock_utils.lua b/drivers/SmartThings/matter-lock/src/lock_utils.lua index 816ca446f2..94e95c196f 100644 --- a/drivers/SmartThings/matter-lock/src/lock_utils.lua +++ b/drivers/SmartThings/matter-lock/src/lock_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 lock_utils = { -- Lock device field names diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/can_handle.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/can_handle.lua new file mode 100644 index 0000000000..7bbb45ed44 --- /dev/null +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/can_handle.lua @@ -0,0 +1,19 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_new_matter_lock_products(opts, driver, device) + local device_lib = require "st.device" + if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then + return false + end + local FINGERPRINTS = require("new-matter-lock.fingerprints") + for _, p in ipairs(FINGERPRINTS) do + if device.manufacturer_info.vendor_id == p[1] and + device.manufacturer_info.product_id == p[2] then + return true, require("new-matter-lock") + end + end + return false +end + +return is_new_matter_lock_products diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua new file mode 100644 index 0000000000..bddc7b1fd1 --- /dev/null +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -0,0 +1,35 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +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 + {0x101D, 0x8110}, -- Yale, New Lock + {0x1533, 0x0001}, -- eufy, E31 + {0x1533, 0x0002}, -- eufy, E30 + {0x1533, 0x0003}, -- eufy, C34 + {0x1533, 0x000F}, -- eufy, FamiLock S3 Max + {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 + {0x135D, 0x00A1}, -- Nuki, Smart Lock + {0x135D, 0x00B0}, -- Nuki, Smart Lock + {0x15F2, 0x0001}, -- Viomi, AiSafety Smart Lock E100 + {0x158B, 0x0001}, -- Deasino, DS-MT01 + {0x10E1, 0x2002}, -- VDA + {0x1421, 0x0042}, -- Kwikset Halo Select Plus + {0x1421, 0x0081}, -- Kwikset Aura Reach +} + +return NEW_MATTER_LOCK_PRODUCTS 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 e47a883143..11aa2b0884 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -1,18 +1,7 @@ --- 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 device_lib = require "st.device" +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local im = require "st.matter.interaction_model" @@ -60,36 +49,6 @@ local ALIRO_KEY_TYPE_TO_CRED_ENUM_MAP = { ["nonEvictableEndpointKey"] = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY } -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 - {0x101D, 0x8110}, -- Yale, New Lock - {0x1533, 0x0001}, -- eufy, E31 - {0x1533, 0x0002}, -- eufy, E30 - {0x1533, 0x0003}, -- eufy, C34 - {0x1533, 0x000F}, -- eufy, FamiLock S3 Max - {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 - {0x135D, 0x00A1}, -- Nuki, Smart Lock - {0x135D, 0x00B0}, -- Nuki, Smart Lock - {0x15F2, 0x0001}, -- Viomi, AiSafety Smart Lock E100 - {0x158B, 0x0001}, -- Deasino, DS-MT01 - {0x10E1, 0x2002}, -- VDA - {0x1421, 0x0042}, -- Kwikset Halo Select Plus - {0x1421, 0x0081}, -- Kwikset Aura Reach -} local battery_support = { NO_BATTERY = "NO_BATTERY", @@ -152,18 +111,6 @@ local subscribed_events = { } } -local function is_new_matter_lock_products(opts, driver, device) - if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then - return false - end - for _, p in ipairs(NEW_MATTER_LOCK_PRODUCTS) do - if device.manufacturer_info.vendor_id == p[1] and - device.manufacturer_info.product_id == p[2] then - return true - end - end - return false -end local function find_default_endpoint(device, cluster) local res = device.MATTER_DEFAULT_ENDPOINT @@ -2971,7 +2918,7 @@ local new_matter_lock_handler = { capabilities.battery, capabilities.batteryLevel }, - can_handle = is_new_matter_lock_products + can_handle = require("new-matter-lock.can_handle"), } return new_matter_lock_handler diff --git a/drivers/SmartThings/matter-lock/src/sub_drivers.lua b/drivers/SmartThings/matter-lock/src/sub_drivers.lua new file mode 100644 index 0000000000..0f5d1f76d2 --- /dev/null +++ b/drivers/SmartThings/matter-lock/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("new-matter-lock"), +} +return sub_drivers diff --git a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua index 5c4add0ff5..c5da1b600e 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.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" test.set_rpc_version(0) diff --git a/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua index 0d17cafda6..eb8ec9fa98 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.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 + local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua index 0db34172d5..8fd0cc0574 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.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/matter-lock/src/test/test_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua index 8e0be6359d..a8f7506798 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.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 + local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua index c53547025c..990bff8b60 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.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 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua index 2960d905c2..a3e05c9634 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.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/matter-lock/src/test/test_matter_lock_cota.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua index 57468ef2fc..c41d911881 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.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/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index d6382faa06..c4c226fbbe 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.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-lock/src/test/test_matter_lock_unlatch.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua index 38154d7b04..642ca3bf7a 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.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" test.set_rpc_version(0) diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 7d5a59ff32..738248fd8e 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.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" test.set_rpc_version(0) diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua index 6926f1d62d..03a51a3150 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.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" test.set_rpc_version(0) From 05bef658db425512d6029b6a921d05ef3406163f Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:33 -0600 Subject: [PATCH 090/538] 17074: zigbee-siren lazy load subdrivers --- .../zigbee-siren/src/frient/can_handle.lua | 15 +++++++++++++ .../zigbee-siren/src/frient/init.lua | 21 +++++-------------- drivers/SmartThings/zigbee-siren/src/init.lua | 18 ++++------------ .../zigbee-siren/src/lazy_load_subdriver.lua | 15 +++++++++++++ .../zigbee-siren/src/ozom/can_handle.lua | 11 ++++++++++ .../zigbee-siren/src/ozom/init.lua | 20 ++++-------------- .../zigbee-siren/src/sub_drivers.lua | 9 ++++++++ .../src/test/test_frient_siren.lua | 2 +- .../zigbee-siren/src/test/test_ozom_siren.lua | 2 +- 9 files changed, 65 insertions(+), 48 deletions(-) create mode 100644 drivers/SmartThings/zigbee-siren/src/frient/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-siren/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-siren/src/ozom/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-siren/src/sub_drivers.lua diff --git a/drivers/SmartThings/zigbee-siren/src/frient/can_handle.lua b/drivers/SmartThings/zigbee-siren/src/frient/can_handle.lua new file mode 100644 index 0000000000..ed6acf5b42 --- /dev/null +++ b/drivers/SmartThings/zigbee-siren/src/frient/can_handle.lua @@ -0,0 +1,15 @@ +-- 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() == "SIRZB-110" or + device:get_model() == "SIRZB-111" or + device:get_model() == "SIRZB-112") + then + return true, require("frient") + end + return false +end + +return frient_can_handle diff --git a/drivers/SmartThings/zigbee-siren/src/frient/init.lua b/drivers/SmartThings/zigbee-siren/src/frient/init.lua index 78738ca9a8..d408579696 100644 --- a/drivers/SmartThings/zigbee-siren/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-siren/src/frient/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 data_types = require "st.zigbee.data_types" local battery_defaults = require "st.zigbee.defaults.battery_defaults" --ZCL @@ -419,9 +410,7 @@ local frient_siren_driver = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "frient A/S" and (device:get_model() == "SIRZB-110" or device:get_model() == "SIRZB-111" or device:get_model() == "SIRZB-112") - end + can_handle = require("frient.can_handle"), } return frient_siren_driver \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-siren/src/init.lua b/drivers/SmartThings/zigbee-siren/src/init.lua index 21b1865b07..b1ad81c9c6 100644 --- a/drivers/SmartThings/zigbee-siren/src/init.lua +++ b/drivers/SmartThings/zigbee-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 ZigbeeDriver = require "st.zigbee" local defaults = require "st.zigbee.defaults" @@ -194,7 +184,7 @@ local zigbee_siren_driver_template = { added = device_added, doConfigure = do_configure }, - sub_drivers = { require("ozom"), require("frient") }, + sub_drivers = require("sub_drivers"), cluster_configurations = { [alarm.ID] = { { diff --git a/drivers/SmartThings/zigbee-siren/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-siren/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-siren/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-siren/src/ozom/can_handle.lua b/drivers/SmartThings/zigbee-siren/src/ozom/can_handle.lua new file mode 100644 index 0000000000..bec6069a7f --- /dev/null +++ b/drivers/SmartThings/zigbee-siren/src/ozom/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function ozom_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "ClimaxTechnology" then + return true, require("ozom") + end + return false +end + +return ozom_can_handle diff --git a/drivers/SmartThings/zigbee-siren/src/ozom/init.lua b/drivers/SmartThings/zigbee-siren/src/ozom/init.lua index e4cecc28ab..c17a1c4fb7 100644 --- a/drivers/SmartThings/zigbee-siren/src/ozom/init.lua +++ b/drivers/SmartThings/zigbee-siren/src/ozom/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 data_types = require "st.zigbee.data_types" --ZCL @@ -75,9 +65,7 @@ local ozom_siren_driver = { [switch.commands.on.NAME] = siren_switch_on_handler } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "ClimaxTechnology" - end + can_handle = require("ozom.can_handle"), } return ozom_siren_driver diff --git a/drivers/SmartThings/zigbee-siren/src/sub_drivers.lua b/drivers/SmartThings/zigbee-siren/src/sub_drivers.lua new file mode 100644 index 0000000000..05d186b87e --- /dev/null +++ b/drivers/SmartThings/zigbee-siren/src/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" + +return { + lazy_load_if_possible("frient"), + lazy_load_if_possible("ozom"), +} diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua index f8951d3b31..8140da64bd 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.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-siren/src/test/test_ozom_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_ozom_siren.lua index 5b9a6a2bea..f05c58468f 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_ozom_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_ozom_siren.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. From 5ab66d3d34b5a3f54ed963250f31d6147334870c Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:05 -0600 Subject: [PATCH 091/538] CHAD-17065: zigbee-contact lazy loading subdrivers --- .../zigbee-contact/src/aqara/can_handle.lua | 14 ++++++ .../zigbee-contact/src/aqara/fingerprints.lua | 8 +++ .../zigbee-contact/src/aqara/init.lua | 16 ++---- .../src/aurora-contact-sensor/can_handle.lua | 14 ++++++ .../aurora-contact-sensor/fingerprints.lua | 9 ++++ .../src/aurora-contact-sensor/init.lua | 30 ++--------- .../zigbee-contact/src/configurations.lua | 15 +----- .../contact-temperature-sensor/can_handle.lua | 14 ++++++ .../ecolink-contact/can_handle.lua | 14 ++++++ .../ecolink-contact/fingerprints.lua | 9 ++++ .../ecolink-contact/init.lua | 30 ++--------- .../fingerprints.lua | 24 +++++++++ .../src/contact-temperature-sensor/init.lua | 50 +++---------------- .../sub_drivers.lua | 8 +++ .../zigbee-contact/src/frient/can_handle.lua | 15 ++++++ .../frient/frient-vibration/can_handle.lua | 15 ++++++ .../src/frient/frient-vibration/init.lua | 17 +------ .../zigbee-contact/src/frient/init.lua | 24 ++------- .../zigbee-contact/src/frient/sub_drivers.lua | 8 +++ .../SmartThings/zigbee-contact/src/init.lua | 26 ++-------- .../src/lazy_load_subdriver.lua | 15 ++++++ .../src/multi-sensor/can_handle.lua | 14 ++++++ .../centralite-multi/can_handle.lua | 11 ++++ .../multi-sensor/centralite-multi/init.lua | 20 ++------ .../src/multi-sensor/fingerprints.lua | 13 +++++ .../zigbee-contact/src/multi-sensor/init.lua | 42 +++------------- .../src/multi-sensor/multi_utils.lua | 17 ++----- .../multi-sensor/samjin-multi/can_handle.lua | 11 ++++ .../src/multi-sensor/samjin-multi/init.lua | 20 ++------ .../smartthings-multi/can_handle.lua | 11 ++++ .../multi-sensor/smartthings-multi/init.lua | 20 ++------ .../src/multi-sensor/sub_drivers.lua | 11 ++++ .../thirdreality-multi/can_handle.lua | 11 ++++ .../multi-sensor/thirdreality-multi/init.lua | 20 ++------ .../zigbee-contact/src/sengled/can_handle.lua | 14 ++++++ .../src/sengled/fingerprints.lua | 8 +++ .../zigbee-contact/src/sengled/init.lua | 16 ++---- .../src/smartsense-multi/can_handle.lua | 17 +++++++ .../src/smartsense-multi/fingerprints.lua | 9 ++++ .../src/smartsense-multi/init.lua | 34 ++----------- .../zigbee-contact/src/sub_drivers.lua | 14 ++++++ .../src/test/test_aqara_contact_sensor.lua | 15 +----- .../src/test/test_aurora_contact_sensor.lua | 15 +----- .../src/test/test_centralite_multi_sensor.lua | 15 +----- .../test/test_contact_temperature_sensor.lua | 15 +----- .../src/test/test_ecolink_contact.lua | 15 +----- .../src/test/test_ewelink_heiman_sensor.lua | 15 +----- .../src/test/test_frient_contact_sensor.lua | 15 +----- .../test/test_frient_contact_sensor_2_pro.lua | 15 +----- .../test/test_frient_contact_sensor_pro.lua | 15 +----- .../src/test/test_orvibo_contact_sensor.lua | 15 +----- .../src/test/test_samjin_multi_sensor.lua | 15 +----- .../src/test/test_sengled_contact_sensor.lua | 15 +----- .../src/test/test_smartsense_multi.lua | 17 ++----- .../test/test_smartthings_multi_sensor.lua | 15 +----- .../src/test/test_third_reality_contact.lua | 15 +----- .../src/test/test_zigbee_contact.lua | 15 +----- .../src/test/test_zigbee_contact_battery.lua | 15 +----- .../src/test/test_zigbee_contact_tyco.lua | 15 +----- 59 files changed, 411 insertions(+), 569 deletions(-) create mode 100644 drivers/SmartThings/zigbee-contact/src/aqara/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/aqara/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/aurora-contact-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/aurora-contact-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/ecolink-contact/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/ecolink-contact/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/frient/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/frient/frient-vibration/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/frient/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/multi-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/multi-sensor/centralite-multi/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/multi-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/multi-sensor/samjin-multi/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/multi-sensor/smartthings-multi/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/multi-sensor/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/multi-sensor/thirdreality-multi/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/sengled/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/sengled/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/smartsense-multi/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/smartsense-multi/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/sub_drivers.lua diff --git a/drivers/SmartThings/zigbee-contact/src/aqara/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/aqara/can_handle.lua new file mode 100644 index 0000000000..7877f2c180 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/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-contact/src/aqara/fingerprints.lua b/drivers/SmartThings/zigbee-contact/src/aqara/fingerprints.lua new file mode 100644 index 0000000000..bcad0d779d --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/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.magnet.agl02" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-contact/src/aqara/init.lua b/drivers/SmartThings/zigbee-contact/src/aqara/init.lua index 477a62afd4..9093ee7bb9 100644 --- a/drivers/SmartThings/zigbee-contact/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-contact/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" @@ -13,9 +16,6 @@ local PRIVATE_CLUSTER_ID = 0xFCC0 local PRIVATE_ATTRIBUTE_ID = 0x0009 local PRIVATE_HEART_BATTERY_ENERGY_ID = 0x00F7 -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.magnet.agl02" } -} local CONFIGURATIONS = { { @@ -36,14 +36,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 device_init(driver, device) device:remove_configured_attribute(IASZone.ID, IASZone.attributes.ZoneStatus.ID) @@ -136,7 +128,7 @@ local aqara_contact_handler = { doConfigure = do_configure, added = added_handler, }, - can_handle = is_aqara_products + can_handle = require("aqara.can_handle"), } return aqara_contact_handler diff --git a/drivers/SmartThings/zigbee-contact/src/aurora-contact-sensor/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/aurora-contact-sensor/can_handle.lua new file mode 100644 index 0000000000..ceb3f4d933 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/aurora-contact-sensor/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_aurora_contact(opts, driver, device, ...) + local FINGERPRINTS = require("aurora-contact-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("aurora-contact-sensor") + end + end + return false +end + +return can_handle_aurora_contact diff --git a/drivers/SmartThings/zigbee-contact/src/aurora-contact-sensor/fingerprints.lua b/drivers/SmartThings/zigbee-contact/src/aurora-contact-sensor/fingerprints.lua new file mode 100644 index 0000000000..022bbc83d5 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/aurora-contact-sensor/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AURORA_CONTACT_FINGERPRINTS = { + { mfr = "Aurora", model = "DoorSensor50AU" }, -- Aurora Smart Door/Window Sensor + { mfr = "Aurora", model = "WindowSensor51AU" } -- Aurora Smart Door/Window Sensor +} + +return AURORA_CONTACT_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-contact/src/aurora-contact-sensor/init.lua b/drivers/SmartThings/zigbee-contact/src/aurora-contact-sensor/init.lua index efa6fb8c37..b5ac4fe8bb 100644 --- a/drivers/SmartThings/zigbee-contact/src/aurora-contact-sensor/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/aurora-contact-sensor/init.lua @@ -1,26 +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 clusters = require "st.zigbee.zcl.clusters" local battery_defaults = require "st.zigbee.defaults.battery_defaults" local IASZone = clusters.IASZone -local AURORA_CONTACT_FINGERPRINTS = { - { mfr = "Aurora", model = "DoorSensor50AU" }, -- Aurora Smart Door/Window Sensor - { mfr = "Aurora", model = "WindowSensor51AU" } -- Aurora Smart Door/Window Sensor -} local AURORA_CONTACT_CONFIGURATION = { { @@ -33,14 +19,6 @@ local AURORA_CONTACT_CONFIGURATION = { } } -local function can_handle_aurora_contact(opts, driver, device, ...) - for _, fingerprint in ipairs(AURORA_CONTACT_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.use_battery_voltage_handling(device) @@ -55,7 +33,7 @@ local aurora_contact = { lifecycle_handlers = { init = device_init }, - can_handle = can_handle_aurora_contact + can_handle = require("aurora-contact-sensor.can_handle"), } return aurora_contact diff --git a/drivers/SmartThings/zigbee-contact/src/configurations.lua b/drivers/SmartThings/zigbee-contact/src/configurations.lua index 1e08ecffd4..cd5ede1b6e 100644 --- a/drivers/SmartThings/zigbee-contact/src/configurations.lua +++ b/drivers/SmartThings/zigbee-contact/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 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/can_handle.lua new file mode 100644 index 0000000000..2c9f359dbe --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_contact_temperature_sensor(opts, driver, device, ...) + local FINGERPRINTS = require("contact-temperature-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("contact-temperature-sensor") + end + end + return false +end + +return can_handle_contact_temperature_sensor diff --git a/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/ecolink-contact/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/ecolink-contact/can_handle.lua new file mode 100644 index 0000000000..b17c1efb21 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/ecolink-contact/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_ecolink_sensor(opts, driver, device, ...) + local FINGERPRINTS = require("contact-temperature-sensor.ecolink-contact.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("contact-temperature-sensor.ecolink-contact") + end + end + return false +end + +return can_handle_ecolink_sensor diff --git a/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/ecolink-contact/fingerprints.lua b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/ecolink-contact/fingerprints.lua new file mode 100644 index 0000000000..c86e6e073f --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/ecolink-contact/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ECOLINK_CONTACT_TEMPERATURE_FINGERPRINTS = { + { mfr = "Ecolink", model = "4655BC0-R" }, + { mfr = "Ecolink", model = "DWZB1-ECO" } +} + +return ECOLINK_CONTACT_TEMPERATURE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/ecolink-contact/init.lua b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/ecolink-contact/init.lua index 22999c59aa..31dc67fd3e 100644 --- a/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/ecolink-contact/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/ecolink-contact/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 device_management = require "st.zigbee.device_management" @@ -22,19 +12,7 @@ local SHORT_POLL_INTERVAL = 0x0200 local LONG_POLL_INTERVAL = 0xB1040000 local FAST_POLL_TIMEOUT = 0x0028 -local ECOLINK_CONTACT_TEMPERATURE_FINGERPRINTS = { - { mfr = "Ecolink", model = "4655BC0-R" }, - { mfr = "Ecolink", model = "DWZB1-ECO" } -} -local function can_handle_ecolink_sensor(opts, driver, device, ...) - for _, fingerprint in ipairs(ECOLINK_CONTACT_TEMPERATURE_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() @@ -51,7 +29,7 @@ local ecolink_sensor = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_ecolink_sensor + can_handle = require("contact-temperature-sensor.ecolink-contact.can_handle"), } return ecolink_sensor diff --git a/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/fingerprints.lua b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/fingerprints.lua new file mode 100644 index 0000000000..4efc09684b --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/fingerprints.lua @@ -0,0 +1,24 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local CONTACT_TEMPERATURE_SENSOR_FINGERPRINTS = { + { mfr = "CentraLite", model = "3300-S" }, + { mfr = "CentraLite", model = "3300" }, + { mfr = "CentraLite", model = "3320-L" }, + { mfr = "CentraLite", model = "3323-G" }, + { mfr = "CentraLite", model = "Contact Sensor-A" }, + { mfr = "Visonic", model = "MCT-340 E" }, + { mfr = "Visonic", model = "MCT-340 SMA" }, + { mfr = "Ecolink", model = "4655BC0-R" }, + { mfr = "Ecolink", model = "DWZB1-ECO" }, + { mfr = "iMagic by GreatStar", model = "1116-S" }, + { mfr = "Bosch", model = "RFMS-ZBMS" }, + { mfr = "Megaman", model = "MS601/z1" }, + { mfr = "AduroSmart Eria", model = "CSW_ADUROLIGHT" }, + { mfr = "ADUROLIGHT", model = "CSW_ADUROLIGHT" }, + { mfr = "Sercomm Corp.", model = "SZ-DWS04" }, + { mfr = "DAWON_DNS", model = "SS-B100-ZB" }, + { mfr = "Compacta", model = "ZBWDS" } +} + +return CONTACT_TEMPERATURE_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/init.lua b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/init.lua index 33b2e3daa3..76f822fedd 100644 --- a/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/init.lua @@ -1,48 +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 battery_defaults = require "st.zigbee.defaults.battery_defaults" local configurationMap = require "configurations" -local CONTACT_TEMPERATURE_SENSOR_FINGERPRINTS = { - { mfr = "CentraLite", model = "3300-S" }, - { mfr = "CentraLite", model = "3300" }, - { mfr = "CentraLite", model = "3320-L" }, - { mfr = "CentraLite", model = "3323-G" }, - { mfr = "CentraLite", model = "Contact Sensor-A" }, - { mfr = "Visonic", model = "MCT-340 E" }, - { mfr = "Visonic", model = "MCT-340 SMA" }, - { mfr = "Ecolink", model = "4655BC0-R" }, - { mfr = "Ecolink", model = "DWZB1-ECO" }, - { mfr = "iMagic by GreatStar", model = "1116-S" }, - { mfr = "Bosch", model = "RFMS-ZBMS" }, - { mfr = "Megaman", model = "MS601/z1" }, - { mfr = "AduroSmart Eria", model = "CSW_ADUROLIGHT" }, - { mfr = "ADUROLIGHT", model = "CSW_ADUROLIGHT" }, - { mfr = "Sercomm Corp.", model = "SZ-DWS04" }, - { mfr = "DAWON_DNS", model = "SS-B100-ZB" }, - { mfr = "Compacta", model = "ZBWDS" } -} -local function can_handle_contact_temperature_sensor(opts, driver, device, ...) - for _, fingerprint in ipairs(CONTACT_TEMPERATURE_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 configuration = configurationMap.get_device_configuration(device) @@ -61,10 +25,8 @@ local contact_temperature_sensor = { lifecycle_handlers = { init = device_init }, - sub_drivers = { - require("contact-temperature-sensor/ecolink-contact") - }, - can_handle = can_handle_contact_temperature_sensor + sub_drivers = require("contact-temperature-sensor.sub_drivers"), + can_handle = require("contact-temperature-sensor.can_handle"), } return contact_temperature_sensor diff --git a/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/sub_drivers.lua b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/sub_drivers.lua new file mode 100644 index 0000000000..d47c4ad123 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/contact-temperature-sensor/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("contact-temperature-sensor.ecolink-contact"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-contact/src/frient/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/frient/can_handle.lua new file mode 100644 index 0000000000..e2d56c3dc0 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/frient/can_handle.lua @@ -0,0 +1,15 @@ +-- 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() == "WISZB-120" or + device:get_model() == "WISZB-121" or + device:get_model() == "WISZB-131" or + device:get_model() == "WISZB-137") then + return true, require("frient") + end + return false +end + +return frient_can_handle diff --git a/drivers/SmartThings/zigbee-contact/src/frient/frient-vibration/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/frient/frient-vibration/can_handle.lua new file mode 100644 index 0000000000..14ecae440a --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/frient/frient-vibration/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FRIENT_DEVICE_FINGERPRINTS = { + { mfr = "frient A/S", model = "WISZB-137", }, +} + +return function(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, require("frient.frient-vibration") + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-contact/src/frient/frient-vibration/init.lua b/drivers/SmartThings/zigbee-contact/src/frient/frient-vibration/init.lua index 2514bfb63b..bba3dc0bdf 100644 --- a/drivers/SmartThings/zigbee-contact/src/frient/frient-vibration/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/frient/frient-vibration/init.lua @@ -27,19 +27,6 @@ local PowerConfiguration = zcl_clusters.PowerConfiguration local POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT = 0x2D local TEMPERATURE_ENDPOINT = 0x26 -local FRIENT_DEVICE_FINGERPRINTS = { - { mfr = "frient A/S", model = "WISZB-137", }, -} - -local function can_handle_vibration_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 Frient_AccelerationMeasurementCluster = { ID = 0xFC04, ManufacturerSpecificCode = 0x1015, @@ -219,7 +206,7 @@ local frient_vibration_driver_template = { [capabilities.refresh.commands.refresh.NAME] = do_refresh, } }, - can_handle = can_handle_vibration_sensor + can_handle = require ("frient.frient-vibration.can_handle") } -return frient_vibration_driver_template \ No newline at end of file +return frient_vibration_driver_template diff --git a/drivers/SmartThings/zigbee-contact/src/frient/init.lua b/drivers/SmartThings/zigbee-contact/src/frient/init.lua index 1379d0a8c0..f28cad8cdb 100644 --- a/drivers/SmartThings/zigbee-contact/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/frient/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 clusters = require "st.zigbee.zcl.clusters" local battery_defaults = require "st.zigbee.defaults.battery_defaults" @@ -80,9 +70,7 @@ local frient_sensor = { doConfigure = do_configure, infoChanged = info_changed }, - sub_drivers = { - require("frient/frient-vibration"), - }, + sub_drivers = require("frient.sub_drivers"), zigbee_handlers = { cluster = { [IASZone.ID] = { @@ -95,9 +83,7 @@ local frient_sensor = { } } }, - can_handle = function(opts, driver, device, ...) - return (device:get_manufacturer() == "frient A/S") and (device:get_model() == "WISZB-120" or device:get_model() == "WISZB-121" or device:get_model() == "WISZB-131" or device:get_model() == "WISZB-137") - end + can_handle = require("frient.can_handle"), } return frient_sensor diff --git a/drivers/SmartThings/zigbee-contact/src/frient/sub_drivers.lua b/drivers/SmartThings/zigbee-contact/src/frient/sub_drivers.lua new file mode 100644 index 0000000000..cfaca8fca3 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/frient/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("frient.frient-vibration"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-contact/src/init.lua b/drivers/SmartThings/zigbee-contact/src/init.lua index 67be42e671..63a7bf7565 100644 --- a/drivers/SmartThings/zigbee-contact/src/init.lua +++ b/drivers/SmartThings/zigbee-contact/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" @@ -97,15 +87,7 @@ local zigbee_contact_driver_template = { init = device_init, added = added_handler, }, - sub_drivers = { - require("aqara"), - require("aurora-contact-sensor"), - require("contact-temperature-sensor"), - require("multi-sensor"), - require("smartsense-multi"), - require("sengled"), - require("frient") - }, + sub_drivers = require("sub_drivers"), ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, health_check = false, } diff --git a/drivers/SmartThings/zigbee-contact/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-contact/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/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-contact/src/multi-sensor/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/can_handle.lua new file mode 100644 index 0000000000..0065632f88 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_zigbee_multi_sensor(opts, driver, device, ...) + local FINGERPRINTS = require("multi-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("multi-sensor") + end + end + return false +end + +return can_handle_zigbee_multi_sensor diff --git a/drivers/SmartThings/zigbee-contact/src/multi-sensor/centralite-multi/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/centralite-multi/can_handle.lua new file mode 100644 index 0000000000..94e73b1691 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/centralite-multi/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function centralite_multi_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "CentraLite" then + return true, require("multi-sensor.centralite-multi") + end + return false +end + +return centralite_multi_can_handle diff --git a/drivers/SmartThings/zigbee-contact/src/multi-sensor/centralite-multi/init.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/centralite-multi/init.lua index 1e75aa6b0d..0f7b7b7f25 100644 --- a/drivers/SmartThings/zigbee-contact/src/multi-sensor/centralite-multi/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/centralite-multi/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 multi_utils = require "multi-sensor/multi_utils" @@ -67,9 +57,7 @@ local centralite_handler = { [capabilities.refresh.commands.refresh.NAME] = do_refresh, } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "CentraLite" - end + can_handle = require("multi-sensor.centralite-multi.can_handle"), } return centralite_handler diff --git a/drivers/SmartThings/zigbee-contact/src/multi-sensor/fingerprints.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/fingerprints.lua new file mode 100644 index 0000000000..7abfe772b5 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/fingerprints.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local MULTI_SENSOR_FINGERPRINTS = { + { mfr = "CentraLite", model = "3320" }, + { mfr = "CentraLite", model = "3321" }, + { mfr = "CentraLite", model = "3321-S" }, + { mfr = "SmartThings", model = "multiv4" }, + { mfr = "Samjin", model = "multi" }, + { mfr = "Third Reality, Inc", model = "3RVS01031Z" } +} + +return MULTI_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-contact/src/multi-sensor/init.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/init.lua index a440156c11..4cc8f88efa 100644 --- a/drivers/SmartThings/zigbee-contact/src/multi-sensor/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/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_commands = require "st.zigbee.zcl.global_commands" local multi_utils = require "multi-sensor/multi_utils" @@ -18,23 +9,7 @@ local zcl_clusters = require "st.zigbee.zcl.clusters" local contactSensor_defaults = require "st.zigbee.defaults.contactSensor_defaults" local capabilities = require "st.capabilities" -local MULTI_SENSOR_FINGERPRINTS = { - { mfr = "CentraLite", model = "3320" }, - { mfr = "CentraLite", model = "3321" }, - { mfr = "CentraLite", model = "3321-S" }, - { mfr = "SmartThings", model = "multiv4" }, - { mfr = "Samjin", model = "multi" }, - { mfr = "Third Reality, Inc", model = "3RVS01031Z" } -} -local function can_handle_zigbee_multi_sensor(opts, driver, device, ...) - for _, fingerprint in ipairs(MULTI_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 multi_sensor_report_handler(driver, device, zb_rx) local x, y, z @@ -98,12 +73,7 @@ local multi_sensor = { } } }, - sub_drivers = { - require("multi-sensor/smartthings-multi"), - require("multi-sensor/samjin-multi"), - require("multi-sensor/centralite-multi"), - require("multi-sensor/thirdreality-multi") - }, - can_handle = can_handle_zigbee_multi_sensor + sub_drivers = require("multi-sensor.sub_drivers"), + can_handle = require("multi-sensor.can_handle"), } return multi_sensor diff --git a/drivers/SmartThings/zigbee-contact/src/multi-sensor/multi_utils.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/multi_utils.lua index e24ec1097b..b1b3e18be4 100644 --- a/drivers/SmartThings/zigbee-contact/src/multi-sensor/multi_utils.lua +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/multi_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 capabilities = require "st.capabilities" local cluster_base = require "st.zigbee.cluster_base" @@ -152,4 +141,4 @@ multi_utils.convert_to_signedInt16 = function(byte1, byte2) end -return multi_utils \ No newline at end of file +return multi_utils diff --git a/drivers/SmartThings/zigbee-contact/src/multi-sensor/samjin-multi/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/samjin-multi/can_handle.lua new file mode 100644 index 0000000000..e524845f40 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/samjin-multi/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function samjin_multi_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Samjin" then + return true, require("multi-sensor.samjin-multi") + end + return false +end + +return samjin_multi_can_handle diff --git a/drivers/SmartThings/zigbee-contact/src/multi-sensor/samjin-multi/init.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/samjin-multi/init.lua index 935d1cfc20..3bc3edbcf9 100644 --- a/drivers/SmartThings/zigbee-contact/src/multi-sensor/samjin-multi/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/samjin-multi/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 data_types = require "st.zigbee.data_types" @@ -54,9 +44,7 @@ local samjin_driver = { [capabilities.refresh.commands.refresh.NAME] = do_refresh, } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Samjin" - end + can_handle = require("multi-sensor.samjin-multi.can_handle"), } return samjin_driver diff --git a/drivers/SmartThings/zigbee-contact/src/multi-sensor/smartthings-multi/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/smartthings-multi/can_handle.lua new file mode 100644 index 0000000000..6c99521efc --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/smartthings-multi/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function smartthings_multi_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "SmartThings" then + return true, require("multi-sensor.smartthings-multi") + end + return false +end + +return smartthings_multi_can_handle diff --git a/drivers/SmartThings/zigbee-contact/src/multi-sensor/smartthings-multi/init.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/smartthings-multi/init.lua index 69d0b7f3f4..24234ef6de 100644 --- a/drivers/SmartThings/zigbee-contact/src/multi-sensor/smartthings-multi/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/smartthings-multi/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_commands = require "st.zigbee.zcl.global_commands" @@ -91,9 +81,7 @@ local smartthings_multi = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "SmartThings" - end + can_handle = require("multi-sensor.smartthings-multi.can_handle"), } return smartthings_multi diff --git a/drivers/SmartThings/zigbee-contact/src/multi-sensor/sub_drivers.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/sub_drivers.lua new file mode 100644 index 0000000000..3769ab89d1 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/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("multi-sensor/smartthings-multi"), + lazy_load_if_possible("multi-sensor/samjin-multi"), + lazy_load_if_possible("multi-sensor/centralite-multi"), + lazy_load_if_possible("multi-sensor/thirdreality-multi"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-contact/src/multi-sensor/thirdreality-multi/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/thirdreality-multi/can_handle.lua new file mode 100644 index 0000000000..bdfc4f75b5 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/thirdreality-multi/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function thirdreality_multi_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Third Reality, Inc" then + return true, require("multi-sensor.thirdreality-multi") + end + return false +end + +return thirdreality_multi_can_handle diff --git a/drivers/SmartThings/zigbee-contact/src/multi-sensor/thirdreality-multi/init.lua b/drivers/SmartThings/zigbee-contact/src/multi-sensor/thirdreality-multi/init.lua index c63596cab5..1be7988fac 100644 --- a/drivers/SmartThings/zigbee-contact/src/multi-sensor/thirdreality-multi/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/multi-sensor/thirdreality-multi/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 zcl_commands = require "st.zigbee.zcl.global_commands" local multi_utils = require "multi-sensor/multi_utils" @@ -47,9 +37,7 @@ local thirdreality_multi = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Third Reality, Inc" - end + can_handle = require("multi-sensor.thirdreality-multi.can_handle"), } return thirdreality_multi diff --git a/drivers/SmartThings/zigbee-contact/src/sengled/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/sengled/can_handle.lua new file mode 100644 index 0000000000..68d774fb8f --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/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-contact/src/sengled/fingerprints.lua b/drivers/SmartThings/zigbee-contact/src/sengled/fingerprints.lua new file mode 100644 index 0000000000..4d015324b3 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/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 = "E2D-G73" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-contact/src/sengled/init.lua b/drivers/SmartThings/zigbee-contact/src/sengled/init.lua index 95b6e5e3e8..6a4b4c6814 100644 --- a/drivers/SmartThings/zigbee-contact/src/sengled/init.lua +++ b/drivers/SmartThings/zigbee-contact/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 = "E2D-G73" } -} 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.1, 3.0)(driver, device) @@ -49,7 +41,7 @@ local sengled_contact_handler = { lifecycle_handlers = { init = device_init }, - can_handle = is_sengled_products + can_handle = require("sengled.can_handle"), } return sengled_contact_handler diff --git a/drivers/SmartThings/zigbee-contact/src/smartsense-multi/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/smartsense-multi/can_handle.lua new file mode 100644 index 0000000000..f5c226cc55 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/smartsense-multi/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, ...) + local SMARTSENSE_PROFILE_ID = 0xFC01 + local FINGERPRINTS = require("smartsense-multi.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("smartsense-multi") + end + end + local endpoint = device.zigbee_endpoints[1] or device.zigbee_endpoints["1"] + if endpoint.profile_id == SMARTSENSE_PROFILE_ID then return true end + return false +end + +return can_handle diff --git a/drivers/SmartThings/zigbee-contact/src/smartsense-multi/fingerprints.lua b/drivers/SmartThings/zigbee-contact/src/smartsense-multi/fingerprints.lua new file mode 100644 index 0000000000..fff1b601a3 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/smartsense-multi/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local SMARTSENSE_MULTI_FINGERPRINTS = { + { mfr = "SmartThings", model = "PGC313" }, + { mfr = "SmartThings", model = "PGC313EU" } +} + +return SMARTSENSE_MULTI_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-contact/src/smartsense-multi/init.lua b/drivers/SmartThings/zigbee-contact/src/smartsense-multi/init.lua index cbe59d7c09..d5030278e8 100644 --- a/drivers/SmartThings/zigbee-contact/src/smartsense-multi/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/smartsense-multi/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 capabilities = require "st.capabilities" local multi_utils = require "multi-sensor/multi_utils" @@ -24,23 +13,6 @@ local SMARTSENSE_MULTI_ACC_CMD = 0x00 local SMARTSENSE_MULTI_XYZ_CMD = 0x05 local SMARTSENSE_MULTI_STATUS_CMD = 0x07 local SMARTSENSE_MULTI_STATUS_REPORT_CMD = 0x09 -local SMARTSENSE_PROFILE_ID = 0xFC01 - -local SMARTSENSE_MULTI_FINGERPRINTS = { - { mfr = "SmartThings", model = "PGC313" }, - { mfr = "SmartThings", model = "PGC313EU" } -} - -local function can_handle(opts, driver, device, ...) - for _, fingerprint in ipairs(SMARTSENSE_MULTI_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - local endpoint = device.zigbee_endpoints[1] or device.zigbee_endpoints["1"] - if endpoint.profile_id == SMARTSENSE_PROFILE_ID then return true end - return false -end local function acceleration_handler(driver, device, zb_rx) -- This is a custom cluster command for the kickstarter multi. @@ -182,7 +154,7 @@ local smartsense_multi = { } } }, - can_handle = can_handle + can_handle = require("smartsense-multi.can_handle"), } return smartsense_multi diff --git a/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua b/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua new file mode 100644 index 0000000000..394de5a72d --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/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("aurora-contact-sensor"), + lazy_load_if_possible("contact-temperature-sensor"), + lazy_load_if_possible("multi-sensor"), + lazy_load_if_possible("smartsense-multi"), + lazy_load_if_possible("sengled"), + lazy_load_if_possible("frient"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua index 119e3fbdab..7f2dae99fe 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_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-contact/src/test/test_aurora_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua index 40cc2c47be..46fc40f686 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua index e3628f6ec7..d914712e06 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua index e05233ad48..075b923bed 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_contact_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 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua index a05f420166..716828541a 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua index 58c2c8d6b2..f983211856 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua index 7d1319abc0..bd7e7512dd 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua index 12bfbc0b1e..4b0363caba 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_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 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua index d9671a5283..6a7876541f 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_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 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua index 458af19546..291755bb17 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua index d8a23f4af5..2e0c67469d 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua index 1950a22649..39f127dcf4 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_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 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-contact/src/test/test_smartsense_multi.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua index 9db8033062..035c49d27f 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.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 zigbee_test_utils = require "integration_test.zigbee_test_utils" @@ -437,4 +426,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-contact/src/test/test_smartthings_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua index 05e5fdd0d4..5ef40d5aaf 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua index 07beb7c0dd..a6d989609f 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua index d7076ec9ee..8db97fd224 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.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-contact/src/test/test_zigbee_contact_battery.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua index 44570bcce1..5e3404b2a0 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_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 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-contact/src/test/test_zigbee_contact_tyco.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua index e288d3a927..0ee62b73ee 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.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" From a30b7c04915e948e8ed60dc62d1c25241490d047 Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon, 9 Feb 2026 11:24:39 -0600 Subject: [PATCH 092/538] Matter Sensor: fix gating for modular profile update (#2766) The `MODULAR_PROFILE_UPDATED` flag check in infoChanged could cause a subscription request to be sent out before the actual profile update takes place if the infoChanged event was caused by something other than a profile update. This change adds a function to check if the profile was actually updated before sending the subscription request. --- .../device_configuration.lua | 1 - .../air_quality_sensor_utils/fields.lua | 2 -- .../air_quality_sensor_utils/utils.lua | 18 ++++++++++++++++++ .../sub_drivers/air_quality_sensor/init.lua | 4 ++-- 4 files changed, 20 insertions(+), 5 deletions(-) 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 index 6e1f1a5d81..a30eaed0f8 100644 --- 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 @@ -71,7 +71,6 @@ function DeviceConfiguration.match_profile(device) 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. 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 index 0d3c5dd21c..1e4658c99e 100644 --- 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 @@ -26,8 +26,6 @@ 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 = { 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 index 1a36f5f920..95ca80964c 100644 --- 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 @@ -77,4 +77,22 @@ function AirQualitySensorUtils.set_supported_health_concern_values(device) end end +function AirQualitySensorUtils.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 + return AirQualitySensorUtils 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 index 61280fd107..98b8430c98 100644 --- 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 @@ -66,12 +66,12 @@ function AirQualitySensorLifecycleHandlers.device_init(driver, device) 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.profile.id ~= args.old_st_store.profile.id or + aqs_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) 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 From 23134b902c644739366629e0ace803d553ba08c3 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 5 Feb 2026 10:42:42 -0800 Subject: [PATCH 093/538] Merge pull request #2731 from SmartThingsCommunity/Sonoff_SNZB-01P Sonoff SNZB-01P - device label change --- drivers/SmartThings/zigbee-button/fingerprints.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-button/fingerprints.yml b/drivers/SmartThings/zigbee-button/fingerprints.yml index c70cae5eac..c73109ebca 100644 --- a/drivers/SmartThings/zigbee-button/fingerprints.yml +++ b/drivers/SmartThings/zigbee-button/fingerprints.yml @@ -220,7 +220,7 @@ zigbeeManufacturer: model: WB01 deviceProfileName: one-button-battery - id: eWeLink/SNZB-01P - deviceLabel: eWeLink Button + deviceLabel: Zigbee Wireless Switch manufacturer: eWeLink model: SNZB-01P deviceProfileName: one-button-battery From cd71bb442ef05b6e8cad005a78f0052ac65e2a6f Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon, 9 Feb 2026 18:34:52 -0600 Subject: [PATCH 094/538] Matter Camera: Fix gating of videoStreamSettings capability (#2769) `videoStreamSettings` should be included in the camera profile if the Camera AVSM cluster is available and the VDO feature is enabled. It's currently gated by the presence of the Camera AVSULM cluster which is incorrect. --- .../camera/camera_utils/device_configuration.lua | 2 +- .../matter-switch/src/test/test_matter_camera.lua | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) 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 index c84337736d..20641ebd33 100644 --- 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 @@ -66,6 +66,7 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr table.insert(main_component_capabilities, capabilities.videoCapture2.ID) end table.insert(main_component_capabilities, capabilities.cameraViewportSettings.ID) + table.insert(main_component_capabilities, capabilities.videoStreamSettings.ID) end if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.LOCAL_STORAGE) then table.insert(main_component_capabilities, capabilities.localMediaStorage.ID) @@ -103,7 +104,6 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr 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 diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 1028197590..db8d980fab 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -239,6 +239,7 @@ local expected_metadata = { { "videoCapture2", "cameraViewportSettings", + "videoStreamSettings", "localMediaStorage", "audioRecording", "cameraPrivacyMode", @@ -246,7 +247,6 @@ local expected_metadata = { "hdr", "nightVision", "mechanicalPanTiltZoom", - "videoStreamSettings", "zoneManagement", "webrtc", "motionSensor", @@ -1857,11 +1857,11 @@ test.register_coroutine_test( uint32(clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID) }) }) - local expected_metadata = { + local updated_expected_metadata = { optional_component_capabilities = { { "main", - { "videoCapture2", "cameraViewportSettings", "localMediaStorage", "audioRecording", "cameraPrivacyMode", - "imageControl", "hdr", "nightVision", "mechanicalPanTiltZoom", "videoStreamSettings", "zoneManagement", + { "videoCapture2", "cameraViewportSettings", "videoStreamSettings", "localMediaStorage", "audioRecording", + "cameraPrivacyMode", "imageControl", "hdr", "nightVision", "mechanicalPanTiltZoom", "zoneManagement", "webrtc", "motionSensor", "sounds", } }, { "statusLed", @@ -1879,7 +1879,7 @@ test.register_coroutine_test( }, profile = "camera" } - mock_device:expect_metadata_update(expected_metadata) + mock_device:expect_metadata_update(updated_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 From 5e9fdc86809e3807951a5506142ecce48d9bbef9 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:36 -0600 Subject: [PATCH 095/538] CHAD-17067: zigbee-fan lazy load subdrivers --- .../zigbee-fan/src/configurations.lua | 15 ++------- .../zigbee-fan/src/fan-light/can_handle.lua | 14 +++++++++ .../zigbee-fan/src/fan-light/fingerprints.lua | 8 +++++ .../zigbee-fan/src/fan-light/init.lua | 31 +++---------------- drivers/SmartThings/zigbee-fan/src/init.lua | 23 +++----------- .../zigbee-fan/src/lazy_load_subdriver.lua | 15 +++++++++ .../zigbee-fan/src/sub_drivers.lua | 8 +++++ .../zigbee-fan/src/test/test_fan_light.lua | 16 ++-------- 8 files changed, 59 insertions(+), 71 deletions(-) create mode 100644 drivers/SmartThings/zigbee-fan/src/fan-light/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-fan/src/fan-light/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-fan/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-fan/src/sub_drivers.lua diff --git a/drivers/SmartThings/zigbee-fan/src/configurations.lua b/drivers/SmartThings/zigbee-fan/src/configurations.lua index c2cbb57de0..f6bcbe7bf5 100644 --- a/drivers/SmartThings/zigbee-fan/src/configurations.lua +++ b/drivers/SmartThings/zigbee-fan/src/configurations.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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-fan/src/fan-light/can_handle.lua b/drivers/SmartThings/zigbee-fan/src/fan-light/can_handle.lua new file mode 100644 index 0000000000..46523f65f9 --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/src/fan-light/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_itm_fanlight(opts, driver, device) + local FINGERPRINTS = require("fan-light.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("fan-light") + end + end + return false +end + +return can_handle_itm_fanlight diff --git a/drivers/SmartThings/zigbee-fan/src/fan-light/fingerprints.lua b/drivers/SmartThings/zigbee-fan/src/fan-light/fingerprints.lua new file mode 100644 index 0000000000..bf93802c10 --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/src/fan-light/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FINGERPRINTS = { + { mfr = "Samsung Electronics", model = "SAMSUNG-ITM-Z-003" }, +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua b/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua index 95329c1ddf..94d44bfde3 100644 --- a/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua +++ b/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua @@ -1,34 +1,13 @@ --- 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 clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" local FanControl = clusters.FanControl local Level = clusters.Level local OnOff = clusters.OnOff -local FINGERPRINTS = { - { mfr = "Samsung Electronics", model = "SAMSUNG-ITM-Z-003" }, -} -local function can_handle_itm_fanlight(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 -- CAPABILITY HANDLERS @@ -111,9 +90,7 @@ local itm_fan_light = { [capabilities.fanSpeed.commands.setFanSpeed.NAME] = fan_speed_handler } }, - can_handle = can_handle_itm_fanlight + can_handle = require("fan-light.can_handle"), } return itm_fan_light - - diff --git a/drivers/SmartThings/zigbee-fan/src/init.lua b/drivers/SmartThings/zigbee-fan/src/init.lua index a34e90ff5e..c4a7185838 100644 --- a/drivers/SmartThings/zigbee-fan/src/init.lua +++ b/drivers/SmartThings/zigbee-fan/src/init.lua @@ -1,23 +1,13 @@ --- 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 capabilities = require "st.capabilities" local ZigbeeDriver = require "st.zigbee" local defaults = require "st.zigbee.defaults" -local configurationMap = require "configurations" local device_init = function(self, device) + local configurationMap = require "configurations" local configuration = configurationMap.get_device_configuration(device) if configuration ~= nil then for _, attribute in ipairs(configuration) do @@ -32,9 +22,7 @@ local zigbee_fan_driver = { capabilities.switchLevel, capabilities.fanspeed }, - sub_drivers = { - require("fan-light") - }, + sub_drivers = require("sub_drivers"), lifecycle_handlers = { init = device_init }, @@ -44,4 +32,3 @@ local zigbee_fan_driver = { defaults.register_for_default_handlers(zigbee_fan_driver,zigbee_fan_driver.supported_capabilities) local fan = ZigbeeDriver("zigbee-fan", zigbee_fan_driver) fan:run() - diff --git a/drivers/SmartThings/zigbee-fan/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-fan/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/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-fan/src/sub_drivers.lua b/drivers/SmartThings/zigbee-fan/src/sub_drivers.lua new file mode 100644 index 0000000000..f43392d622 --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/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("fan-light"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua index 4ef4ce37e6..0fa987e35a 100644 --- a/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua +++ b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.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 + local test = require "integration_test" local t_utils = require "integration_test.utils" local clusters = require "st.zigbee.zcl.clusters" From 10586b2cdf1caad2446aaca91cf0bc6754e28b1a Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:18 -0600 Subject: [PATCH 096/538] CHAD-17069: zigbee-illuminance lazy load sub drivers --- .../src/aqara/can_handle.lua | 14 +++++++++++++ .../src/aqara/fingerprints.lua | 8 ++++++++ .../src/aqara/init.lua | 16 ++++----------- .../zigbee-illuminance-sensor/src/init.lua | 20 ++++--------------- .../src/lazy_load_subdriver.lua | 15 ++++++++++++++ .../src/sub_drivers.lua | 8 ++++++++ .../src/test/test_illuminance_sensor.lua | 15 ++------------ .../test/test_illuminance_sensor_aqara.lua | 15 ++------------ 8 files changed, 57 insertions(+), 54 deletions(-) create mode 100644 drivers/SmartThings/zigbee-illuminance-sensor/src/aqara/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-illuminance-sensor/src/aqara/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-illuminance-sensor/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-illuminance-sensor/src/sub_drivers.lua diff --git a/drivers/SmartThings/zigbee-illuminance-sensor/src/aqara/can_handle.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/aqara/can_handle.lua new file mode 100644 index 0000000000..e4453597ed --- /dev/null +++ b/drivers/SmartThings/zigbee-illuminance-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-illuminance-sensor/src/aqara/fingerprints.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/aqara/fingerprints.lua new file mode 100644 index 0000000000..69218a197a --- /dev/null +++ b/drivers/SmartThings/zigbee-illuminance-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.sen_ill.agl01" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-illuminance-sensor/src/aqara/init.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/aqara/init.lua index 72fcbc2489..0eb5e1b059 100644 --- a/drivers/SmartThings/zigbee-illuminance-sensor/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-illuminance-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 zcl_commands = require "st.zigbee.zcl.global_commands" @@ -19,9 +22,6 @@ local FREQUENCY_ATTRIBUTE_ID = 0x0000 local FREQUENCY_DEFAULT_VALUE = 5 local FREQUENCY_PREF = "frequencyPref" -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.sen_ill.agl01" } -} local configuration = { { @@ -42,14 +42,6 @@ local configuration = { } } -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 detection_frequency_capability_handler(driver, device, command) local frequency = command.args.frequency @@ -104,7 +96,7 @@ local aqara_illuminance_handler = { } } }, - can_handle = is_aqara_products + can_handle = require("aqara.can_handle"), } return aqara_illuminance_handler diff --git a/drivers/SmartThings/zigbee-illuminance-sensor/src/init.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/init.lua index 635503c232..7f84eb68dc 100644 --- a/drivers/SmartThings/zigbee-illuminance-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-illuminance-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" @@ -26,12 +16,10 @@ local zigbee_illuminance_driver = { capabilities.illuminanceMeasurement, capabilities.battery }, - sub_drivers = { - require("aqara") - }, lifecycle_handlers = { doConfigure = do_configure, }, + sub_drivers = require("sub_drivers"), health_check = false, } diff --git a/drivers/SmartThings/zigbee-illuminance-sensor/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-illuminance-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-illuminance-sensor/src/sub_drivers.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/sub_drivers.lua new file mode 100644 index 0000000000..6211f46578 --- /dev/null +++ b/drivers/SmartThings/zigbee-illuminance-sensor/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("aqara"), +} +return sub_drivers 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 1987964075..92e93119f5 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 @@ -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/test/test_illuminance_sensor_aqara.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua index 11d66478ba..557d4e2345 100644 --- a/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua +++ b/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.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" From 42658b056d2561b17b761915c24959506cf8636d Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:35:29 -0600 Subject: [PATCH 097/538] Matter Thermostat: Add 'off' to thermostatMode by default only if switch is not supported (#2761) --- .../src/test/test_matter_room_ac.lua | 84 +++++++++++++++++++ .../attribute_handlers.lua | 8 +- 2 files changed, 90 insertions(+), 2 deletions(-) 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 633fb3f1c1..bb7b9e6bde 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 @@ -481,4 +481,88 @@ test.register_message_test( } ) +local ControlSequenceOfOperation = clusters.Thermostat.attributes.ControlSequenceOfOperation +test.register_message_test( + "Room AC control sequence reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + ControlSequenceOfOperation:build_test_report_data(mock_device, 1, ControlSequenceOfOperation.COOLING_AND_HEATING_WITH_REHEAT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + ControlSequenceOfOperation:build_test_report_data(mock_device, 1, ControlSequenceOfOperation.HEATING_WITH_REHEAT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + ControlSequenceOfOperation:build_test_report_data(mock_device, 1, ControlSequenceOfOperation.COOLING_WITH_REHEAT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"cool"}, {visibility={displayed=false}})) + }, + } +) + +test.register_message_test( + "Additional mode reports should extend the supported modes", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.ControlSequenceOfOperation:build_test_report_data(mock_device, 1, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.SystemMode:build_test_report_data(mock_device, 1, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat", "emergency heat"}, {visibility={displayed=false}})) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.emergency_heat()) + } + } +) + + test.run_registered_tests() diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua index 78f5ee3bf2..f0a210bc16 100644 --- a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua @@ -84,9 +84,13 @@ 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. + -- We also assert that Off is supported if the switch capability is not 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}) + if device:supports_capability(capabilities.switch) == false then + device:set_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN, {capabilities.thermostatMode.thermostatMode.off.NAME}, {persist=true}) + else + device:set_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN, {}, {persist=true}) + end end local supported_modes = st_utils.deep_copy(device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN)) local disallowed_mode_operations = {} From 133c29a527124b2a79896884a2f0013288d4d4d3 Mon Sep 17 00:00:00 2001 From: Dawid Liberda <157011002+daveylib-abb@users.noreply.github.com> Date: Wed, 11 Feb 2026 00:34:45 +0100 Subject: [PATCH 098/538] Introduction of the LUA Edge Driver for ABB SCU200 InSite Energy Management System (#2190) * Introduction of the LUA Edge Driver for ABB SCU200 InSite Energy Managament System * Added powerConsumptionReport capability to the INS-E3 product * Implementing suggestions made by the SmartThings Energy team * Removed unnecessary device profiles * Fixed detecting type of current sensors and removed support of production current sensors * Fixed all the warnings from the Luacheck linter tool * Moved the abb-scu200 driver to the ABB directory and renamed to insite-scu200 --- drivers/ABB/insite-scu200/config.yml | 7 + .../profiles/auxiliary-contact.yml | 34 ++ drivers/ABB/insite-scu200/profiles/bridge.yml | 8 + .../profiles/current-sensor-consumption.yml | 16 + .../profiles/current-sensor-production.yml | 16 + .../insite-scu200/profiles/energy-meter.yml | 44 ++ .../ABB/insite-scu200/profiles/gas-meter.yml | 10 + .../insite-scu200/profiles/output-module.yml | 10 + .../profiles/usb-energy-meter.yml | 14 + .../insite-scu200/profiles/water-meter.yml | 10 + .../ABB/insite-scu200/search-parameters.yaml | 2 + drivers/ABB/insite-scu200/src/abb/api.lua | 145 +++++ .../insite-scu200/src/abb/device_manager.lua | 115 ++++ .../src/abb/device_refresher.lua | 382 +++++++++++++ drivers/ABB/insite-scu200/src/commands.lua | 111 ++++ drivers/ABB/insite-scu200/src/config.lua | 72 +++ .../insite-scu200/src/connection_monitor.lua | 71 +++ drivers/ABB/insite-scu200/src/discovery.lua | 315 +++++++++++ .../insite-scu200/src/eventsource_handler.lua | 128 +++++ drivers/ABB/insite-scu200/src/fields.lua | 16 + drivers/ABB/insite-scu200/src/init.lua | 45 ++ drivers/ABB/insite-scu200/src/lifecycles.lua | 87 +++ .../ABB/insite-scu200/src/lunchbox/init.lua | 4 + .../ABB/insite-scu200/src/lunchbox/rest.lua | 427 ++++++++++++++ .../src/lunchbox/sse/eventsource.lua | 523 ++++++++++++++++++ .../ABB/insite-scu200/src/lunchbox/util.lua | 46 ++ drivers/ABB/insite-scu200/src/utils.lua | 219 ++++++++ 27 files changed, 2877 insertions(+) create mode 100644 drivers/ABB/insite-scu200/config.yml create mode 100644 drivers/ABB/insite-scu200/profiles/auxiliary-contact.yml create mode 100644 drivers/ABB/insite-scu200/profiles/bridge.yml create mode 100644 drivers/ABB/insite-scu200/profiles/current-sensor-consumption.yml create mode 100644 drivers/ABB/insite-scu200/profiles/current-sensor-production.yml create mode 100644 drivers/ABB/insite-scu200/profiles/energy-meter.yml create mode 100644 drivers/ABB/insite-scu200/profiles/gas-meter.yml create mode 100644 drivers/ABB/insite-scu200/profiles/output-module.yml create mode 100644 drivers/ABB/insite-scu200/profiles/usb-energy-meter.yml create mode 100644 drivers/ABB/insite-scu200/profiles/water-meter.yml create mode 100644 drivers/ABB/insite-scu200/search-parameters.yaml create mode 100644 drivers/ABB/insite-scu200/src/abb/api.lua create mode 100644 drivers/ABB/insite-scu200/src/abb/device_manager.lua create mode 100644 drivers/ABB/insite-scu200/src/abb/device_refresher.lua create mode 100644 drivers/ABB/insite-scu200/src/commands.lua create mode 100644 drivers/ABB/insite-scu200/src/config.lua create mode 100644 drivers/ABB/insite-scu200/src/connection_monitor.lua create mode 100644 drivers/ABB/insite-scu200/src/discovery.lua create mode 100644 drivers/ABB/insite-scu200/src/eventsource_handler.lua create mode 100644 drivers/ABB/insite-scu200/src/fields.lua create mode 100644 drivers/ABB/insite-scu200/src/init.lua create mode 100644 drivers/ABB/insite-scu200/src/lifecycles.lua create mode 100644 drivers/ABB/insite-scu200/src/lunchbox/init.lua create mode 100644 drivers/ABB/insite-scu200/src/lunchbox/rest.lua create mode 100644 drivers/ABB/insite-scu200/src/lunchbox/sse/eventsource.lua create mode 100644 drivers/ABB/insite-scu200/src/lunchbox/util.lua create mode 100644 drivers/ABB/insite-scu200/src/utils.lua diff --git a/drivers/ABB/insite-scu200/config.yml b/drivers/ABB/insite-scu200/config.yml new file mode 100644 index 0000000000..2cda5384d4 --- /dev/null +++ b/drivers/ABB/insite-scu200/config.yml @@ -0,0 +1,7 @@ +name: 'SCU200 InSite Energy Management System' +packageKey: 'ABB.SCU200' +description: "SmartThings driver for SCU200 InSite Energy Management System" +vendorSupportInformation: "https://support.smartthings.com" +permissions: + lan: {} + discovery: {} \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/profiles/auxiliary-contact.yml b/drivers/ABB/insite-scu200/profiles/auxiliary-contact.yml new file mode 100644 index 0000000000..92c4a2fcb6 --- /dev/null +++ b/drivers/ABB/insite-scu200/profiles/auxiliary-contact.yml @@ -0,0 +1,34 @@ +name: abb.scu200.auxiliary-contact.v1 +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch +deviceConfig: + dashboard: + states: + - component: main + capability: switch + version: 1 + actions: [] + detailView: + - component: main + capability: switch + version: 1 + visibleCondition: + capability: switch + version: 1 + component: main + value: switch.value + operator: ONE_OF + operand: '[""]' + automation: + conditions: + - component: main + capability: switch + version: 1 + actions: [] \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/profiles/bridge.yml b/drivers/ABB/insite-scu200/profiles/bridge.yml new file mode 100644 index 0000000000..d45ebc4943 --- /dev/null +++ b/drivers/ABB/insite-scu200/profiles/bridge.yml @@ -0,0 +1,8 @@ +name: abb.scu200.bridge.v1 +components: +- id: main + capabilities: + - id: refresh + version: 1 + categories: + - name: Bridges diff --git a/drivers/ABB/insite-scu200/profiles/current-sensor-consumption.yml b/drivers/ABB/insite-scu200/profiles/current-sensor-consumption.yml new file mode 100644 index 0000000000..5843dae90f --- /dev/null +++ b/drivers/ABB/insite-scu200/profiles/current-sensor-consumption.yml @@ -0,0 +1,16 @@ +name: abb.scu200.current-sensor-consumption.v1 +components: +- id: main + capabilities: + - id: currentMeasurement + version: 1 + - id: powerMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: CurbPowerMeter \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/profiles/current-sensor-production.yml b/drivers/ABB/insite-scu200/profiles/current-sensor-production.yml new file mode 100644 index 0000000000..ec4e12aeae --- /dev/null +++ b/drivers/ABB/insite-scu200/profiles/current-sensor-production.yml @@ -0,0 +1,16 @@ +name: abb.scu200.current-sensor-production.v1 +components: +- id: main + capabilities: + - id: currentMeasurement + version: 1 + - id: powerMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: CurbPowerMeter \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/profiles/energy-meter.yml b/drivers/ABB/insite-scu200/profiles/energy-meter.yml new file mode 100644 index 0000000000..37fab87dbe --- /dev/null +++ b/drivers/ABB/insite-scu200/profiles/energy-meter.yml @@ -0,0 +1,44 @@ +name: abb.scu200.energy-meter.v1 +components: +- id: main + capabilities: + - id: voltageMeasurement + version: 1 + - id: currentMeasurement + version: 1 + - id: powerMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: CurbPowerMeter +- id: consumptionMeter + label: "From Grid" + capabilities: + - id: powerConsumptionReport + version: 1 + - id: energyMeter + version: 1 + categories: + - name: CurbPowerMeter +- id: productionMeter + label: "To Grid" + capabilities: + - id: powerConsumptionReport + version: 1 + - id: energyMeter + version: 1 + categories: + - name: CurbPowerMeter +- id: surplus + capabilities: + - id: powerConsumptionReport + version: 1 + - id: energyMeter + version: 1 + categories: + - name: CurbPowerMeter \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/profiles/gas-meter.yml b/drivers/ABB/insite-scu200/profiles/gas-meter.yml new file mode 100644 index 0000000000..cc3297eb05 --- /dev/null +++ b/drivers/ABB/insite-scu200/profiles/gas-meter.yml @@ -0,0 +1,10 @@ +name: abb.scu200.gas-meter.v1 +components: +- id: main + capabilities: + - id: gasMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: Others \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/profiles/output-module.yml b/drivers/ABB/insite-scu200/profiles/output-module.yml new file mode 100644 index 0000000000..d6e0395da8 --- /dev/null +++ b/drivers/ABB/insite-scu200/profiles/output-module.yml @@ -0,0 +1,10 @@ +name: abb.scu200.output-module.v1 +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch diff --git a/drivers/ABB/insite-scu200/profiles/usb-energy-meter.yml b/drivers/ABB/insite-scu200/profiles/usb-energy-meter.yml new file mode 100644 index 0000000000..8eb09ce365 --- /dev/null +++ b/drivers/ABB/insite-scu200/profiles/usb-energy-meter.yml @@ -0,0 +1,14 @@ +name: abb.scu200.usb-energy-meter.v1 +components: +- id: main + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: refresh + version: 1 + categories: + - name: CurbPowerMeter \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/profiles/water-meter.yml b/drivers/ABB/insite-scu200/profiles/water-meter.yml new file mode 100644 index 0000000000..bf5f50068d --- /dev/null +++ b/drivers/ABB/insite-scu200/profiles/water-meter.yml @@ -0,0 +1,10 @@ +name: abb.scu200.water-meter.v1 +components: +- id: main + capabilities: + - id: waterMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: Others \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/search-parameters.yaml b/drivers/ABB/insite-scu200/search-parameters.yaml new file mode 100644 index 0000000000..5cfaf45c83 --- /dev/null +++ b/drivers/ABB/insite-scu200/search-parameters.yaml @@ -0,0 +1,2 @@ +ssdp: + - searchTerm: urn:ABB:device:SCU200:1 \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/abb/api.lua b/drivers/ABB/insite-scu200/src/abb/api.lua new file mode 100644 index 0000000000..6524d86567 --- /dev/null +++ b/drivers/ABB/insite-scu200/src/abb/api.lua @@ -0,0 +1,145 @@ +local log = require("log") +local st_utils = require "st.utils" +local json = require "st.json" + +-- Local imports +local config = require("config") +local utils = require("utils") +local RestClient = require "lunchbox.rest" + +-- API for the ABB SCU200 Bridge +local api = {} +api.__index = api + +local SSL_CONFIG = { + mode = "client", + protocol = "any", + verify = "none", + options = "all" +} + +local ADDITIONAL_HEADERS = { + ["Accept"] = "application/json", + ["Content-Type"] = "application/json", +} + +-- Method for getting the base URL +local function get_base_url(bridge_ip) + return "https://" .. bridge_ip .. ":" .. config.REST_API_PORT +end + +-- Method for processing the REST response +local function process_rest_response(response, err, partial) + if err ~= nil then + return response, err, nil + elseif response ~= nil then + local status, decoded_json = pcall(json.decode, response:get_body()) + + if status and response.status == 200 then + log.debug("process_rest_response(): Response = " .. response.status .. " " .. response:get_body()) + + return decoded_json, nil, response.status + elseif status then + log.error("process_rest_response(): Response error = " .. response.status) + + return nil, "response status is not 200 OK", response.status + else + log.error("process_rest_response(): Failed to decode data") + + return nil, "failed to decode data", nil + end + else + return nil, "no response or error received", nil + end +end + +-- Method for creating a retry function +local function retry_fn(retry_attempts) + local count = 0 + + return function() + count = count + 1 + return count < retry_attempts + end +end + +-- Method for performing a GET request +local function do_get(api_instance, path) + log.debug("do_get(): Sending GET request to " .. path) + + return process_rest_response(api_instance.client:get(path, api_instance.headers, retry_fn(5))) +end + +-- Method for performing a POST request +local function do_post(api_instance, path, payload) + log.debug("do_post(): Sending POST request to " .. path .. " with payload " .. json.encode(payload)) + + return process_rest_response(api_instance.client:post(path, payload, api_instance.headers, retry_fn(5))) +end + +-- Method for creating a labeled socket builder +function api.labeled_socket_builder(label) + local socket_builder = utils.labeled_socket_builder(label, SSL_CONFIG) + + return socket_builder +end + +-- Method for creating a new bridge manager +function api.new_bridge_manager(bridge_ip, bridge_dni) + local base_url = get_base_url(bridge_ip) + local socket_builder = api.labeled_socket_builder(bridge_dni) + + return setmetatable( + { + headers = st_utils.deep_copy(ADDITIONAL_HEADERS), + client = RestClient.new(base_url, socket_builder), + base_url = base_url + }, + api + ) +end + +-- Method for getting the thing infos +function api.get_thing_infos(bridge_ip, bridge_dni) + local socket_builder = api.labeled_socket_builder(bridge_dni .. " (thing infos)") + local response, error, status = process_rest_response(RestClient.one_shot_get(get_base_url(bridge_ip) .. "/devices", ADDITIONAL_HEADERS, socket_builder)) + + if not error and status == 200 then + return response + else + log.error("api.get_thing_infos(): Failed to get thing infos, error = " .. error) + return nil + end +end + +-- Method for getting the bridge info +function api.get_bridge_info(bridge_ip, bridge_dni) + local socket_builder = api.labeled_socket_builder(bridge_dni .. " (bridge info)") + local response, error, status = process_rest_response(RestClient.one_shot_get(get_base_url(bridge_ip) .. "/bridge", ADDITIONAL_HEADERS, socket_builder)) + + if not error and status == 200 then + return response + else + log.error("api.get_bridge_info(): Failed to get thing infos, error = " .. error) + return nil + end +end + +-- API methods +function api:get_devices() + return do_get(self, "/devices") +end + +function api:get_device_by_id(id) + return do_get(self, string.format("/devices/%s", id)) +end + +function api:post_device_by_id(id, payload) + return do_post(self, string.format("/devices/%s/control", id), payload) +end + +function api:get_sse_url() + return self.base_url .. "/events" +end + +return api \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/abb/device_manager.lua b/drivers/ABB/insite-scu200/src/abb/device_manager.lua new file mode 100644 index 0000000000..a9de6c0811 --- /dev/null +++ b/drivers/ABB/insite-scu200/src/abb/device_manager.lua @@ -0,0 +1,115 @@ +local log = require("log") + +-- Local imports +local utils = require("utils") +local fields = require("fields") +local api = require("abb.api") +local device_refresher = require("abb.device_refresher") + +-- Device manager methods +local device_manager = {} + +-- Method for checking if connection is valid +function device_manager.is_valid_connection(driver, device, conn_info) + local dni = utils.get_dni_from_device(device) + + if not conn_info then + log.error("device_manager.is_valid_connection(): Failed to find conn_info, dni = " .. dni) + return false + end + + local bridge_ip = utils.get_device_ip_address(device) + local thing_infos = api.get_thing_infos(bridge_ip, dni) + + if thing_infos and thing_infos.devices then + return true + else + log.error("device_manager.is_valid_connection(): Failed to get thing infos, dni = " .. dni) + return false + end +end + +-- Method for getting bridge connection info +function device_manager.get_bridge_connection_info(driver, bridge_dni, bridge_ip) + local bridge_conn_info = api.new_bridge_manager(bridge_ip, bridge_dni) + + if bridge_conn_info == nil then + log.error("device_manager.get_bridge_connection_info(): No bridge connection info") + end + + return bridge_conn_info +end + +-- Method for handling JSON status +function device_manager.handle_device_json(driver, device, device_json) + local dni = utils.get_dni_from_device(device) + if dni == nil then + log.error("device_manager.handle_device_json(): dni is nil, the device has been probably deleted") + return + end + + if not device_json then + log.error("device_manager.handle_device_json(): device_json is nil, dni = " .. dni) + return + end + + log.debug("device_manager.handle_device_json(): dni: " .. dni .. " device_json = " .. utils.dump(device_json)) + + local status = device_json.status + if status ~= nil then + if status == "offline" then + log.info("device_manager.handle_device_json(): status is offline, dni = " .. dni) + + device:offline() + return + elseif status == "online" then + device:online() + end + end + + local values = device_json.values + if values == nil then + log.error("device_manager.handle_device_json(): values is nil, dni = " .. dni) + return + end + + device_refresher.refresh_device(driver, device, values) +end + +-- Method for refreshing device +function device_manager.refresh(driver, device) + local dni = utils.get_dni_from_device(device) + local communication_device = device:get_parent_device() or device + local conn_info = communication_device:get_field(fields.CONN_INFO) + + if not conn_info then + log.warn("device_manager.refresh(): Failed to find conn_info, dni = " .. dni) + return + end + + local response, err, status = conn_info:get_device_by_id(dni) + + if err or status ~= 200 then + status = status or "nil" + log.error("device_manager.refresh(): Failed to get device by id, dni = " .. dni .. ", err = " .. err .. ", status = " .. status) + + device:offline() + + return + end + + device_manager.handle_device_json(driver, device, response) +end + +-- Method for monitoring the connection of the bridge devices +function device_manager.bridge_monitor(driver, device, bridge_info) + local child_devices = device:get_child_list() + + for _, thing_device in ipairs(child_devices) do + device.thread:call_with_delay(0, function() -- Run within bridge thread to use the same connection + device_manager.refresh(driver, thing_device) + end) + end +end + +return device_manager \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/abb/device_refresher.lua b/drivers/ABB/insite-scu200/src/abb/device_refresher.lua new file mode 100644 index 0000000000..628c5b4201 --- /dev/null +++ b/drivers/ABB/insite-scu200/src/abb/device_refresher.lua @@ -0,0 +1,382 @@ +local log = require("log") +local caps = require('st.capabilities') + +-- Local imports +local utils = require("utils") +local config = require("config") +local fields = require('fields') + +-- Controller for refreshing device data +local device_refresher = {} + +local function refresh_current_sensor(driver, device, values) + local dni = utils.get_dni_from_device(device) + log.info("refresh_current_sensor(): Refreshing data of Current Sensor, dni = " .. dni) + + -- Refresh Current Measurement + local current = values.current + + if current ~= nil then + log.trace("refresh_current_sensor(): Refreshing Current Measurement, dni = " .. dni) + device.profile.components["main"]:emit_event(caps.currentMeasurement.current({value=current, unit="A"})) + end + + -- Refresh Active Power + local activePower = values.activePower + + if activePower ~= nil then + log.trace("refresh_current_sensor(): Refreshing Active Power, dni = " .. dni) + device.profile.components["main"]:emit_event(caps.powerMeter.power({value=activePower, unit="W"})) + end + + -- Refresh Active Energy + local activeEnergy = values.activeEnergy + + if activeEnergy ~= nil then + -- Refresh Active Energy + log.trace("refresh_current_sensor(): Refreshing Active Energy, dni = " .. dni) + device.profile.components["main"]:emit_event(caps.energyMeter.energy({value=activeEnergy, unit="kWh"})) + + -- Verify whether the appropriate time have elapsed to report the energy values + local last_energy_report = device:get_field(fields.LAST_ENERGY_REPORT) or 0.0 + + if (os.time() - last_energy_report) >= config.EDGE_CHILD_ENERGY_REPORT_INTERVAL then -- Report the energy consumption/production periodically + local current_consumption_production_report = device:get_latest_state("main", caps.powerConsumptionReport.ID, caps.powerConsumptionReport.powerConsumption.NAME) + + -- Calculate delta consumption/production energy + local delta_consumption_production_report = 0.0 + if current_consumption_production_report ~= nil then + delta_consumption_production_report = math.max((activeEnergy * 1000) - current_consumption_production_report.energy, 0.0) + end + + -- Refresh Power Energy Report + log.trace("refresh_current_sensor(): Refreshing Energy Report, dni = " .. dni) + device.profile.components["main"]:emit_event(caps.powerConsumptionReport.powerConsumption({energy=activeEnergy * 1000, deltaEnergy=delta_consumption_production_report})) + + -- Save date of the last energy report + local current_energy_report = last_energy_report + config.EDGE_CHILD_ENERGY_REPORT_INTERVAL + if (current_energy_report + config.EDGE_CHILD_ENERGY_REPORT_INTERVAL) < os.time() then + current_energy_report = os.time() + end + + device:set_field(fields.LAST_ENERGY_REPORT, current_energy_report, {persist=false}) + else + log.debug("refresh_current_sensor(): " .. config.EDGE_CHILD_ENERGY_REPORT_INTERVAL .. " seconds haven't elapsed yet! Last consumption was at " .. last_energy_report .. ", dni = " .. dni) + end + end + + return true +end + +local function refresh_energy_meter(driver, device, values) + local dni = utils.get_dni_from_device(device) + log.info("refresh_energy_meter(): Refreshing data of Energy Meter, dni = " .. dni) + + -- Refresh Voltage Measurement + local voltage = values.voltage + + if voltage ~= nil then + log.trace("refresh_energy_meter(): Refreshing Voltage Measurement, dni = " .. dni) + device.profile.components["main"]:emit_event(caps.voltageMeasurement.voltage({value=voltage, unit="V"})) + end + + -- Refresh Current Measurement + local current = values.current + + if current ~= nil then + log.trace("refresh_energy_meter(): Refreshing Current Measurement, dni = " .. dni) + device.profile.components["main"]:emit_event(caps.currentMeasurement.current({value=current, unit="A"})) + end + + -- Refresh Active Power + local activePower = values.activePowerTotal + + if activePower ~= nil then + log.trace("refresh_energy_meter(): Refreshing Active Power, dni = " .. dni) + device.profile.components["main"]:emit_event(caps.powerMeter.power({value=activePower, unit="W"})) + end + + -- Refresh Active Energy Net, Import & Export Total + local activeEnergyNetTotal = values.activeEnergyNetTotal + local activeEnergyImportTotal = values.activeEnergyImportTotal + local activeEnergyExportTotal = values.activeEnergyExportTotal + + if activeEnergyNetTotal == nil then + if activeEnergyImportTotal ~= nil and activeEnergyExportTotal ~= nil then + -- If only Import and Export Total are available, calculate Net Total + activeEnergyNetTotal = activeEnergyImportTotal - activeEnergyExportTotal + end + end + + local activeEnergyNetPositive = math.max(activeEnergyNetTotal, 0.0) + local activeEnergyNetNegative = math.min(activeEnergyNetTotal, 0.0) * -1 -- Convert to positive value + + if activeEnergyNetTotal ~= nil and activeEnergyImportTotal ~= nil and activeEnergyExportTotal ~= nil then + -- Refresh Active Energy Net Positive + log.trace("refresh_energy_meter(): Refreshing Active Energy Net Positive, dni = " .. dni) + device.profile.components["main"]:emit_event(caps.energyMeter.energy({value=activeEnergyNetPositive, unit="kWh"})) + + -- Refresh Active Energy Import Total + log.trace("refresh_energy_meter(): Refreshing Active Energy Import Total, dni = " .. dni) + device.profile.components["consumptionMeter"]:emit_event(caps.energyMeter.energy({value=activeEnergyImportTotal, unit="kWh"})) + + -- Refresh Active Energy Export Total + log.trace("refresh_energy_meter(): Refreshing Active Energy Export Total, dni = " .. dni) + device.profile.components["productionMeter"]:emit_event(caps.energyMeter.energy({value=activeEnergyExportTotal, unit="kWh"})) + + -- Refresh Active Energy Net Negative + log.trace("refresh_energy_meter(): Refreshing Active Energy Net Negative, dni = " .. dni) + device.profile.components["surplus"]:emit_event(caps.energyMeter.energy({value=activeEnergyNetNegative, unit="kWh"})) + + -- Verify whether the appropriate time have elapsed to report the energy net, consumption and production + local last_energy_report = device:get_field(fields.LAST_ENERGY_REPORT) or 0.0 + + if (os.time() - last_energy_report) >= config.EDGE_CHILD_ENERGY_REPORT_INTERVAL then -- Report the energy net, consumption and production periodically + local current_net_positive_report = device:get_latest_state("main", caps.powerConsumptionReport.ID, caps.powerConsumptionReport.powerConsumption.NAME) + local current_consumption_report = device:get_latest_state("consumptionMeter", caps.powerConsumptionReport.ID, caps.powerConsumptionReport.powerConsumption.NAME) + local current_production_report = device:get_latest_state("productionMeter", caps.powerConsumptionReport.ID, caps.powerConsumptionReport.powerConsumption.NAME) + local current_net_negative_report = device:get_latest_state("surplus", caps.powerConsumptionReport.ID, caps.powerConsumptionReport.powerConsumption.NAME) + + -- Calculate delta net, consumption and production energy + local delta_net_positive_report = 0.0 + if current_net_positive_report ~= nil then + delta_net_positive_report = math.max((activeEnergyNetPositive * 1000) - current_net_positive_report.energy, 0.0) + end + + local delta_consumption_energy = 0.0 + if current_consumption_report ~= nil then + delta_consumption_energy = math.max((activeEnergyImportTotal * 1000) - current_consumption_report.energy, 0.0) + end + + local delta_production_energy = 0.0 + if current_production_report ~= nil then + delta_production_energy = math.max((activeEnergyExportTotal * 1000) - current_production_report.energy, 0.0) + end + + local delta_net_negative_report = 0.0 + if current_net_negative_report ~= nil then + delta_net_negative_report = math.max((activeEnergyNetNegative * 1000) - current_net_negative_report.energy, 0.0) + end + + -- Refresh Power Net Positive, Consumption, Production & Net Negative Report + log.trace("refresh_energy_meter(): Refreshing Power Net Positive, Consumption, Production & Net Negative Report, dni = " .. dni) + device.profile.components["main"]:emit_event(caps.powerConsumptionReport.powerConsumption({energy=activeEnergyNetPositive * 1000, deltaEnergy=delta_net_positive_report})) + device.profile.components["consumptionMeter"]:emit_event(caps.powerConsumptionReport.powerConsumption({energy=activeEnergyImportTotal * 1000, deltaEnergy=delta_consumption_energy})) + device.profile.components["productionMeter"]:emit_event(caps.powerConsumptionReport.powerConsumption({energy=activeEnergyExportTotal * 1000, deltaEnergy=delta_production_energy})) + device.profile.components["surplus"]:emit_event(caps.powerConsumptionReport.powerConsumption({energy=activeEnergyNetNegative * 1000, deltaEnergy=delta_net_negative_report})) + + -- Save date of the last consumption + local current_energy_report = last_energy_report + config.EDGE_CHILD_ENERGY_REPORT_INTERVAL + if (current_energy_report + config.EDGE_CHILD_ENERGY_REPORT_INTERVAL) < os.time() then + current_energy_report = os.time() + end + + device:set_field(fields.LAST_ENERGY_REPORT, current_energy_report, {persist=false}) + else + log.debug("refresh_energy_meter(): " .. config.EDGE_CHILD_ENERGY_REPORT_INTERVAL .. " seconds haven't elapsed yet! Last consumption was at " .. last_energy_report .. ", dni = " .. dni) + end + end + + return true +end + +local function refresh_auxiliary_contact(driver, device, values) + local dni = utils.get_dni_from_device(device) + log.info("refresh_auxiliary_contact(): Refreshing data of Auxiliary Contact, dni = " .. dni) + + -- Refresh Contact Sensor + local isClosed = values.isClosed + + if isClosed ~= nil then + log.trace("refresh_auxiliary_contact(): Refreshing Switch, dni = " .. dni) + + if isClosed == 1 then + isClosed = true + else + isClosed = false + end + + if isClosed then + device:emit_event(caps.switch.switch.on()) + else + device:emit_event(caps.switch.switch.off()) + end + end + + return true +end + +local function refresh_output_module(driver, device, values) + local dni = utils.get_dni_from_device(device) + log.info("refresh_output_module(): Refreshing data of Output Module, dni = " .. dni) + + -- Refresh Switch + local isClosed = values.isClosed + log.trace("refresh_output_module(): Refreshing Switch, dni = " .. dni) + + if isClosed == 1 then + isClosed = true + else + isClosed = false + end + + if isClosed then + device:emit_event(caps.switch.switch.on()) + else + device:emit_event(caps.switch.switch.off()) + end + + return true +end + +local function refresh_water_meter(driver, device, values) + local dni = utils.get_dni_from_device(device) + log.info("refresh_water_meter(): Refreshing data of Water Meter, dni = " .. dni) + + local unit = values.unit + if unit == nil then + log.error("refresh_water_meter(): The unit of the water meter is not set, dni = " .. dni) + return false + end + + -- Refresh Water Meter: last hour + local lastHourFlow = values.lastHourFlow + + if lastHourFlow ~= nil then + log.trace("refresh_water_meter(): Refreshing Water Meter: last hour, dni = " .. dni) + device:emit_event(caps.waterMeter.lastHour({value=lastHourFlow, unit=unit})) + end + + -- Refresh Water Meter: last 24 hours + local lastTwentyFourHoursFlow = values.lastTwentyFourHoursFlow + + if lastTwentyFourHoursFlow ~= nil then + log.trace("refresh_water_meter(): Refreshing Water Meter: last 24 hours, dni = " .. dni) + device:emit_event(caps.waterMeter.lastTwentyFourHours({value=lastTwentyFourHoursFlow, unit=unit})) + end + + -- Refresh Water Meter: last 7 days + local lastSevenDaysFlow = values.lastSevenDaysFlow + + if lastSevenDaysFlow ~= nil then + log.trace("refresh_water_meter(): Refreshing Water Meter: last 7 days, dni = " .. dni) + device:emit_event(caps.waterMeter.lastSevenDays({value=lastSevenDaysFlow, unit=unit})) + end + + return true +end + +local function refresh_gas_meter(driver, device, values) + local dni = utils.get_dni_from_device(device) + log.info("refresh_gas_meter(): Refreshing data of Gas Meter, dni = " .. dni) + + local gasMeterVolumeUnit = values.gasMeterVolumeUnit + if gasMeterVolumeUnit == nil then + log.error("refresh_gas_meter(): The unit of the gas meter is not set, dni = " .. dni) + return false + end + + -- Correct the unit if necessary + if gasMeterVolumeUnit == "m3" then + gasMeterVolumeUnit = "m^3" + end + + -- Refresh Gas Meter + local gasMeterVolume = values.gasMeterVolume + + if gasMeterVolume ~= nil then + log.trace("refresh_gas_meter(): Refreshing Gas Meter, dni = " .. dni) + device:emit_event(caps.gasMeter.gasMeterVolume({value=gasMeterVolume, unit=gasMeterVolumeUnit})) + end + + return true +end + +local function refresh_usb_energy_meter(driver, device, values) + local dni = utils.get_dni_from_device(device) + log.info("refresh_usb_energy_meter(): Refreshing data of USB Energy Meter, dni = " .. dni) + + -- Refresh Active Power Import Total + local activePowerImportTotal = values.activePowerImportTotal + + if activePowerImportTotal ~= nil then + log.trace("refresh_usb_energy_meter(): Refreshing Active Power Import Total, dni = " .. dni) + device:emit_event(caps.powerMeter.power({value=activePowerImportTotal, unit="W"})) + end + + -- Refresh Active Energy Import Total + local activeEnergyImportTotal = values.activeEnergyImportTotal + + if activeEnergyImportTotal ~= nil then + log.trace("refresh_usb_energy_meter(): Refreshing Active Energy Import Total, dni = " .. dni) + device:emit_event(caps.energyMeter.energy({value=activeEnergyImportTotal, unit="kWh"})) + + -- Verify whether the appropriate time have elapsed to report the energy consumption and production + local last_energy_report = device:get_field(fields.LAST_ENERGY_REPORT) or 0.0 + + if (os.time() - last_energy_report) >= config.EDGE_CHILD_ENERGY_REPORT_INTERVAL then -- Report the energy consumption periodically + local current_consumption_report = device:get_latest_state("main", caps.powerConsumptionReport.ID, caps.powerConsumptionReport.powerConsumption.NAME) + + -- Calculate delta consumption energy + local delta_consumption_energy = 0.0 + if current_consumption_report ~= nil then + delta_consumption_energy = math.max((activeEnergyImportTotal * 1000) - current_consumption_report.energy, 0.0) + end + + -- Refresh Power Consumption Report + log.trace("refresh_usb_energy_meter(): Refreshing Power Consumption Report, dni = " .. dni) + device:emit_event(caps.powerConsumptionReport.powerConsumption({energy=activeEnergyImportTotal * 1000, deltaEnergy=delta_consumption_energy})) + + -- Save date of the last consumption + local current_energy_report = last_energy_report + config.EDGE_CHILD_ENERGY_REPORT_INTERVAL + if (current_energy_report + config.EDGE_CHILD_ENERGY_REPORT_INTERVAL) < os.time() then + current_energy_report = os.time() + end + + device:set_field(fields.LAST_ENERGY_REPORT, current_energy_report, {persist=false}) + else + log.debug("refresh_usb_energy_meter(): " .. config.EDGE_CHILD_ENERGY_REPORT_INTERVAL .. " seconds haven't elapsed yet! Last consumption was at " .. last_energy_report .. ", dni = " .. dni) + end + end + + return true +end + +function device_refresher.refresh_device(driver, device, values) + local dni, device_type = utils.get_dni_from_device(device) + log.info("device_refresher.refresh_device(): Refreshing data of device, dni = " .. dni) + + if device_type == fields.DEVICE_TYPE_BRIDGE then + log.debug("device_refresher.refresh_device(): Cannot refresh bridge device, dni = " .. dni) + return + end + + log.debug("device_refresher.refresh_device(): Provided values: " .. utils.dump(values)) + + local refresh_methods = { + [utils.get_thing_exact_type(config.EDGE_CHILD_CURRENT_SENSOR_TYPE)] = refresh_current_sensor, + [utils.get_thing_exact_type(config.EDGE_CHILD_ENERGY_METER_MODULE_TYPE)] = refresh_energy_meter, + [utils.get_thing_exact_type(config.EDGE_CHILD_AUXILIARY_CONTACT_TYPE)] = refresh_auxiliary_contact, + [utils.get_thing_exact_type(config.EDGE_CHILD_OUTPUT_MODULE_TYPE)] = refresh_output_module, + [utils.get_thing_exact_type(config.EDGE_CHILD_ENERGY_METER_TYPE)] = refresh_energy_meter, + [utils.get_thing_exact_type(config.EDGE_CHILD_WATER_METER_TYPE)] = refresh_water_meter, + [utils.get_thing_exact_type(config.EDGE_CHILD_GAS_METER_TYPE)] = refresh_gas_meter, + [utils.get_thing_exact_type(config.EDGE_CHILD_USB_ENERGY_METER_TYPE)] = refresh_usb_energy_meter + } + + local device_model = utils.get_device_model(device) + if device_model == nil then + log.error("device_refresher.refresh_device(): No device model found for device, dni = " .. dni) + return + end + + local refresh_method = refresh_methods[device_model] + if refresh_method == nil then + log.error("device_refresher.refresh_device(): No refresh method found for device, dni = " .. dni .. ", model = " .. device_model) + return + end + + return refresh_method(driver, device, values) +end + +return device_refresher \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/commands.lua b/drivers/ABB/insite-scu200/src/commands.lua new file mode 100644 index 0000000000..453d0f521f --- /dev/null +++ b/drivers/ABB/insite-scu200/src/commands.lua @@ -0,0 +1,111 @@ +local caps = require('st.capabilities') +local log = require('log') +local json = require('dkjson') + +-- Local imports +local utils = require('utils') +local fields = require('fields') +local config = require("config") +local device_manager = require('abb.device_manager') +local connection_monitor = require('connection_monitor') + +-- Commands handler for the bridge and thing devices +local commands = {} + +-- Method for posting the payload to the device +local function post_payload(device, payload) + local dni = utils.get_dni_from_device(device) + local communication_device = device:get_parent_device() or device + local conn_info = communication_device:get_field(fields.CONN_INFO) + + local _, err, status = conn_info:post_device_by_id(dni, payload) + if not err and status == 200 then + log.info("post_payload(): Success, dni = " .. dni) + + return true + else + status = status or "nil" + log.error("post_payload(): Error, err = " .. err .. ", status = " .. status .. ", dni = " .. dni) + + device:offline() + + return false + end +end + +-- Switch on command +function commands.switch_on(driver, device, cmd) + local dni, _ = utils.get_dni_from_device(device) + log.info("commands.switch_on(): Switching on capablity within dni = " .. dni) + + local device_model = utils.get_device_model(device) + + local payload = nil + local event = nil + if device_model == utils.get_thing_exact_type(config.EDGE_CHILD_OUTPUT_MODULE_TYPE) then + payload = json.encode({capability = cmd.capability, command = cmd.command}) + event = caps.switch.switch.on() + end + + if payload ~= nil and event ~= nil then + local bridge = device:get_parent_device() + + bridge.thread:call_with_delay(0, function() -- Run within bridge thread to use the same connection + local success = post_payload(device, payload) + if success then + device:emit_event(event) + end + end) + end +end + +-- Switch off commands +function commands.switch_off(driver, device, cmd) + local dni, _ = utils.get_dni_from_device(device) + log.info("commands.switch_off(): Switching off capablity within dni = " .. dni) + + local device_model = utils.get_device_model(device) + + local payload = nil + local event = nil + if device_model == utils.get_thing_exact_type(config.EDGE_CHILD_OUTPUT_MODULE_TYPE) then + payload = json.encode({capability = cmd.capability, command = cmd.command}) + event = caps.switch.switch.off() + end + + if payload ~= nil and event ~= nil then + local bridge = device:get_parent_device() + + bridge.thread:call_with_delay(0, function() -- Run within bridge thread to use the same connection + local success = post_payload(device, payload) + if success then + device:emit_event(event) + end + end) + end +end + +-- Refresh command +function commands.refresh(driver, device, cmd) + local dni, device_type = utils.get_dni_from_device(device) + log.info("commands.refresh(): Refresh capability within dni = " .. dni) + + if device_type == fields.DEVICE_TYPE_BRIDGE then + connection_monitor.check_and_update_connection(driver, device) + local child_devices = device:get_child_list() + + for _, thing_device in ipairs(child_devices) do + device_manager.refresh(driver, thing_device) + end + elseif device_type == fields.DEVICE_TYPE_THING then + local bridge = device:get_parent_device() + + if bridge.thread ~= nil then + bridge.thread:call_with_delay(0, function() -- Run within bridge thread to use the same connection + device_manager.refresh(driver, device) + end) + end + end +end + +return commands \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/config.lua b/drivers/ABB/insite-scu200/src/config.lua new file mode 100644 index 0000000000..8470708d9e --- /dev/null +++ b/drivers/ABB/insite-scu200/src/config.lua @@ -0,0 +1,72 @@ +local config = {} + +-- Device Config +config.DEVICE_TYPE = "LAN" + +config.MANUFACTURER = "ABB" + +config.BRIDGE_PROFILE = "abb.scu200.bridge.v1" +config.BRIDGE_TYPE = "SCU200" +config.BRIDGE_VERSION = "1" + +config.BRIDGE_URN = "urn:" .. config.MANUFACTURER .. ":device:" .. config.BRIDGE_TYPE .. ":" .. config.BRIDGE_VERSION + +config.BRIDGE_CONN_MONITOR_INTERVAL = 300 -- 5 minutes + +-- Edge Child Config +config.EDGE_CHILD_TYPE = "EDGE_CHILD" + +config.EDGE_CHILD_CURRENT_SENSOR_TYPE = "CurrentSensor" +config.EDGE_CHILD_ENERGY_METER_MODULE_TYPE = "EnergyMeterModule" +config.EDGE_CHILD_AUXILIARY_CONTACT_TYPE = "AuxiliaryContact" +config.EDGE_CHILD_OUTPUT_MODULE_TYPE = "OutputModule" +config.EDGE_CHILD_ENERGY_METER_TYPE = "EnergyMeter" +config.EDGE_CHILD_WATER_METER_TYPE = "WaterMeter" +config.EDGE_CHILD_GAS_METER_TYPE = "GasMeter" +config.EDGE_CHILD_USB_ENERGY_METER_TYPE = "USBEnergyMeter" + +config.EDGE_CHILD_CURRENT_SENSOR_VERSION = 1 +config.EDGE_CHILD_ENERGY_METER_MODULE_VERSION = 1 +config.EDGE_CHILD_AUXILIARY_CONTACT_VERSION = 1 +config.EDGE_CHILD_OUTPUT_MODULE_VERSION = 1 +config.EDGE_CHILD_ENERGY_METER_VERSION = 1 +config.EDGE_CHILD_WATER_METER_VERSION = 1 +config.EDGE_CHILD_GAS_METER_VERSION = 1 +config.EDGE_CHILD_USB_ENERGY_METER_VERSION = 1 + +config.EDGE_CHILD_CURRENT_SENSOR_CONSUMPTION_PROFILE = "abb.scu200.current-sensor-consumption.v1" +config.EDGE_CHILD_CURRENT_SENSOR_PRODUCTION_PROFILE = "abb.scu200.current-sensor-production.v1" +config.EDGE_CHILD_AUXILIARY_CONTACT_PROFILE = "abb.scu200.auxiliary-contact.v1" +config.EDGE_CHILD_OUTPUT_MODULE_PROFILE = "abb.scu200.output-module.v1" +config.EDGE_CHILD_ENERGY_METER_PROFILE = "abb.scu200.energy-meter.v1" +config.EDGE_CHILD_WATER_METER_PROFILE = "abb.scu200.water-meter.v1" +config.EDGE_CHILD_GAS_METER_PROFILE = "abb.scu200.gas-meter.v1" +config.EDGE_CHILD_USB_ENERGY_METER_PROFILE = "abb.scu200.usb-energy-meter.v1" + +config.EDGE_CHILD_CURRENT_SENSOR_REFRESH_PERIOD = 30 +config.EDGE_CHILD_ENERGY_METER_MODULE_REFRESH_PERIOD = 30 +config.EDGE_CHILD_AUXILIARY_CONTACT_REFRESH_PERIOD = 300 -- 5 minutes +config.EDGE_CHILD_OUTPUT_MODULE_REFRESH_PERIOD = 300 -- 5 minutes +config.EDGE_CHILD_ENERGY_METER_REFRESH_PERIOD = 30 +config.EDGE_CHILD_WATER_METER_REFRESH_PERIOD = 300 -- 5 minutes +config.EDGE_CHILD_GAS_METER_REFRESH_PERIOD = 300 -- 5 minutes +config.EDGE_CHILD_USB_ENERGY_METER_REFRESH_PERIOD = 30 + +config.EDGE_CHILD_ENERGY_REPORT_INTERVAL = 900 -- 15 minutes + +-- REST API Config +config.REST_API_PORT = 1025 + +-- SSDP Config +config.MC_ADDRESS = "239.255.255.250" +config.MC_PORT = 1900 +config.MC_TIMEOUT = 5 +config.MSEARCH = table.concat({ + "M-SEARCH * HTTP/1.1", + "HOST: 239.255.255.250:1900", + "MAN: \"ssdp:discover\"", + "MX: 5", + "ST: " .. config.BRIDGE_URN +}, "\r\n") + +return config diff --git a/drivers/ABB/insite-scu200/src/connection_monitor.lua b/drivers/ABB/insite-scu200/src/connection_monitor.lua new file mode 100644 index 0000000000..bdeb2b9621 --- /dev/null +++ b/drivers/ABB/insite-scu200/src/connection_monitor.lua @@ -0,0 +1,71 @@ +local log = require("log") + +-- Local imports +local fields = require("fields") +local utils = require("utils") +local discovery = require("discovery") +local eventsource_handler = require("eventsource_handler") +local device_manager = require("abb.device_manager") + +-- Connection monitor for the SCU200 Bridge +local connection_monitor = {} + +function connection_monitor.update_connection(driver, device, bridge_ip) + local bridge_dni = utils.get_dni_from_device(device) + log.info("connection_monitor.update_connection(): Update connection for bridge device: " .. bridge_dni) + + local conn_info = device_manager.get_bridge_connection_info(driver, bridge_dni, bridge_ip) + + if device_manager.is_valid_connection(driver, device, conn_info) then + device:set_field(fields.CONN_INFO, conn_info) + eventsource_handler.create_sse(driver, device) + end +end + +local function find_new_connection(driver, device) + local dni = utils.get_dni_from_device(device) + log.info("find_new_connection(): Find new connection for dni = " .. dni) + + local found_devices = discovery.find_devices() + + if found_devices ~= nil then + local found_device = found_devices[dni] + + if found_device then + log.info("find_new_connection(): Found new connection for dni = " .. dni) + + local ip = found_device.ip + + device:set_field(fields.BRIDGE_IPV4, ip, {persist = true}) + connection_monitor.update_connection(driver, device, ip) + end + end +end + +function connection_monitor.check_and_update_connection(driver, device) + local dni = utils.get_dni_from_device(device) + local conn_info = device:get_field(fields.CONN_INFO) + + if not device_manager.is_valid_connection(driver, device, conn_info) then + log.error("connection_monitor.check_and_update_connection(): Disconnected from device. Try to find new connection for dni = " .. dni) + + find_new_connection(driver, device) + end +end + +-- Method for monitoring the connection of the bridge devices +function connection_monitor.monitor_connections(driver) + local device_list = driver:get_devices() + + for _, device in ipairs(device_list) do + if device:get_field(fields.DEVICE_TYPE) == fields.DEVICE_TYPE_BRIDGE then + local dni = utils.get_dni_from_device(device) + log.info("connection_monitor.monitor_connections(): Monitoring connection for bridge device: " .. dni) + + connection_monitor.check_and_update_connection(driver, device) + device_manager.bridge_monitor(driver, device) + end + end +end + +return connection_monitor \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/discovery.lua b/drivers/ABB/insite-scu200/src/discovery.lua new file mode 100644 index 0000000000..3958c48026 --- /dev/null +++ b/drivers/ABB/insite-scu200/src/discovery.lua @@ -0,0 +1,315 @@ +local log = require "log" +local socket = require('socket') +local cosock = require "cosock" + +-- Local imports +local api = require("abb.api") +local config = require("config") +local utils = require("utils") +local fields = require("fields") + +-- Discovery service run within SmartThings app +local discovery = {} + +local joined_bridge = {} +local joined_thing = {} + +-- Method for setting the device fields +function discovery.set_device_fields(driver, device) + local dni = utils.get_dni_from_device(device) + + if joined_bridge[dni] ~= nil then + log.info("discovery.set_device_field(): Setting device field for bridge: " .. dni) + local bridge_cache_value = driver.datastore.bridge_discovery_cache[dni] + + device:set_field(fields.BRIDGE_IPV4, bridge_cache_value.ip, {persist = true}) + device:set_field(fields.DEVICE_TYPE, fields.DEVICE_TYPE_BRIDGE, {persist = true}) + elseif joined_thing[dni] ~= nil then + log.info("discovery.set_device_field(): Setting device field for thing: " .. dni) + local thing_cache_value = driver.datastore.thing_discovery_cache[dni] + + device:set_field(fields.PARENT_BRIDGE_DNI, thing_cache_value.parent_bridge_dni, {persist = true}) + device:set_field(fields.THING_INFO, thing_cache_value.thing_info, {persist = true}) + device:set_field(fields.DEVICE_TYPE, fields.DEVICE_TYPE_THING, {persist = true}) + else + log.warn("discovery.set_device_field(): Could not set device field for unknown device: " .. dni) + end +end + +-- Method for updating the bridge discovery cache +local function update_bridge_discovery_cache(driver, dni, device) + log.info("update_bridge_discovery_cache(): Updating bridge discovery cache: " .. dni) + + driver.datastore.bridge_discovery_cache[dni] = { + ip = device["ip"] + } +end + +-- Method for updating the thing discovery cache +local function update_thing_discovery_cache(driver, thing_dni, parent_bridge_dni, thing_info) + log.info("update_thing_discovery_cache(): Updating thing discovery cache: " .. thing_dni) + + driver.datastore.thing_discovery_cache[thing_dni] = { + parent_bridge_dni = parent_bridge_dni, + thing_info = thing_info, + } +end + +-- Method for trying to add a new bridge +local function try_add_bridge(driver, dni, device) + log.info("try_add_bridge(): Trying to add bridge: " .. dni) + + local bridge_info = api.get_bridge_info(device["ip"], dni) + if bridge_info == nil then + log.error("try_add_bridge(): Failed to get bridge info for bridge: " .. dni) + return false + end + + update_bridge_discovery_cache(driver, dni, device) + + local metadata = { + type = config.DEVICE_TYPE, + device_network_id = dni, + label = bridge_info.name, + profile = config.BRIDGE_PROFILE, + manufacturer = config.MANUFACTURER, + model = config.BRIDGE_TYPE, + vendor_provided_label = config.BRIDGE_TYPE + } + + local success, err = driver:try_create_device(metadata) + + if success then + log.debug("try_add_bridge(): Bridge created: " .. dni) + + return true + else + log.error("try_add_bridge(): Failed to create bridge: " .. dni) + log.debug("try_add_bridge(): Error: " .. err) + + return false + end +end + +-- Method for trying to add a new thing +local function try_add_thing(driver, parent_device, thing_dni, thing_info) + local parent_device_dni = utils.get_dni_from_device(parent_device) + log.info("try_add_thing(): Trying to add thing: " .. thing_dni .. " of type: " .. thing_info.type .. " on bridge: " .. parent_device_dni) + + update_thing_discovery_cache(driver, thing_dni, parent_device_dni, thing_info) + + if thing_info.type == utils.get_thing_exact_type(config.EDGE_CHILD_WATER_METER_TYPE) or thing_info.type == utils.get_thing_exact_type(config.EDGE_CHILD_GAS_METER_TYPE) then + log.warn("try_add_thing(): Not supported thing type: " .. thing_info.type) + return false + elseif thing_info.type == utils.get_thing_exact_type(config.EDGE_CHILD_CURRENT_SENSOR_TYPE) and thing_info.properties.isExport then + log.warn("try_add_thing(): Current sensor with production data is not supported") + return false + end + + local profile_ref = utils.get_thing_profile_ref(thing_info) + if profile_ref == nil then + log.error("try_add_thing(): Failed to get profile reference for thing: " .. thing_dni) + return false + end + + local parent_device_id = utils.get_device_id_from_device(parent_device) + + local metadata = { + type = config.EDGE_CHILD_TYPE, + label = thing_info.name, + vendor_provided_label = thing_info.name, + profile = profile_ref, + manufacturer = config.MANUFACTURER, + model = thing_info.type, + parent_device_id = parent_device_id, + parent_assigned_child_key = thing_info.uuid, + } + + local success, err = driver:try_create_device(metadata) + + if success then + log.debug("try_add_thing(): Thing created: " .. thing_dni) + + return true + else + log.error("try_add_thing(): Failed to create thing: " .. thing_dni) + log.debug("try_add_thing(): Error: " .. err) + + return false + end +end + +-- SSDP Response parser +local function parse_ssdp(data) + local res = {} + + res.status = data:sub(0, data:find('\r\n')) + + for line in data:gmatch("[^\r\n]+") do + local _, _, header, value = string.find(line, "([%w-]+):%s*([%a+-:_ /=?]*)") + + if header ~= nil and value ~= nil then + res[header:lower()] = value + end + end + + return res +end + +-- Method for finding devices +function discovery.find_devices() + log.info("discovery.find_devices(): Finding devices") + + -- Initialize UDP socket + local upnp = cosock.socket.udp() + + upnp:setsockname('*', 0) + upnp:setoption("broadcast", true) + upnp:settimeout(config.MC_TIMEOUT) + + -- Broadcast M-SEARCH request + log.info("discovery.find_devices(): Scanning network...") + + upnp:sendto(config.MSEARCH, config.MC_ADDRESS, config.MC_PORT) + + -- Listen for responses + local devices = {} + local start_time = socket.gettime() + + while (socket.gettime() - start_time) < config.MC_TIMEOUT do + local res = upnp:receivefrom() + + if res ~= nil then + local device = parse_ssdp(res) + local dni = string.match(device["usn"], "^uuid:([a-zA-Z0-9-]+)::" .. config.BRIDGE_URN .. "$") + + if dni ~= nil then + local _, _, device_ip = string.find(device["location"], "https?://(%d+%.%d+%.%d+%.%d+):?%d*/?.*") + device["ip"] = device_ip + + devices[dni] = device + end + end + end + + -- Print found devices + if next(devices) then + for dni, device in pairs(devices) do + log.debug("discovery.find_devices(): Device found: " .. utils.dump(device)) + end + else + log.debug("discovery.find_devices(): No devices found") + end + + -- Close the UDP socket + upnp:close() + + log.debug("discovery.find_devices(): Stop scanning network") + + if devices ~= nil then + return devices + end + + return nil +end + +-- Start the discovery of bridges +local function discover_bridges(driver) + log.info("discover_bridges(): Discovering bridges") + + -- Get the known devices + local known_devices = {} + + for _, device in pairs(driver:get_devices()) do + local dni, device_type = utils.get_dni_from_device(device) + known_devices[dni] = device + + log.debug("discover_bridges(): Known devices: " .. dni .. " with type: " .. device_type) + end + + -- Find new devices + local found_devices = discovery.find_devices() + + if found_devices ~= nil then + for dni, device in pairs(found_devices) do + if not known_devices or not known_devices[dni] then + log.info("discover_bridges(): Found new bridge: " .. dni) + + if not joined_bridge[dni] then + if try_add_bridge(driver, dni, device) then + joined_bridge[dni] = true + end + else + log.debug("discover_bridges(): Bridge already joined: " .. dni) + end + else + log.debug("discover_bridges(): Bridge already added: " .. dni) + end + end + end +end + +-- Start the discovery of things +local function discover_things(driver) + log.info("discover_things(): Discovering things") + + -- Get the known devices + local known_devices = {} + + for _, device in pairs(driver:get_devices()) do + local dni, device_type = utils.get_dni_from_device(device) + known_devices[dni] = device + + log.debug("discover_things(): Known devices: " .. dni .. " with type: " .. device_type) + end + + -- Found new devices + for bridge_dni, bridge_cache_value in pairs(driver.datastore.bridge_discovery_cache) do + local bridge_ip = bridge_cache_value.ip + log.info("discover_things(): Fetching things from bridge: " .. bridge_dni .. " at IP: " .. bridge_ip) + + if known_devices[bridge_dni] ~= nil and known_devices[bridge_dni]:get_field(fields.CONN_INFO) ~= nil then + local thing_infos = api.get_thing_infos(bridge_ip, bridge_dni) + + if thing_infos and thing_infos.devices ~= nil then + for _, thing_info in pairs(thing_infos.devices) do + if thing_info ~= nil then + local thing_dni = thing_info.uuid + + log.info("discover_things(): Found thing: " .. thing_dni .. " on bridge: " .. bridge_dni) + + if thing_dni ~= nil then + if not known_devices[thing_dni] then + if try_add_thing(driver, known_devices[bridge_dni], thing_dni, thing_info) then + joined_thing[thing_dni] = true + end + elseif not joined_thing[thing_dni] then + log.debug("discover_things(): Thing already known: " .. thing_dni) + else + log.debug("discover_things(): Thing already joined: " .. thing_dni) + end + end + end + + cosock.socket.sleep(0.2) + end + end + end + end +end + +-- Main function to start the discovery service +function discovery.start(driver, _, should_continue) + log.info("discovery.start(): Starting discovery") + + while should_continue() do + discover_bridges(driver) + discover_things(driver) + + cosock.socket.sleep(0.2) + end + + log.info("discovery.start(): Ending discovery") +end + +return discovery \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/eventsource_handler.lua b/drivers/ABB/insite-scu200/src/eventsource_handler.lua new file mode 100644 index 0000000000..82e7131b87 --- /dev/null +++ b/drivers/ABB/insite-scu200/src/eventsource_handler.lua @@ -0,0 +1,128 @@ +local log = require('log') +local json = require('dkjson') + +-- Local imports +local EventSource = require "lunchbox.sse.eventsource" +local device_manager = require("abb.device_manager") +local device_refresher = require("abb.device_refresher") +local fields = require "fields" +local utils = require "utils" + +local eventsource_handler = {} + +-- Method for handling an incoming SSE event +function eventsource_handler.handle_sse_event(driver, bridge, msg) + log.debug("eventsource_handler.handle_sse_event(): Handling SSE event (TYPE: " .. msg.type .. ", DATA: " .. msg.data .. ")") + + if msg.type == "valueChanged" then + local data = json.decode(msg.data) + + if data ~= nil and next(data) ~= nil then + -- Find the device + local device = nil + + local child_devices = bridge:get_child_list() + for _, thing_device in ipairs(child_devices) do + local dni = utils.get_dni_from_device(thing_device) + + if dni == data.uuid then + device = thing_device + break + end + end + + if device == nil then + log.warn("eventsource_handler.handle_sse_event(): Failed to find the device with dni: " .. data.uuid) + return + end + + -- Prepare the values + local values = {} + + values[data.attribute.name] = data.attribute.value + + -- Refresh the device + if device_refresher.refresh_device(driver, device, values) then + -- Define online status + device:online() + else + log.error("eventsource_handler.handle_sse_event(): Failed to update the device's values") + + -- Set device as offline + device:offline() + end + else + log.error("eventsource_handler.handle_sse_event(): Failed to decode JSON data: " .. msg.data) + end + elseif msg.type == "noDevices" then + log.info("eventsource_handler.handle_sse_event(): No devices to monitor found") + + eventsource_handler.close_sse(driver, bridge) + elseif msg.type == "refreshConnection" then + log.info("eventsource_handler.handle_sse_event(): Refreshing connection") + + eventsource_handler.close_sse(driver, bridge) + eventsource_handler.create_sse(driver, bridge) + else + log.warn("eventsource_handler.handle_sse_event(): Unknown SSE event type: " .. msg.type) + end +end + +-- Method for creating SSE +function eventsource_handler.create_sse(driver, device) + local dni = utils.get_dni_from_device(device) + log.info("eventsource_handler.create_sse(): Creating SSE for dni: " .. dni) + + local conn_info = device:get_field(fields.CONN_INFO) + + if not device_manager.is_valid_connection(driver, device, conn_info) then + log.error("eventsource_handler.create_sse(): Invalid connection for dni: " .. dni) + return + end + + local sse_url = conn_info:get_sse_url() + if not sse_url then + log.error("eventsource_handler.create_sse(): Failed to get sse_url for dni: " .. dni) + return + end + + log.trace("eventsource_handler.create_sse(): Creating SSE EventSource for " .. dni .. " with sse_url: " .. sse_url) + local eventsource = EventSource.new(sse_url, {}, nil, nil) + + eventsource.onmessage = function(msg) + if msg then + eventsource_handler.handle_sse_event(driver, device, msg) + end + end + + eventsource.onerror = function() + log.error("eventsource_handler.create_sse(): Error in the eventsource for dni: " .. dni) + device:offline() + end + + eventsource.onopen = function(msg) + log.info("eventsource_handler.create_sse(): Eventsource has been opened for dni: " .. dni) + device:online() + end + + local old_eventsource = device:get_field(fields.EVENT_SOURCE) + if old_eventsource then + log.info("eventsource_handler.create_sse(): Eventsource has been closed for dni: " .. dni) + old_eventsource:close() + end + device:set_field(fields.EVENT_SOURCE, eventsource) +end + +-- Method for closing SSE +function eventsource_handler.close_sse(driver, device) + local dni = utils.get_dni_from_device(device) + log.info("eventsource_handler.close_sse(): Closing SSE for dni: " .. dni) + + local eventsource = device:get_field(fields.EVENT_SOURCE) + if eventsource then + log.info("eventsource_handler.close_sse(): Closing eventsource for device: " .. dni) + eventsource:close() + end +end + +return eventsource_handler diff --git a/drivers/ABB/insite-scu200/src/fields.lua b/drivers/ABB/insite-scu200/src/fields.lua new file mode 100644 index 0000000000..7c56019cc1 --- /dev/null +++ b/drivers/ABB/insite-scu200/src/fields.lua @@ -0,0 +1,16 @@ +-- Table of constants used to index in to device store fields +local fields = { + BRIDGE_IPV4 = "bridge_ipv4", + THING_INFO = "thing_info", + CONN_INFO = "conn_info", + PARENT_BRIDGE_DNI = "parent_bridge_dni", + EVENT_SOURCE = "eventsource", + DEVICE_TYPE = "devcie_type", + LAST_ENERGY_REPORT = "last_energy_report", + _INIT = "init", + + DEVICE_TYPE_BRIDGE = "bridge", + DEVICE_TYPE_THING = "thing" +} + +return fields \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/init.lua b/drivers/ABB/insite-scu200/src/init.lua new file mode 100644 index 0000000000..df33d18b1c --- /dev/null +++ b/drivers/ABB/insite-scu200/src/init.lua @@ -0,0 +1,45 @@ +local log = require('log') +local Driver = require('st.driver') +local caps = require('st.capabilities') + +-- Local imports +local discovery = require('discovery') +local commands = require('commands') +local config = require('config') +local lifecycles = require('lifecycles') +local connection_monitor = require('connection_monitor') + +-- Driver definition +local driver = Driver("ABB.SCU200", { + discovery = discovery.start, + lifecycle_handlers = lifecycles, + capability_handlers = { + -- Refresh command handler + [caps.refresh.ID] = { + [caps.refresh.commands.refresh.NAME] = commands.refresh + }, + [caps.switch.ID] = { + [caps.switch.commands.on.NAME] = commands.switch_on, + [caps.switch.commands.off.NAME] = commands.switch_off + } + } +}) + +-- Prepare datastores for bridge and thing discovery caches +if driver.datastore.bridge_discovery_cache == nil then + driver.datastore.bridge_discovery_cache = {} +end + +if driver.datastore.thing_discovery_cache == nil then + driver.datastore.thing_discovery_cache = {} +end + +-- Connection monitoring thread +driver:call_on_schedule(config.BRIDGE_CONN_MONITOR_INTERVAL, connection_monitor.monitor_connections, "SCU200 Bridge connection monitoring thread") + +-- Initialize driver +log.info("Starting driver") + +driver:run() + +log.warn("Exiting driver") \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/lifecycles.lua b/drivers/ABB/insite-scu200/src/lifecycles.lua new file mode 100644 index 0000000000..83cdfa0eb7 --- /dev/null +++ b/drivers/ABB/insite-scu200/src/lifecycles.lua @@ -0,0 +1,87 @@ +local log = require('log') + +-- Local imports +local fields = require('fields') +local utils = require('utils') +local discovery = require('discovery') +local commands = require('commands') +local connection_monitor = require('connection_monitor') +local eventsource_handler = require("eventsource_handler") + +-- Lifecycles handlers for the driver +local lifecycles = {} + +-- Lifecycle handler for a device which has been initialized +function lifecycles.init(driver, device) + local dni, device_type = utils.get_dni_from_device(device) + + -- Verify if the device has already been initialized + if device:get_field(fields._INIT) then + log.info("lifecycles.init(): Device already initialized: " .. dni .. " of type: " .. device_type) + return + end + + log.info("lifecycles.init(): Initializing device: " .. dni .. " of type: " .. device_type) + + if device_type == fields.DEVICE_TYPE_BRIDGE then + if driver.datastore.bridge_discovery_cache[dni] then + log.debug("lifecycles.init(): Setting unsaved bridge fields") + discovery.set_device_fields(driver, device) + end + + local bridge_ip = device:get_field(fields.BRIDGE_IPV4) + + connection_monitor.update_connection(driver, device, bridge_ip) + elseif device_type == fields.DEVICE_TYPE_THING then + if driver.datastore.thing_discovery_cache[dni] then + log.debug("lifecycles.init(): Setting unsaved thing fields") + discovery.set_device_fields(driver, device) + end + + -- Refresh the device manually + commands.refresh(driver, device, nil) + + -- Refresh schedule + local refresh_period = utils.get_thing_refresh_period(device) + + device.thread:call_on_schedule( + refresh_period, + function () + return commands.refresh(driver, device, nil) + end, + "Refresh schedule") + end + + -- Set the device as initialized + device:set_field(fields._INIT, true, {persist = false}) +end + +-- Lifecycle handler for a device which has been added +function lifecycles.added(driver, device) + local dni, device_type = utils.get_dni_from_device(device) + log.info("lifecycles.added(): Adding device: " .. dni .. " of type: " .. device_type) + + -- Force the initialization due to cases where the device is not initialized after being added + lifecycles.init(driver, device) +end + +-- Lifecycle handler for a device which has been removed +function lifecycles.removed(driver, device) + local dni, device_type = utils.get_dni_from_device(device) + log.info("lifecycles.removed(): Removing device: " .. dni .. " of type: " .. device_type) + + if device_type == fields.DEVICE_TYPE_BRIDGE then + log.debug("lifecycles.removed(): Closing SSE for device: " .. dni) + + eventsource_handler.close_sse(driver, device) + elseif device_type == fields.DEVICE_TYPE_THING then + log.debug("lifecycles.removed(): Removing schedules for device: " .. dni) + + -- Remove the schedules to avoid unnecessary CPU processing + for timer in pairs(device.thread.timers) do + device.thread:cancel_timer(timer) + end + end +end + +return lifecycles \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/lunchbox/init.lua b/drivers/ABB/insite-scu200/src/lunchbox/init.lua new file mode 100644 index 0000000000..d454f39e68 --- /dev/null +++ b/drivers/ABB/insite-scu200/src/lunchbox/init.lua @@ -0,0 +1,4 @@ +local RestClient = require "lunchbox.rest" +local EventSource = require "lunchbox.sse.eventsource" + +return {RestClient = RestClient, EventSource = EventSource} \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/lunchbox/rest.lua b/drivers/ABB/insite-scu200/src/lunchbox/rest.lua new file mode 100644 index 0000000000..b7c03525a6 --- /dev/null +++ b/drivers/ABB/insite-scu200/src/lunchbox/rest.lua @@ -0,0 +1,427 @@ +---@class ChunkedResponse : Response +---@field package _received_body boolean +---@field package _parsed_headers boolean +---@field public new fun(status_code: number, socket: table?): ChunkedResponse +---@field public fill_body fun(self: ChunkedResponse): string? +---@field public append_body fun(self: ChunkedResponse, next_chunk_body: string): ChunkedResponse + +local socket = require "cosock.socket" +local utils = require "utils" +local lb_utils = require "lunchbox.util" +local Request = require "luncheon.request" +local Response = require "luncheon.response" --[[@as ChunkedResponse]] +local json = require('dkjson') + +local api_version = require("version").api + +local RestCallStates = { + SEND = "Send", + RECEIVE = "Receive", + RETRY = "Retry", + RECONNECT = "Reconnect", + COMPLETE = "Complete", +} + +local function connect(client) + local port = 80 + local use_ssl = false + + if client.base_url.scheme == "https" then + port = 443 + use_ssl = true + end + + if client.base_url.port ~= port then port = client.base_url.port end + local sock, err = client.socket_builder(client.base_url.host, port, use_ssl) + + if sock == nil then + client.socket = nil + return false, err + end + + client.socket = sock + return true +end + +local function reconnect(client) + if client.socket ~= nil then + client.socket:close() + client.socket = nil + end + return connect(client) +end + +---comment +---@param client RestClient +---@param request Request +---@return integer? bytes_sent +---@return string? err_msg +---@return integer idx +local function send_request(client, request) + if client.socket == nil then + return nil, "no socket available", 0 + end + local payload = request:serialize() + + local bytes, err, idx = nil, nil, 0 + + repeat bytes, err, idx = client.socket:send(payload, idx + 1, #payload) until (bytes == #payload) + or (err ~= nil) + + return bytes, err, idx +end + +local function parse_chunked_response(original_response, sock) + local ChunkedTransferStates = { + EXPECTING_CHUNK_LENGTH = "ExpectingChunkLength", + EXPECTING_BODY_CHUNK = "ExpectingBodyChunk", + } + + local full_response = Response.new(original_response.status, nil) --[[@as ChunkedResponse]] + + for header in original_response.headers:iter() do full_response.headers:append_chunk(header) end + + local original_body, err = original_response:get_body() + if type(original_body) ~= "string" or err ~= nil then + return original_body, (err or "unexpected nil in error position") + end + local next_chunk_bytes = tonumber(original_body, 16) + local next_chunk_body = "" + local bytes_read = 0; + + local state = ChunkedTransferStates.EXPECTING_BODY_CHUNK + + repeat + local pat = nil + local next_recv, next_err, partial = nil, nil, nil + + if state == ChunkedTransferStates.EXPECTING_BODY_CHUNK then + pat = next_chunk_bytes + else + pat = "*l" + end + + next_recv, next_err, partial = sock:receive(pat) + + if next_err ~= nil then + if string.lower(next_err) == "closed" then + if partial ~= nil and #partial >= 1 then + full_response:append_body(partial) + next_chunk_bytes = 0 + else + return nil, next_err + end + else + return nil, ("unexpected error reading chunked transfer: " .. next_err) + end + end + + if next_recv ~= nil and #next_recv >= 1 then + if state == ChunkedTransferStates.EXPECTING_BODY_CHUNK then + bytes_read = bytes_read + #next_recv + next_chunk_body = next_chunk_body .. next_recv + + if bytes_read >= next_chunk_bytes then + full_response = full_response:append_body(next_chunk_body) + next_chunk_body = "" + bytes_read = 0 + + state = ChunkedTransferStates.EXPECTING_CHUNK_LENGTH + end + elseif state == ChunkedTransferStates.EXPECTING_CHUNK_LENGTH then + next_chunk_bytes = tonumber(next_recv, 16) + + state = ChunkedTransferStates.EXPECTING_BODY_CHUNK + end + end + until next_chunk_bytes == 0 + + local _ = sock:receive("*l") -- clear the trailing CRLF + + full_response._received_body = true + full_response._parsed_headers = true + + return full_response +end + +local function recv_additional_response(original_response, sock) + local full_response = Response.new(original_response.status, nil) + local headers = original_response:get_headers() + local content_length_str = headers:get_one("Content-Length") + local content_length = nil + local bytes_read = 0 + if content_length_str then + content_length = math.tointeger(content_length_str) + end + + local next_recv, next_err, partial + + repeat + next_recv, next_err, partial = sock:receive(content_length - bytes_read) + + if next_recv ~= nil and #next_recv >= 1 then + full_response:append_body(next_recv) + bytes_read = bytes_read + #next_recv + end + + if partial ~= nil and #partial >= 1 then + full_response:append_body(partial) + bytes_read = bytes_read + #partial + end + until next_err == "closed" or bytes_read >= content_length + + full_response._received_body = true + full_response._parsed_headers = true + + return full_response +end + +local function handle_response(sock) + if api_version >= 9 then + local response, err = Response.tcp_source(sock) + if err or (not response) then return response, (err or "unknown error") end + return response, response:fill_body() + end + -- called select right before passing in so we receive immediately + local initial_recv, initial_err, partial = Response.source(function() return sock:receive('*l') end) + + local full_response = nil + + if initial_recv ~= nil then + local headers = initial_recv:get_headers() + + if headers:get_one("Content-Length") then + full_response = recv_additional_response(initial_recv, sock) + elseif headers and headers:get_one("Transfer-Encoding") == "chunked" then + local response, err = parse_chunked_response(initial_recv, sock) + if err ~= nil then + return nil, err + end + full_response = response + else + full_response = initial_recv + end + + return full_response + else + return nil, initial_err, partial + end +end + +local function execute_request(client, request, retry_fn) + if not client._active then + return nil, "Called `execute request` on a terminated REST Client", nil + end + + if client.socket == nil then + local success, err = connect(client) + if not success then return nil, err, nil end + end + + local should_retry = retry_fn + + if type(should_retry) ~= "function" then + should_retry = function() return false end + end + + -- send output + local _bytes_sent, send_err, _idx = nil, nil, 0 + -- recv output + local response, recv_err, partial = nil, nil, nil + -- return values + local ret, err = nil, nil + + local backoff = utils.backoff_builder(60, 1, 0.1) + local current_state = RestCallStates.SEND + + repeat + local retry = should_retry() + if current_state == RestCallStates.SEND then + backoff = utils.backoff_builder(60, 1, 0.1) + _bytes_sent, send_err, _idx = send_request(client, request) + + if not send_err then + current_state = RestCallStates.RECEIVE + elseif retry then + if string.lower(send_err) == "closed" or string.lower(send_err):match("broken pipe") then + current_state = RestCallStates.RECONNECT + else + current_state = RestCallStates.RETRY + end + else + ret = nil + err = send_err + current_state = RestCallStates.COMPLETE + end + elseif current_state == RestCallStates.RECEIVE then + response, recv_err, partial = handle_response(client.socket) + + if not recv_err then + ret = response + err = nil + current_state = RestCallStates.COMPLETE + elseif retry then + if string.lower(recv_err) == "closed" or string.lower(recv_err):match("broken pipe") then + current_state = RestCallStates.RECONNECT + else + current_state = RestCallStates.RETRY + end + else + ret = nil + err = recv_err + current_state = RestCallStates.COMPLETE + end + elseif current_state == RestCallStates.RECONNECT then + local success, reconn_err = reconnect(client) + if success then + current_state = RestCallStates.RETRY + elseif not retry then + ret = nil + err = reconn_err + current_state = RestCallStates.COMPLETE + else + socket.sleep(backoff()) + end + elseif current_state == RestCallStates.RETRY then + bytes_sent, send_err, _idx = nil, nil, 0 + response, recv_err, partial = nil, nil, nil + current_state = RestCallStates.SEND + socket.sleep(backoff()) + end + until current_state == RestCallStates.COMPLETE + + return ret, err, partial +end + +---@class RestClient +--- +---@field base_url table `net.url` URL table +---@field socket table `cosock` TCP socket +local RestClient = {} +RestClient.__index = RestClient + +function RestClient.one_shot_get(full_url, additional_headers, socket_builder) + local url_table = lb_utils.force_url_table(full_url) + + local query_params = "" + if url_table.query ~= nil then + query_params = "?" + + for param, value in pairs(url_table.query) do + query_params = query_params .. param .. "=" .. value .. "&" + end + + query_params = query_params:sub(1, -2) + end + + local client = RestClient.new(url_table.scheme .. "://" .. url_table.authority, socket_builder) + local ret, err = client:get(url_table.path .. query_params, additional_headers) + + client:shutdown() + + return ret, err +end + +function RestClient.one_shot_post(full_url, body, additional_headers, socket_builder) + local url_table = lb_utils.force_url_table(full_url) + + local query_params = "" + if url_table.query ~= nil then + query_params = "?" + + for param, value in pairs(url_table.query) do + query_params = query_params .. param .. "=" .. value .. "&" + end + + query_params = query_params:sub(1, -2) + end + + if type(body) == "table" then + body = json.encode(body) + end + + local client = RestClient.new(url_table.scheme .. "://" .. url_table.authority, socket_builder) + local ret, err = client:post(url_table.path .. query_params, body, additional_headers) + + client:shutdown() + + return ret, err +end + +function RestClient:close_socket() + if self.socket ~= nil and self._active then + self.socket:close() + self.socket = nil + end +end + +function RestClient:shutdown() + self:close_socket() + self._active = false +end + +function RestClient:update_base_url(new_url) + if self.socket ~= nil then + self.socket:close() + self.socket = nil + end + + self.base_url = lb_utils.force_url_table(new_url) +end + +function RestClient:get(path, additional_headers, retry_fn) + local request = Request.new("GET", path, nil):add_header( + "user-agent", "smartthings-lua-edge-driver" + ):add_header("host", string.format("%s", self.base_url.host)):add_header( + "connection", "keep-alive" + ) + + if additional_headers ~= nil and type(additional_headers) == "table" then + for k, v in pairs(additional_headers) do request = request:add_header(k, v) end + end + + return execute_request(self, request, retry_fn) +end + +function RestClient:post(path, body_string, additional_headers, retry_fn) + local request = Request.new("POST", path, nil):add_header( + "user-agent", "smartthings-lua-edge-driver" + ):add_header("host", string.format("%s", self.base_url.host)):add_header( + "connection", "keep-alive" + ) + + if additional_headers ~= nil and type(additional_headers) == "table" then + for k, v in pairs(additional_headers) do request = request:add_header(k, v) end + end + + request = request:append_body(body_string) + + return execute_request(self, request, retry_fn) +end + +function RestClient:put(path, body_string, additional_headers, retry_fn) + local request = Request.new("PUT", path, nil):add_header( + "user-agent", "smartthings-lua-edge-driver" + ):add_header("host", string.format("%s", self.base_url.host)):add_header( + "connection", "keep-alive" + ) + + if additional_headers ~= nil and type(additional_headers) == "table" then + for k, v in pairs(additional_headers) do request = request:add_header(k, v) end + end + + request = request:append_body(body_string) + + return execute_request(self, request, retry_fn) +end + +function RestClient.new(base_url, sock_builder) + base_url = lb_utils.force_url_table(base_url) + + if type(sock_builder) ~= "function" then sock_builder = utils.labeled_socket_builder() end + + return + setmetatable({base_url = base_url, socket_builder = sock_builder, socket = nil, _active = true}, RestClient) +end + +return RestClient \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/lunchbox/sse/eventsource.lua b/drivers/ABB/insite-scu200/src/lunchbox/sse/eventsource.lua new file mode 100644 index 0000000000..d9a5930df2 --- /dev/null +++ b/drivers/ABB/insite-scu200/src/lunchbox/sse/eventsource.lua @@ -0,0 +1,523 @@ +local cosock = require "cosock" +local socket = require "cosock.socket" +local ssl = require "cosock.ssl" +local json = require "dkjson" + +local log = require "log" +local util = require "lunchbox.util" +local Request = require "luncheon.request" +local Response = require "luncheon.response" + +--- A pure Lua implementation of the EventSource interface. +--- The EventSource interface represents the client end of an HTTP(S) +--- connection that receives an event stream following the Server-Sent events +--- specification. +--- +--- MDN Documentation for EventSource: https://developer.mozilla.org/en-US/docs/Web/API/EventSource +--- HTML Spec: https://html.spec.whatwg.org/multipage/server-sent-events.html +--- +--- @class EventSource +--- @field public url table A `net.url` table representing the URL for the connection +--- @field public ready_state number Enumeration of the ready states outlined in the spec. +--- @field public onopen function in-line callback for on-open events +--- @field public onmessage function in-line callback for on-message events +--- @field public onerror function in-line callback for on-error events; error callbacks will fire +--- @field package _reconnect boolean flag that says whether or not the client should attempt to reconnect on close. +--- @field package _reconnect_time_millis number The amount of time to wait between reconnects, in millis. Can be sent by the server. +--- @field package _sock_builder function|nil optional. If this function exists, it will be called to create a new TCP socket on connection. +--- @field package _sock table? the TCP socket for the connection +--- @field package _needs_more boolean flag to track whether or not we're still expecting mroe on this source before we dispatch +--- @field package _last_field string the last field the parsing path saw, in case it needs to append more to its value +--- @field package _extra_headers table a table of string:string key-value pairs that will be inserted in to the initial requests's headers. +--- @field package _parse_buffers table inner state, keeps track of the various event stream buffers in between dispatches. +--- @field package _listeners table event listeners attached using the add_event_listener API instead of the inline callbacks. +local EventSource = {} +EventSource.__index = EventSource + +--- The Ready States that an EventSource can be in. We use base 0 to match the specification. +EventSource.ReadyStates = util.read_only { + CONNECTING = 0, -- The connection has not yet been established + OPEN = 1, -- The connection is open + CLOSED = 2 -- The connection has closed +} + +--- The event types supported by this source, patterned after their values in JavaScript. +EventSource.EventTypes = util.read_only { + ON_OPEN = "open", + ON_MESSAGE = "message", + ON_ERROR = "error", +} + +--- Helper function that creates the initial Request to start the stream. +--- @function create_request +--- @local +--- @param url_table table a net.url table +--- @param extra_headers table a set of key/value pairs (strings) to capture any extra HTTP headers needed +--- @param body table a set of key/value pairs (strings) to be sent as the body of the initial POST request. +local function create_request(url_table, extra_headers, body) + local request = Request.new("POST", url_table.path, nil) + :add_header("user-agent", "smartthings-lua-edge-driver") + :add_header("host", string.format("%s", url_table.host)) + :add_header("connection", "keep-alive") + :add_header("accept", "text/event-stream") + :add_header("content-type", "application/json") + + if type(extra_headers) == "table" then + for k, v in pairs(extra_headers) do + request = request:add_header(k, v) + end + end + + local encoded_body = json.encode(body) + request = request:append_body(encoded_body) + + return request +end + +--- Helper function to send the request and kick off the stream. +--- @function send_stream_start_request +--- @local +--- @param payload string the entire string buffer to send +--- @param sock table the TCP socket to send it over +local function send_stream_start_request(payload, sock) + local bytes, err, idx = nil, nil, 0 + + repeat + bytes, err, idx = sock:send(payload, idx + 1, #payload) + until (bytes == #payload) or (err ~= nil) + + if err then + log.error_with({ hub_logs = true }, "send error: " .. err) + end + + return bytes, err, idx +end + +--- Helper function to create an table representing an event from the source's parse buffers. +--- @function make_event +--- @local +--- @param source EventSource +local function make_event(source) + local event_type = nil + + if #source._parse_buffers["event"] > 0 then + event_type = source._parse_buffers["event"] + end + + return { + type = event_type or "message", + data = source._parse_buffers["data"], + origin = source.url.scheme .. "://" .. source.url.host, + lastEventId = source._parse_buffers["id"] + } +end + +--- SSE spec for dispatching an event: +--- https://html.spec.whatwg.org/multipage/server-sent-events.html#dispatchMessage +--- @function dispatch_event +--- @local +--- @param source EventSource +local function dispatch_event(source) + local data_buffer = source._parse_buffers["data"] + local is_blank_line = data_buffer ~= nil and + (#data_buffer == 0) or + data_buffer == "\n" or + data_buffer == "\r" or + data_buffer == "\r\n" + if data_buffer ~= nil and not is_blank_line then + local event = util.read_only(make_event(source)) + + if type(source.onmessage) == "function" then + source.onmessage(event) + end + + for _, listener in ipairs(source._listeners[EventSource.EventTypes.ON_MESSAGE]) do + if type(listener) == "function" then + listener(event) + end + end + end + + source._parse_buffers["event"] = "" + source._parse_buffers["data"] = "" +end + +local valid_fields = util.read_only { + ["event"] = true, + ["data"] = true, + ["id"] = true, + ["retry"] = true +} + +-- An event stream "line" can end in more than one way; from the spec: +-- Lines must be separated by either +-- a U+000D CARRIAGE RETURN U+000A LINE FEED (CRLF) character pair, +-- a single U+000A LINE FEED (LF) character, +-- or a single U+000D CARRIAGE RETURN (CR) character. +-- +-- util.iter_string_lines won't suffice here because: +-- a.) it assumes \n, and +-- b.) it doesn't differentiate between a "line" that ends without a newline and one that does. +-- +-- h/t to github.com/FreeMasen for the suggestions on the efficient implementation of this +local function find_line_endings(chunk) + local r_idx, n_idx = string.find(chunk, "[\r\n]+") + if r_idx == nil or r_idx == n_idx then + -- 1 character or no match + return r_idx, n_idx + end + local slice = string.sub(chunk, r_idx, n_idx) + if slice == "\r\n" then + return r_idx, n_idx + end + -- invalid multi character match, return first character only + return r_idx, r_idx +end + +local function event_lines(chunk) + local remaining = chunk + local line_end, rn_end + local remainder_sent = false + return function() + line_end, rn_end = find_line_endings(remaining) + if not line_end then + if remainder_sent or (not remaining) or #remaining == 0 then + return nil + else + remainder_sent = true + return remaining, false + end + end + local next_line = string.sub(remaining, 1, line_end - 1) + remaining = string.sub(remaining, rn_end + 1) + return next_line, true + end +end +--- SSE spec for interpreting an event stream: +--- https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface +--- @function parse +--- @local +--- @param source EventSource +--- @param recv string the received payload from the last socket receive +local function sse_parse_chunk(source, recv) + for line, complete in event_lines(recv) do + if not source._needs_more and (#line == 0 or (not line:match("([%w%p]+)"))) then -- empty/blank lines indicate dispatch + dispatch_event(source) + elseif source._needs_more then + local append = line + if source._last_field == "data" and complete then append = append .. "\n" end + if complete then source._needs_more = false end + source._parse_buffers[source._last_field] = source._parse_buffers[source._last_field] .. append + else + if line:sub(1, 1) ~= ":" then -- ignore any complete lines that start w/ a colon + local matches = line:gmatch("(%w*)(:*)(.*)") -- colon after field is optional, in that case it's a field w/ no value + + for field, _colon, value in matches do + value = value:gsub("^[^%g]", "", 1) -- trim a single leading space character + + if valid_fields[field] then + source._last_field = field + if field == "retry" then + local new_time = tonumber(value, 10) + if type(new_time) == "number" then + source._reconnect_time_millis = new_time + end + elseif field == "data" then + local append = (value or "") + -- if complete then append = append .. "\n" end + source._parse_buffers[field] = source._parse_buffers[field] .. append + elseif field == "id" then + -- skip ID's if they contain the NULL character + if not string.find(value, '\0') then + source._parse_buffers[field] = value + end + else + source._parse_buffers[field] = value + end + end + source._needs_more = source._needs_more or (not complete) + end + end + end + end +end + +--- Helper function that captures the cyclic logic of the EventSource while in the CONNECTING state. +--- @function connecting_action +--- @local +--- @param source EventSource +local function connecting_action(source) + if not source._sock then + if type(source._sock_builder) == "function" then + source._sock = source._sock_builder() + else + source._sock, err = socket.tcp() + if err ~= nil then return nil, err end + + _, err = source._sock:settimeout(60) + if err ~= nil then return nil, err end + + _, err = source._sock:connect(source.url.host, source.url.port) + if err ~= nil then return nil, err end + + _, err = source._sock:setoption("keepalive", true) + if err ~= nil then return nil, err end + + if source.url.scheme == "https" then + source._sock, err = ssl.wrap(source._sock, { + mode = "client", + protocol = "any", + verify = "none", + options = "all" + }) + if err ~= nil then return nil, err end + + _, err = source._sock:dohandshake() + if err ~= nil then return nil, err end + end + end + end + + local request = create_request(source.url, source._extra_headers, source._body) + + local last_event_id = source._parse_buffers["id"] + + if last_event_id ~= nil and #last_event_id > 0 then + request = request:add_header("Last-Event-ID", last_event_id) + end + + local _, err, _ = send_stream_start_request(request:serialize(), source._sock) + + if err ~= nil then + return nil, err + end + + local response + response, err = Response.tcp_source(source._sock) + + if not response or err ~= nil then + return nil, err or "nil response from Response.tcp_source" + end + + if response.status ~= 200 then + return nil, "Server responded with status other than 200 OK", { response.status, response.status_msg } + end + + local headers, err = response:get_headers() + if err ~= nil then + return nil, err + end + local content_type = string.lower((headers and headers:get_one('content-type') or "none")) + if not content_type:find("text/event-stream", 1, true) then + local err_msg = "Expected content type of text/event-stream in response headers, received: " .. content_type + return nil, err_msg + end + + source.ready_state = EventSource.ReadyStates.OPEN + + if type(source.onopen) == "function" then + source.onopen() + end + + for _, listener in ipairs(source._listeners[EventSource.EventTypes.ON_OPEN]) do + if type(listener) == "function" then + listener() + end + end +end +--- Helper function that captures the cyclic logic of the EventSource while in the OPEN state. +--- @function open_action +--- @local +--- @param source EventSource +local function open_action(source) + local recv, err, partial = source._sock:receive('*l') + + if err then + --- connection is fine but there was nothing + --- to be read from the other end so we just + --- early return. + if err == "timeout" or err == "wantread" then + return + else + --- real error, close the connection. + source._sock:close() + source._sock = nil + source.ready_state = EventSource.ReadyStates.CLOSED + return nil, err, partial + end + end + + -- the number of bytes to read per the chunked encoding spec + local recv_as_num = tonumber(recv, 16) + + if recv_as_num ~= nil and recv_as_num == 0 then + return -- the stream has ended + end + + if recv_as_num ~= nil then + recv, err, partial = source._sock:receive(recv_as_num) + if err then + if err == "timeout" or err == "wantread" then + return + else + --- real error, close the connection. + source._sock:close() + source._sock = nil + source.ready_state = EventSource.ReadyStates.CLOSED + return nil, err, partial + end + end + + local _, err, partial = source._sock:receive('*l') -- clear the final line + + if err then + if err == "timeout" or err == "wantread" then + return + else + --- real error, close the connection. + source._sock:close() + source._sock = nil + source.ready_state = EventSource.ReadyStates.CLOSED + return nil, err, partial + end + end + sse_parse_chunk(source, recv) + else + local recv_dbg = recv or "" + if #recv_dbg == 0 then + recv_dbg = "" + end + recv_dbg = recv_dbg:gsub("\r\n", ""):gsub("\n", ""):gsub("\r", "") + log.error_with({ hub_logs = true }, string.format("Received %s while expecting a chunked encoding payload length (hex number)\n", recv_dbg)) + end +end + +--- Helper function that captures the cyclic logic of the EventSource while in the CLOSED state. +--- @function closed_action +--- @local +--- @param source EventSource +local function closed_action(source) + if source._sock ~= nil then + source._sock:close() + source._sock = nil + end + + if source._reconnect then + if type(source.onerror) == "function" then + source.onerror() + end + + for _, listener in ipairs(source._listeners[EventSource.EventTypes.ON_ERROR]) do + if type(listener) == "function" then + listener() + end + end + + local sleep_time_secs = source._reconnect_time_millis / 1000.0 + socket.sleep(sleep_time_secs) + + source.ready_state = EventSource.ReadyStates.CONNECTING + end +end + +local state_actions = { + [EventSource.ReadyStates.CONNECTING] = connecting_action, + [EventSource.ReadyStates.OPEN] = open_action, + [EventSource.ReadyStates.CLOSED] = closed_action +} + +--- Create a new EventSource. The only required parameter is the URL, which can +--- be a string or a net.url table. The string form will be converted to a net.url table. +--- +--- @param url string|table a string or a net.url table representing the complete URL (minimally a scheme/host/path, port optional) for the event stream. +--- @param extra_headers table|nil an optional table of key-value pairs (strings) to be added to the initial POST request +--- @param body table|nil an optional table of key-value pairs (strings) to be sent as the body of the initial POST request +--- @param sock_builder function|nil an optional function to be used to create the TCP socket for the stream. If nil, a set of defaults will be used to create a new TCP socket. +--- @return EventSource a new EventSource +function EventSource.new(url, extra_headers, body, sock_builder) + local url_table = util.force_url_table(url) + + if not url_table.port then + if url_table.scheme == "http" then + url_table.port = 80 + elseif url_table.scheme == "https" then + url_table.port = 443 + end + end + + local sock = nil + + if type(sock_builder) == "function" then + sock = sock_builder() + end + + local source = setmetatable({ + url = url_table, + ready_state = EventSource.ReadyStates.CONNECTING, + onopen = nil, + onmessage = nil, + onerror = nil, + _needs_more = false, + _last_field = nil, + _reconnect = true, + _reconnect_time_millis = 15 * 1000, + _sock_builder = sock_builder, + _sock = sock, + _extra_headers = extra_headers, + _body = body, + _parse_buffers = { + ["data"] = "", + ["id"] = "", + ["event"] = "", + }, + _listeners = { + [EventSource.EventTypes.ON_OPEN] = {}, + [EventSource.EventTypes.ON_MESSAGE] = {}, + [EventSource.EventTypes.ON_ERROR] = {} + }, + }, EventSource) + + cosock.spawn(function() + local st_utils = require "st.utils" + while true do + if source.ready_state == EventSource.ReadyStates.CLOSED and not source._reconnect then + return + end + local _, action_err, partial = state_actions[source.ready_state](source) + if action_err ~= nil then + if action_err ~= "timeout" or action_err ~= "wantread" then + log.error_with({ hub_logs = true }, "Event Source Coroutine State Machine error: " .. action_err) + if partial ~= nil and #partial > 0 then + log.error_with({ hub_logs = true }, st_utils.stringify_table(partial, "\tReceived Partial", true)) + end + source.ready_state = EventSource.ReadyStates.CLOSED + end + end + end + end) + + return source +end + +--- Close the event source, signalling that a reconnect is not desired +function EventSource:close() + self._reconnect = false + if self._sock ~= nil then + self._sock:close() + end + self._sock = nil + self.ready_state = EventSource.ReadyStates.CLOSED +end + +--- Add a callback to the event source +---@param listener_type string One of "message", "open", or "error" +---@param listener function the callback to be called in case of an event. Open and Error events have no payload. The message event will have a single argument, a table. +function EventSource:add_event_listener(listener_type, listener) + local list = self._listeners[listener_type] + + if list then + table.insert(list, listener) + end +end + +return EventSource \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/lunchbox/util.lua b/drivers/ABB/insite-scu200/src/lunchbox/util.lua new file mode 100644 index 0000000000..6eb94407eb --- /dev/null +++ b/drivers/ABB/insite-scu200/src/lunchbox/util.lua @@ -0,0 +1,46 @@ +local net_url = require "net.url" + +local util = {} + +util.force_url_table = function(url) + if type(url) ~= "table" then url = net_url.parse(url) end + + if not url.port then + if url.scheme == "http" then + url.port = 80 + elseif url.scheme == "https" then + url.port = 443 + end + end + + return url +end + +util.read_only = function(tbl) + if type(tbl) == "table" then + local proxy = {} + local mt = { -- create metatable + __index = tbl, + __newindex = function(t, k, v) error("attempt to update a read-only table", 2) end, + } + setmetatable(proxy, mt) + return proxy + else + return tbl + end +end + +util.iter_string_lines = function(str) + if str:sub(-1) ~= "\n" then str = str .. "\n" end + + return str:gmatch("(.-)\n") +end + +util.copy_data = function(tbl) + local ret = {} + for k, v in pairs(tbl) do ret[k] = v end + + return ret +end + +return util \ No newline at end of file diff --git a/drivers/ABB/insite-scu200/src/utils.lua b/drivers/ABB/insite-scu200/src/utils.lua new file mode 100644 index 0000000000..139b5503c5 --- /dev/null +++ b/drivers/ABB/insite-scu200/src/utils.lua @@ -0,0 +1,219 @@ +local log = require("log") +local socket = require "cosock.socket" +local ssl = require "cosock.ssl" + +-- Local imports +local config = require("config") +local fields = require("fields") + +-- Utility functions for the SmartThings edge driver +local utils = {} + +-- Get the device id from the device +function utils.get_dni_from_device(device) + if device.parent_assigned_child_key then + local thing_dni = device.parent_assigned_child_key + + return thing_dni, fields.DEVICE_TYPE_THING + else + local bridge_dni = device.device_network_id + + return bridge_dni, fields.DEVICE_TYPE_BRIDGE + end +end + +-- Get the device ID +function utils.get_device_id_from_device(device) + return device.st_store.id +end + +-- Get the device model +function utils.get_device_model(device) + local thing_info = device:get_field(fields.THING_INFO) + + if thing_info == nil then + return nil + end + + return thing_info.type +end + +-- Get the device IP address +function utils.get_device_ip_address(device) + local _, device_type = utils.get_dni_from_device(device) + + if device_type == fields.DEVICE_TYPE_BRIDGE then + return device:get_field(fields.BRIDGE_IPV4) + else + local bridge = device:get_parent_device() + + return bridge:get_field(fields.BRIDGE_IPV4) + end +end + +-- Method for getting edge child device version by type +function utils.get_edge_child_device_version(edge_child_device_type) + local edge_child_device_versions = { + [config.EDGE_CHILD_CURRENT_SENSOR_TYPE] = config.EDGE_CHILD_CURRENT_SENSOR_VERSION, + [config.EDGE_CHILD_ENERGY_METER_MODULE_TYPE] = config.EDGE_CHILD_ENERGY_METER_MODULE_VERSION, + [config.EDGE_CHILD_AUXILIARY_CONTACT_TYPE] = config.EDGE_CHILD_AUXILIARY_CONTACT_VERSION, + [config.EDGE_CHILD_OUTPUT_MODULE_TYPE] = config.EDGE_CHILD_OUTPUT_MODULE_VERSION, + [config.EDGE_CHILD_ENERGY_METER_TYPE] = config.EDGE_CHILD_ENERGY_METER_VERSION, + [config.EDGE_CHILD_WATER_METER_TYPE] = config.EDGE_CHILD_WATER_METER_VERSION, + [config.EDGE_CHILD_GAS_METER_TYPE] = config.EDGE_CHILD_GAS_METER_VERSION, + [config.EDGE_CHILD_USB_ENERGY_METER_TYPE] = config.EDGE_CHILD_USB_ENERGY_METER_VERSION + } + + return edge_child_device_versions[edge_child_device_type] +end + +-- Method for getting the thing exact type +function utils.get_thing_exact_type(edge_child_device_type) + local device_version = utils.get_edge_child_device_version(edge_child_device_type) + + if device_version == nil then + return nil + end + + return config.MANUFACTURER .. "_" .. config.BRIDGE_TYPE .. "_" .. edge_child_device_type .. "_" .. device_version +end + +-- Method for getting the thing profile reference +function utils.get_thing_profile_ref(thing_info) + if thing_info.type == utils.get_thing_exact_type(config.EDGE_CHILD_CURRENT_SENSOR_TYPE) then + if thing_info.properties.isExport then + return config.EDGE_CHILD_CURRENT_SENSOR_PRODUCTION_PROFILE + else + return config.EDGE_CHILD_CURRENT_SENSOR_CONSUMPTION_PROFILE + end + end + + local thing_profiles = { + [utils.get_thing_exact_type(config.EDGE_CHILD_ENERGY_METER_MODULE_TYPE)] = config.EDGE_CHILD_ENERGY_METER_PROFILE, + [utils.get_thing_exact_type(config.EDGE_CHILD_AUXILIARY_CONTACT_TYPE)] = config.EDGE_CHILD_AUXILIARY_CONTACT_PROFILE, + [utils.get_thing_exact_type(config.EDGE_CHILD_OUTPUT_MODULE_TYPE)] = config.EDGE_CHILD_OUTPUT_MODULE_PROFILE, + [utils.get_thing_exact_type(config.EDGE_CHILD_ENERGY_METER_TYPE)] = config.EDGE_CHILD_ENERGY_METER_PROFILE, + [utils.get_thing_exact_type(config.EDGE_CHILD_WATER_METER_TYPE)] = config.EDGE_CHILD_WATER_METER_PROFILE, + [utils.get_thing_exact_type(config.EDGE_CHILD_GAS_METER_TYPE)] = config.EDGE_CHILD_GAS_METER_PROFILE, + [utils.get_thing_exact_type(config.EDGE_CHILD_USB_ENERGY_METER_TYPE)] = config.EDGE_CHILD_USB_ENERGY_METER_PROFILE + } + + return thing_profiles[thing_info.type] +end + +-- Method for getting the thing refresh period +function utils.get_thing_refresh_period(device) + local device_model = utils.get_device_model(device) + + local thing_refresh_periods = { + [utils.get_thing_exact_type(config.EDGE_CHILD_CURRENT_SENSOR_TYPE)] = config.EDGE_CHILD_CURRENT_SENSOR_REFRESH_PERIOD, + [utils.get_thing_exact_type(config.EDGE_CHILD_ENERGY_METER_MODULE_TYPE)] = config.EDGE_CHILD_ENERGY_METER_MODULE_REFRESH_PERIOD, + [utils.get_thing_exact_type(config.EDGE_CHILD_AUXILIARY_CONTACT_TYPE)] = config.EDGE_CHILD_AUXILIARY_CONTACT_REFRESH_PERIOD, + [utils.get_thing_exact_type(config.EDGE_CHILD_OUTPUT_MODULE_TYPE)] = config.EDGE_CHILD_OUTPUT_MODULE_REFRESH_PERIOD, + [utils.get_thing_exact_type(config.EDGE_CHILD_ENERGY_METER_TYPE)] = config.EDGE_CHILD_ENERGY_METER_REFRESH_PERIOD, + [utils.get_thing_exact_type(config.EDGE_CHILD_WATER_METER_TYPE)] = config.EDGE_CHILD_WATER_METER_REFRESH_PERIOD, + [utils.get_thing_exact_type(config.EDGE_CHILD_GAS_METER_TYPE)] = config.EDGE_CHILD_GAS_METER_REFRESH_PERIOD, + [utils.get_thing_exact_type(config.EDGE_CHILD_USB_ENERGY_METER_TYPE)] = config.EDGE_CHILD_USB_ENERGY_METER_REFRESH_PERIOD + } + + return thing_refresh_periods[device_model] +end + +-- Method for dumping a table to string +function utils.dump(o) + if type(o) == "table" then + local s = '{' + + for k,v in pairs(o) do + if type(k) ~= "number" then k = '"'..k..'"' end + s = s .. ' ['..k..'] = ' .. utils.dump(v) .. ',' + end + + return s .. '} ' + else + return tostring(o) + end +end + +-- Method for building a exponential backoff time value generator +function utils.backoff_builder(max, inc, rand) + local count = 0 + inc = inc or 1 + + return function() + local randval = 0 + if rand then + randval = math.random() * rand * 2 - rand + end + + local base = inc * (2 ^ count - 1) + count = count + 1 + + -- ensure base backoff (not including random factor) is less than max + if max then base = math.min(base, max) end + + -- ensure total backoff is >= 0 + return math.max(base + randval, 0) + end +end + +-- Method for creating a labeled socket +function utils.labeled_socket_builder(label, ssl_config) + label = (label or "") + if #label > 0 then + label = label .. " " + end + + if not ssl_config then + ssl_config = { mode = "client", protocol = "any", verify = "none", options = "all" } + end + + local function make_socket(host, port, wrap_ssl) + log.info("utils.labeled_socket_builder(): Creating TCP socket for REST Connection: " .. label) + local _ = nil + local sock, err = socket.tcp() + + if err ~= nil or (not sock) then + return nil, (err or "unknown error creating TCP socket") + end + + log.debug("utils.labeled_socket_builder(): Setting TCP socket timeout for REST Connection: " .. label) + _, err = sock:settimeout(60) + if err ~= nil then + return nil, "settimeout error: " .. err + end + + log.debug("utils.labeled_socket_builder(): Connecting TCP socket for REST Connection: " .. label) + _, err = sock:connect(host, port) + if err ~= nil then + return nil, "Connect error: " .. err + end + + log.debug("utils.labeled_socket_builder(): Set Keepalive for TCP socket for REST Connection: " .. label) + _, err = sock:setoption("keepalive", true) + if err ~= nil then + return nil, "Setoption error: " .. err + end + + if wrap_ssl then + log.debug("utils.labeled_socket_builder(): Creating SSL wrapper for REST Connection: " .. label) + sock, err = ssl.wrap(sock, ssl_config) + if err ~= nil then + return nil, "SSL wrap error: " .. err + end + + log.debug("utils.labeled_socket_builder(): Performing SSL handshake for REST Connection: " .. label) + _, err = sock:dohandshake() + if err ~= nil then + return nil, "Error with SSL handshake: " .. err + end + end + + log.info("utils.labeled_socket_builder(): Successfully created TCP connection: " .. label) + return sock, err + end + + return make_socket +end + +return utils \ No newline at end of file From f2aa768ca9d2400b5df17948fefdd7d7a26c3fa0 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:17 -0600 Subject: [PATCH 099/538] CHAD-17063: Zigbee-button lazy load subdrivers --- .../zigbee-button/src/aqara/can_handle.lua | 12 +++ .../zigbee-button/src/aqara/fingerprints.lua | 13 +++ .../zigbee-button/src/aqara/init.lua | 45 +++-------- .../zigbee-button/src/button_utils.lua | 15 +--- .../src/dimming-remote/can_handle.lua | 14 ++++ .../src/dimming-remote/fingerprints.lua | 9 +++ .../zigbee-button/src/dimming-remote/init.lua | 30 +------ .../zigbee-button/src/ewelink/can_handle.lua | 14 ++++ .../src/ewelink/fingerprints.lua | 9 +++ .../zigbee-button/src/ewelink/init.lua | 32 ++------ .../zigbee-button/src/ezviz/can_handle.lua | 18 +++++ .../zigbee-button/src/ezviz/init.lua | 29 ++----- .../zigbee-button/src/frient/can_handle.lua | 11 +++ .../zigbee-button/src/frient/init.lua | 20 +---- .../SmartThings/zigbee-button/src/init.lua | 29 +------ .../zigbee-button/src/iris/can_handle.lua | 14 ++++ .../zigbee-button/src/iris/fingerprints.lua | 9 +++ .../zigbee-button/src/iris/init.lua | 30 +------ .../zigbee-button/src/lazy_load_subdriver.lua | 15 ++++ .../src/pushButton/can_handle.lua | 11 +++ .../zigbee-button/src/pushButton/init.lua | 30 +------ .../zigbee-button/src/samjin/can_handle.lua | 11 +++ .../zigbee-button/src/samjin/init.lua | 20 +---- .../zigbee-button/src/st/zigbee/zdo/init.lua | 17 +--- .../zigbee-button/src/sub_drivers.lua | 17 ++++ .../src/test/test_SLED_button.lua | 15 +--- .../src/test/test_aduro_button.lua | 15 +--- .../src/test/test_aqara_button.lua | 15 +--- .../src/test/test_centralite_button.lua | 15 +--- .../src/test/test_dimming_remote.lua | 17 +--- .../src/test/test_ewelink_button.lua | 17 +--- .../src/test/test_ezviz_button.lua | 17 +--- .../src/test/test_frient_button.lua | 15 +--- .../src/test/test_heiman_button.lua | 15 +--- .../src/test/test_ikea_on_off.lua | 15 +--- .../src/test/test_ikea_open_close.lua | 15 +--- .../src/test/test_ikea_remote_control.lua | 17 +--- .../src/test/test_iris_button.lua | 15 +--- .../test/test_linxura_aura_smart_button.lua | 17 +--- ...est_linxura_smart_controller_4x_button.lua | 17 +--- .../src/test/test_push_only_button.lua | 15 +--- .../src/test/test_robb_4x_button.lua | 15 +--- .../src/test/test_robb_8x_button.lua | 15 +--- .../src/test/test_samjin_button.lua | 15 +--- .../src/test/test_shinasystem_button.lua | 15 +--- .../src/test/test_somfy_situo_1_button.lua | 15 +--- .../src/test/test_somfy_situo_4_button.lua | 15 +--- .../src/test/test_thirdreality_button.lua | 3 + .../src/test/test_vimar_button.lua | 15 +--- .../src/test/test_wallhero_button.lua | 15 +--- .../src/test/test_zigbee_button.lua | 15 +--- .../src/test/test_zigbee_ecosmart_button.lua | 15 +--- .../src/test/test_zunzunbee_8_button.lua | 17 +--- .../src/thirdreality/can_handle.lua | 11 +++ .../zigbee-button/src/thirdreality/init.lua | 7 +- .../zigbee-multi-button/SLED/can_handle.lua | 11 +++ .../src/zigbee-multi-button/SLED/init.lua | 20 +---- .../adurosmart/can_handle.lua | 14 ++++ .../adurosmart/fingerprints.lua | 11 +++ .../zigbee-multi-button/adurosmart/init.lua | 32 +------- .../src/zigbee-multi-button/can_handle.lua | 14 ++++ .../centralite/can_handle.lua | 14 ++++ .../centralite/fingerprints.lua | 9 +++ .../zigbee-multi-button/centralite/init.lua | 30 +------ .../ecosmart/can_handle.lua | 11 +++ .../src/zigbee-multi-button/ecosmart/init.lua | 20 +---- .../src/zigbee-multi-button/fingerprints.lua | 42 ++++++++++ .../zigbee-multi-button/heiman/can_handle.lua | 14 ++++ .../heiman/fingerprints.lua | 10 +++ .../src/zigbee-multi-button/heiman/init.lua | 37 ++------- .../ikea/TRADFRI_on_off_switch/can_handle.lua | 9 +++ .../init.lua} | 19 +---- .../ikea/TRADFRI_open_close_remote.lua | 36 --------- .../TRADFRI_open_close_remote/can_handle.lua | 9 +++ .../ikea/TRADFRI_open_close_remote/init.lua | 23 ++++++ .../TRADFRI_remote_control/can_handle.lua | 9 +++ .../init.lua} | 19 +---- .../zigbee-multi-button/ikea/can_handle.lua | 14 ++++ .../zigbee-multi-button/ikea/fingerprints.lua | 10 +++ .../src/zigbee-multi-button/ikea/init.lua | 37 ++------- .../zigbee-multi-button/ikea/sub_drivers.lua | 12 +++ .../src/zigbee-multi-button/init.lua | 80 ++----------------- .../linxura/can_handle.lua | 14 ++++ .../linxura/fingerprints.lua | 9 +++ .../src/zigbee-multi-button/linxura/init.lua | 30 +------ .../zigbee-multi-button/robb/can_handle.lua | 15 ++++ .../zigbee-multi-button/robb/fingerprints.lua | 15 ++++ .../src/zigbee-multi-button/robb/init.lua | 13 +-- .../shinasystems/can_handle.lua | 14 ++++ .../shinasystems/fingerprints.lua | 17 ++++ .../zigbee-multi-button/shinasystems/init.lua | 41 ++-------- .../zigbee-multi-button/somfy/can_handle.lua | 11 +++ .../src/zigbee-multi-button/somfy/init.lua | 25 ++---- .../somfy/somfy_situo_1/can_handle.lua | 9 +++ .../init.lua} | 19 +---- .../somfy/somfy_situo_4/can_handle.lua | 9 +++ .../init.lua} | 19 +---- .../zigbee-multi-button/somfy/sub_drivers.lua | 11 +++ .../src/zigbee-multi-button/sub_drivers.lua | 20 +++++ .../zigbee-multi-button/supported_values.lua | 15 +--- .../zigbee-multi-button/vimar/can_handle.lua | 11 +++ .../src/zigbee-multi-button/vimar/init.lua | 20 +---- .../wallhero/can_handle.lua | 14 ++++ .../wallhero/fingerprints.lua | 8 ++ .../src/zigbee-multi-button/wallhero/init.lua | 29 +------ .../zunzunbee/can_handle.lua | 15 ++++ .../zunzunbee/fingerprints.lua | 9 +++ .../zigbee-multi-button/zunzunbee/init.lua | 33 +------- 108 files changed, 821 insertions(+), 1104 deletions(-) create mode 100644 drivers/SmartThings/zigbee-button/src/aqara/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/aqara/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/dimming-remote/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/dimming-remote/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/ewelink/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/ewelink/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/ezviz/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/frient/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/iris/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/iris/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-button/src/pushButton/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/samjin/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-button/src/thirdreality/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/SLED/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/adurosmart/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/adurosmart/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/centralite/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/centralite/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ecosmart/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/heiman/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/heiman/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_on_off_switch/can_handle.lua rename drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/{TRADFRI_on_off_switch.lua => TRADFRI_on_off_switch/init.lua} (55%) delete mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_open_close_remote.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_open_close_remote/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_open_close_remote/init.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_remote_control/can_handle.lua rename drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/{TRADFRI_remote_control.lua => TRADFRI_remote_control/init.lua} (81%) create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/linxura/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/linxura/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/robb/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/robb/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/shinasystems/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/shinasystems/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_1/can_handle.lua rename drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/{somfy_situo_1.lua => somfy_situo_1/init.lua} (78%) create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_4/can_handle.lua rename drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/{somfy_situo_4.lua => somfy_situo_4/init.lua} (83%) create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/vimar/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/wallhero/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/wallhero/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/zunzunbee/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/zunzunbee/fingerprints.lua diff --git a/drivers/SmartThings/zigbee-button/src/aqara/can_handle.lua b/drivers/SmartThings/zigbee-button/src/aqara/can_handle.lua new file mode 100644 index 0000000000..3dc96661cd --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/aqara/can_handle.lua @@ -0,0 +1,12 @@ +-- 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" + if FINGERPRINTS[device:get_model()] and FINGERPRINTS[device:get_model()].mfr == device:get_manufacturer() then + return true, require("aqara") + end + return false +end + +return is_aqara_products diff --git a/drivers/SmartThings/zigbee-button/src/aqara/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/aqara/fingerprints.lua new file mode 100644 index 0000000000..83f7c77050 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/aqara/fingerprints.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FINGERPRINTS = { + ["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) +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/aqara/init.lua b/drivers/SmartThings/zigbee-button/src/aqara/init.lua index 1fb762cbbc..7467fcfe42 100644 --- a/drivers/SmartThings/zigbee-button/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-button/src/aqara/init.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 + local battery_defaults = require "st.zigbee.defaults.battery_defaults" local clusters = require "st.zigbee.zcl.clusters" @@ -34,14 +24,7 @@ local MULTISTATE_INPUT_CLUSTER_ID = 0x0012 local PRESENT_ATTRIBUTE_ID = 0x0055 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 FINGERPRINTS = require "aqara.fingerprints" local configuration = { { @@ -65,7 +48,7 @@ local configuration = { 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 btn_evt_cnt = FINGERPRINTS[device:get_model()].btn_cnt or 1 local evt = capabilities.button.button.held({ state_change = true }) if value.value == 1 then evt = capabilities.button.button.pushed({ state_change = true }) @@ -110,7 +93,7 @@ local function battery_level_handler(driver, device, value, zb_rx) 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 btn_evt_cnt = FINGERPRINTS[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 @@ -139,14 +122,6 @@ local function mode_switching_handler(driver, device, value, zb_rx) 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 @@ -157,11 +132,11 @@ local function device_init(driver, device) end local function added_handler(self, device) - local btn_evt_cnt = AQARA_REMOTE_SWITCH[device:get_model()].btn_cnt or 1 + local btn_evt_cnt = FINGERPRINTS[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 + local type = FINGERPRINTS[device:get_model()].type or "CR2032" + local quantity = FINGERPRINTS[device:get_model()].quantity or 1 if mode == 0 then if model == "lumi.remote.b18ac1" or model == "lumi.remote.b28ac1" then @@ -233,7 +208,7 @@ local aqara_wireless_switch_handler = { } } }, - can_handle = is_aqara_products + can_handle = require("aqara.can_handle"), } return aqara_wireless_switch_handler diff --git a/drivers/SmartThings/zigbee-button/src/button_utils.lua b/drivers/SmartThings/zigbee-button/src/button_utils.lua index 4a8f7101e0..10ca4ab11a 100644 --- a/drivers/SmartThings/zigbee-button/src/button_utils.lua +++ b/drivers/SmartThings/zigbee-button/src/button_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 capabilities = require "st.capabilities" local log = require "log" diff --git a/drivers/SmartThings/zigbee-button/src/dimming-remote/can_handle.lua b/drivers/SmartThings/zigbee-button/src/dimming-remote/can_handle.lua new file mode 100644 index 0000000000..d3a469e214 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/dimming-remote/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_zigbee_dimming_remote(opts, driver, device, ...) + local FINGERPRINTS = require("dimming-remote.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("dimming-remote") + end + end + return false +end + +return can_handle_zigbee_dimming_remote diff --git a/drivers/SmartThings/zigbee-button/src/dimming-remote/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/dimming-remote/fingerprints.lua new file mode 100644 index 0000000000..439694ebaf --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/dimming-remote/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIBEE_DIMMING_SWITCH_FINGERPRINTS = { + { mfr = "OSRAM", model = "LIGHTIFY Dimming Switch" }, + { mfr = "CentraLite", model = "3130" } +} + +return ZIBEE_DIMMING_SWITCH_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/dimming-remote/init.lua b/drivers/SmartThings/zigbee-button/src/dimming-remote/init.lua index d08c975632..54ca675ccd 100644 --- a/drivers/SmartThings/zigbee-button/src/dimming-remote/init.lua +++ b/drivers/SmartThings/zigbee-button/src/dimming-remote/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" @@ -22,19 +12,7 @@ local battery_defaults = require "st.zigbee.defaults.battery_defaults" local button_utils = require "button_utils" -local ZIBEE_DIMMING_SWITCH_FINGERPRINTS = { - { mfr = "OSRAM", model = "LIGHTIFY Dimming Switch" }, - { mfr = "CentraLite", model = "3130" } -} -local function can_handle_zigbee_dimming_remote(opts, driver, device, ...) - for _, fingerprint in ipairs(ZIBEE_DIMMING_SWITCH_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local function button_pushed_handler(button_number) return function(self, device, value, zb_rx) @@ -84,7 +62,7 @@ local dimming_remote = { } } }, - can_handle = can_handle_zigbee_dimming_remote + can_handle = require("dimming-remote.can_handle"), } return dimming_remote diff --git a/drivers/SmartThings/zigbee-button/src/ewelink/can_handle.lua b/drivers/SmartThings/zigbee-button/src/ewelink/can_handle.lua new file mode 100644 index 0000000000..6a9426b75d --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/ewelink/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_ewelink_button(opts, driver, device, ...) + local FINGERPRINTS = require("ewelink.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("ewelink") + end + end + return false +end + +return can_handle_ewelink_button diff --git a/drivers/SmartThings/zigbee-button/src/ewelink/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/ewelink/fingerprints.lua new file mode 100644 index 0000000000..3e21d092d3 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/ewelink/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local EWELINK_BUTTON_FINGERPRINTS = { + { mfr = "eWeLink", model = "WB01" }, + { mfr = "eWeLink", model = "SNZB-01P" } +} + +return EWELINK_BUTTON_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/ewelink/init.lua b/drivers/SmartThings/zigbee-button/src/ewelink/init.lua index 334dd79108..b503b68c64 100644 --- a/drivers/SmartThings/zigbee-button/src/ewelink/init.lua +++ b/drivers/SmartThings/zigbee-button/src/ewelink/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" @@ -19,19 +9,7 @@ local device_management = require "st.zigbee.device_management" local OnOff = clusters.OnOff local button = capabilities.button.button -local EWELINK_BUTTON_FINGERPRINTS = { - { mfr = "eWeLink", model = "WB01" }, - { mfr = "eWeLink", model = "SNZB-01P" } -} -local function can_handle_ewelink_button(opts, driver, device, ...) - for _, fingerprint in ipairs(EWELINK_BUTTON_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() @@ -61,7 +39,7 @@ local ewelink_button = { } } }, - can_handle = can_handle_ewelink_button + can_handle = require("ewelink.can_handle"), } -return ewelink_button \ No newline at end of file +return ewelink_button diff --git a/drivers/SmartThings/zigbee-button/src/ezviz/can_handle.lua b/drivers/SmartThings/zigbee-button/src/ezviz/can_handle.lua new file mode 100644 index 0000000000..b046b8966c --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/ezviz/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_ezviz_button = function(opts, driver, device) + + local EZVIZ_PRIVATE_BUTTON_CLUSTER = 0xFE05 + local EZVIZ_PRIVATE_STANDARD_CLUSTER = 0xFE00 + local EZVIZ_MFR = "EZVIZ" + + local support_button_cluster = device:supports_server_cluster(EZVIZ_PRIVATE_BUTTON_CLUSTER) + local support_standard_cluster = device:supports_server_cluster(EZVIZ_PRIVATE_STANDARD_CLUSTER) + if device:get_manufacturer() == EZVIZ_MFR and support_button_cluster and support_standard_cluster then + return true, require("ezviz") + end + return false +end + +return is_ezviz_button diff --git a/drivers/SmartThings/zigbee-button/src/ezviz/init.lua b/drivers/SmartThings/zigbee-button/src/ezviz/init.lua index 5b630843a2..5bea38ffb4 100644 --- a/drivers/SmartThings/zigbee-button/src/ezviz/init.lua +++ b/drivers/SmartThings/zigbee-button/src/ezviz/init.lua @@ -1,31 +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 EZVIZ_PRIVATE_BUTTON_CLUSTER = 0xFE05 -local EZVIZ_PRIVATE_STANDARD_CLUSTER = 0xFE00 local EZVIZ_PRIVATE_BUTTON_ATTRIBUTE = 0x0000 -local EZVIZ_MFR = "EZVIZ" -local is_ezviz_button = function(opts, driver, device) - local support_button_cluster = device:supports_server_cluster(EZVIZ_PRIVATE_BUTTON_CLUSTER) - local support_standard_cluster = device:supports_server_cluster(EZVIZ_PRIVATE_STANDARD_CLUSTER) - if device:get_manufacturer() == EZVIZ_MFR and support_button_cluster and support_standard_cluster then - return true - end -end local ezviz_private_cluster_button_handler = function(driver, device, zb_rx) local event @@ -53,6 +34,6 @@ local ezviz_button_handler = { } } }, - can_handle = is_ezviz_button + can_handle = require("ezviz.can_handle"), } -return ezviz_button_handler \ No newline at end of file +return ezviz_button_handler diff --git a/drivers/SmartThings/zigbee-button/src/frient/can_handle.lua b/drivers/SmartThings/zigbee-button/src/frient/can_handle.lua new file mode 100644 index 0000000000..924c595fd3 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/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() == "SBTZB-110" or device:get_model() == "MBTZB-110") then + return true, require("frient") + end + return false +end + +return frient_can_handle diff --git a/drivers/SmartThings/zigbee-button/src/frient/init.lua b/drivers/SmartThings/zigbee-button/src/frient/init.lua index 17b963548d..bf622cd694 100644 --- a/drivers/SmartThings/zigbee-button/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-button/src/frient/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 zcl_clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" @@ -232,8 +222,6 @@ local frient_button = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "frient A/S" and (device:get_model() == "SBTZB-110" or device:get_model() == "MBTZB-110") - end + can_handle = require("frient.can_handle"), } return frient_button diff --git a/drivers/SmartThings/zigbee-button/src/init.lua b/drivers/SmartThings/zigbee-button/src/init.lua index a61ff415a7..8ed0db27db 100644 --- a/drivers/SmartThings/zigbee-button/src/init.lua +++ b/drivers/SmartThings/zigbee-button/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" @@ -140,18 +130,7 @@ local zigbee_button_driver_template = { } } }, - sub_drivers = { - require("aqara"), - require("pushButton"), - require("frient"), - require("zigbee-multi-button"), - require("dimming-remote"), - require("iris"), - require("samjin"), - require("ewelink"), - require("thirdreality"), - require("ezviz") - }, + sub_drivers = require("sub_drivers"), lifecycle_handlers = { added = added_handler, }, diff --git a/drivers/SmartThings/zigbee-button/src/iris/can_handle.lua b/drivers/SmartThings/zigbee-button/src/iris/can_handle.lua new file mode 100644 index 0000000000..618bc6eb5a --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/iris/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_iris_button(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 can_handle_iris_button diff --git a/drivers/SmartThings/zigbee-button/src/iris/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/iris/fingerprints.lua new file mode 100644 index 0000000000..4ae1761e06 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/iris/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local IRIS_BUTTON_FINGERPRINTS = { + { mfr = "CentraLite", model = "3455-L" }, + { mfr = "CentraLite", model = "3460-L" } +} + +return IRIS_BUTTON_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/iris/init.lua b/drivers/SmartThings/zigbee-button/src/iris/init.lua index 157e766748..a6eac49870 100644 --- a/drivers/SmartThings/zigbee-button/src/iris/init.lua +++ b/drivers/SmartThings/zigbee-button/src/iris/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" @@ -21,19 +11,7 @@ local device_management = require "st.zigbee.device_management" local battery_defaults = require "st.zigbee.defaults.battery_defaults" local button_utils = require "button_utils" -local IRIS_BUTTON_FINGERPRINTS = { - { mfr = "CentraLite", model = "3455-L" }, - { mfr = "CentraLite", model = "3460-L" } -} -local function can_handle_iris_button(opts, driver, device, ...) - for _, fingerprint in ipairs(IRIS_BUTTON_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local function button_pressed_handler(self, device, value, zb_rx) button_utils.init_button_press(device) @@ -94,7 +72,7 @@ local iris_button = { } } }, - can_handle = can_handle_iris_button + can_handle = require("iris.can_handle"), } return iris_button diff --git a/drivers/SmartThings/zigbee-button/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-button/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/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-button/src/pushButton/can_handle.lua b/drivers/SmartThings/zigbee-button/src/pushButton/can_handle.lua new file mode 100644 index 0000000000..f25592fb01 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/pushButton/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function pushButton_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "HEIMAN" and device:get_model() == "SOS-EM" then + return true, require("pushButton") + end + return false +end + +return pushButton_can_handle diff --git a/drivers/SmartThings/zigbee-button/src/pushButton/init.lua b/drivers/SmartThings/zigbee-button/src/pushButton/init.lua index 6fec59d69e..170307a600 100644 --- a/drivers/SmartThings/zigbee-button/src/pushButton/init.lua +++ b/drivers/SmartThings/zigbee-button/src/pushButton/init.lua @@ -1,27 +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 + --- Copyright 2020 SmartThings --- --- Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except --- in compliance with the License. You may obtain a copy of the License at: --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software distributed under the License is distributed --- on an "AS IS" BASIS, WITHOUT 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 button_utils = require "button_utils" @@ -38,9 +18,7 @@ local push_button = { added = added_handler, }, sub_drivers = {}, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "HEIMAN" and device:get_model() == "SOS-EM" - end + can_handle = require("pushButton.can_handle"), } return push_button diff --git a/drivers/SmartThings/zigbee-button/src/samjin/can_handle.lua b/drivers/SmartThings/zigbee-button/src/samjin/can_handle.lua new file mode 100644 index 0000000000..0886bedb9c --- /dev/null +++ b/drivers/SmartThings/zigbee-button/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" and device:get_model() == "button" then + return true, require("samjin") + end + return false +end + +return samjin_can_handle diff --git a/drivers/SmartThings/zigbee-button/src/samjin/init.lua b/drivers/SmartThings/zigbee-button/src/samjin/init.lua index 116e6aaa80..bd254091a6 100644 --- a/drivers/SmartThings/zigbee-button/src/samjin/init.lua +++ b/drivers/SmartThings/zigbee-button/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 + local zcl_clusters = require "st.zigbee.zcl.clusters" local battery_defaults = require "st.zigbee.defaults.battery_defaults" @@ -28,9 +18,7 @@ local samjin_button = { lifecycle_handlers = { init = init_handler }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Samjin" and device:get_model() == "button" - end + can_handle = require("samjin.can_handle"), } return samjin_button diff --git a/drivers/SmartThings/zigbee-button/src/st/zigbee/zdo/init.lua b/drivers/SmartThings/zigbee-button/src/st/zigbee/zdo/init.lua index 9d009d47cb..3d832ac180 100644 --- a/drivers/SmartThings/zigbee-button/src/st/zigbee/zdo/init.lua +++ b/drivers/SmartThings/zigbee-button/src/st/zigbee/zdo/init.lua @@ -1,16 +1,5 @@ --- 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" @@ -152,4 +141,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-button/src/sub_drivers.lua b/drivers/SmartThings/zigbee-button/src/sub_drivers.lua new file mode 100644 index 0000000000..47fe5ff9c4 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/sub_drivers.lua @@ -0,0 +1,17 @@ +-- 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("pushButton"), + lazy_load_if_possible("frient"), + lazy_load_if_possible("zigbee-multi-button"), + lazy_load_if_possible("dimming-remote"), + lazy_load_if_possible("iris"), + lazy_load_if_possible("samjin"), + lazy_load_if_possible("ewelink"), + lazy_load_if_possible("thirdreality"), + lazy_load_if_possible("ezviz"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua index fecbeeabb7..cfbd4a6845 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.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 clusters = require "st.zigbee.zcl.clusters" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua index 2736ea3d96..27b8da764b 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.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-button/src/test/test_aqara_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua index 838b9545fb..67314ef651 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.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" local zigbee_test_utils = require "integration_test.zigbee_test_utils" diff --git a/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua index 6c2e46f724..c8d5ff87ae 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.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-button/src/test/test_dimming_remote.lua b/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua index 4ba87cb461..c552322c9b 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.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 capabilities = require "st.capabilities" @@ -272,4 +261,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-button/src/test/test_ewelink_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua index d08e38391a..5562eced88 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.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 capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -151,4 +140,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-button/src/test/test_ezviz_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua index 77b6768149..be613bbc14 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.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 zigbee_test_utils = require "integration_test.zigbee_test_utils" @@ -196,4 +185,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-button/src/test/test_frient_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua index 3df15b9f08..aa6f211d65 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_frient_button.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-button/src/test/test_heiman_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua index f0604a0cd8..0cc2c734cf 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.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-button/src/test/test_ikea_on_off.lua b/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua index 9479358793..82f477f426 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua b/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua index 7b41684c5a..3d5c7ab58d 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua b/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua index 6b16ed842c..ef08a1d7e5 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.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 capabilities = require "st.capabilities" @@ -305,4 +294,4 @@ test.register_message_test( } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua index f28e47dece..64630415a0 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_iris_button.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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua index 4b9ca6dfd9..8e3ff6e001 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.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 -- Mock out globals local test = require "integration_test" @@ -119,4 +108,4 @@ test.register_coroutine_test( ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua index a53d5f3851..27ec0a8e44 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.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 -- Mock out globals local test = require "integration_test" @@ -119,4 +108,4 @@ test.register_coroutine_test( ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua index ed046c5781..4af8ef8c3c 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.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-button/src/test/test_robb_4x_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua index 554decefc1..4888ae5f7e 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.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-button/src/test/test_robb_8x_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua index 67554e92a0..ebb324a3dc 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.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-button/src/test/test_samjin_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua index cf3bd7fe3d..1a23d68d83 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua index 6f677e80b7..5278edbf8e 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.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-button/src/test/test_somfy_situo_1_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua index 83e9fec30d..7e7e155867 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua index c0329f5561..d358db3fcc 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua index bdb8e51bf6..a102e30e51 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local data_types = require "st.zigbee.data_types" diff --git a/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua index e1d35fb6bc..575175aa47 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.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 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua index e62cbd7055..98d8efbdfd 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.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 capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua index 97feca9121..92a50636a4 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.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-button/src/test/test_zigbee_ecosmart_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua index 8f956e9203..c6f28dfe44 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.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-button/src/test/test_zunzunbee_8_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua index a92e1c6285..ae579dd671 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.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" @@ -203,4 +192,4 @@ test.register_coroutine_test( ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/thirdreality/can_handle.lua b/drivers/SmartThings/zigbee-button/src/thirdreality/can_handle.lua new file mode 100644 index 0000000000..77d97bb6a5 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/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() == "3RSB22BZ" then + return true, require("thirdreality") + end + return false +end + +return thirdreality_can_handle diff --git a/drivers/SmartThings/zigbee-button/src/thirdreality/init.lua b/drivers/SmartThings/zigbee-button/src/thirdreality/init.lua index c4ce10f1bf..f56745c0c1 100644 --- a/drivers/SmartThings/zigbee-button/src/thirdreality/init.lua +++ b/drivers/SmartThings/zigbee-button/src/thirdreality/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local MULTISTATE_INPUT_ATTR = 0x0012 @@ -41,9 +44,7 @@ local thirdreality_device_handler = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Third Reality, Inc" and device:get_model() == "3RSB22BZ" - end + can_handle = require("thirdreality.can_handle"), } return thirdreality_device_handler diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/SLED/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/SLED/can_handle.lua new file mode 100644 index 0000000000..4e96689e08 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/SLED/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function SLED_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Samsung Electronics" and device:get_model() == "SAMSUNG-ITM-Z-005" then + return true, require("zigbee-multi-button.SLED") + end + return false +end + +return SLED_can_handle diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/SLED/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/SLED/init.lua index ea6a9d5faa..cab7458507 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/SLED/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/SLED/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.zigbee.zcl.clusters" @@ -72,9 +62,7 @@ local SLED_button = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Samsung Electronics" and device:get_model() == "SAMSUNG-ITM-Z-005" - end + can_handle = require("zigbee-multi-button.SLED.can_handle"), } return SLED_button diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/adurosmart/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/adurosmart/can_handle.lua new file mode 100644 index 0000000000..9b4eedd1ff --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/adurosmart/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_aduro_button = function(opts, driver, device) + local FINGERPRINTS = require("zigbee-multi-button.adurosmart.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-multi-button.adurosmart") + end + end + return false +end + +return is_aduro_button diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/adurosmart/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/adurosmart/fingerprints.lua new file mode 100644 index 0000000000..c5870be270 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/adurosmart/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ADURO_BUTTON_FINGERPRINTS = { + { mfr = "AduroSmart Eria", model = "ADUROLIGHT_CSC" }, + { mfr = "ADUROLIGHT", model = "ADUROLIGHT_CSC" }, + { mfr = "AduroSmart Eria", model = "Adurolight_NCC" }, + { mfr = "ADUROLIGHT", model = "Adurolight_NCC" } +} + +return ADURO_BUTTON_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/adurosmart/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/adurosmart/init.lua index 76a6ba6aae..56a29a1d0a 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/adurosmart/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/adurosmart/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" @@ -25,21 +15,7 @@ local ADURO_NUM_ENDPOINT = 0x04 local ADURO_MANUFACTURER_SPECIFIC_CLUSTER = 0xFCCC local ADURO_MANUFACTURER_SPECIFIC_CMD = 0x00 -local ADURO_BUTTON_FINGERPRINTS = { - { mfr = "AduroSmart Eria", model = "ADUROLIGHT_CSC" }, - { mfr = "ADUROLIGHT", model = "ADUROLIGHT_CSC" }, - { mfr = "AduroSmart Eria", model = "Adurolight_NCC" }, - { mfr = "ADUROLIGHT", model = "Adurolight_NCC" } -} -local is_aduro_button = function(opts, driver, device) - for _, fingerprint in ipairs(ADURO_BUTTON_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local do_configuration = function(self, device) for endpoint = 1,ADURO_NUM_ENDPOINT do @@ -83,7 +59,7 @@ local aduro_device_handler = { } } }, - can_handle = is_aduro_button + can_handle = require("zigbee-multi-button.adurosmart.can_handle"), } return aduro_device_handler diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/can_handle.lua new file mode 100644 index 0000000000..bd1c57940f --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_zigbee_multi_button(opts, driver, device, ...) + local FINGERPRINTS = require("zigbee-multi-button.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-multi-button") + end + end + return false +end + +return can_handle_zigbee_multi_button diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/centralite/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/centralite/can_handle.lua new file mode 100644 index 0000000000..0216d3729c --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/centralite/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_centralite_button = function(opts, driver, device) + local FINGERPRINTS = require("zigbee-multi-button.centralite.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-multi-button.centralite") + end + end + return false +end + +return is_centralite_button diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/centralite/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/centralite/fingerprints.lua new file mode 100644 index 0000000000..d742cb6c64 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/centralite/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local CENTRALITE_BUTTON_FINGERPRINTS = { + { mfr = "CentraLite", model = "3450-L" }, + { mfr = "CentraLite", model = "3450-L2" } +} + +return CENTRALITE_BUTTON_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/centralite/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/centralite/init.lua index 8eb3762be4..d291efb0cb 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/centralite/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/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 clusters = require "st.zigbee.zcl.clusters" local device_management = require "st.zigbee.device_management" @@ -30,19 +20,7 @@ local EP_BUTTON_COMPONENT_MAP = { [0x04] = 2 } -local CENTRALITE_BUTTON_FINGERPRINTS = { - { mfr = "CentraLite", model = "3450-L" }, - { mfr = "CentraLite", model = "3450-L2" } -} -local is_centralite_button = function(opts, driver, device) - for _, fingerprint in ipairs(CENTRALITE_BUTTON_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local do_configuration = function(self, device) device:send(device_management.build_bind_request(device, PowerConfiguration.ID, self.environment_info.hub_zigbee_eui)) @@ -75,7 +53,7 @@ local centralite_device_handler = { } } }, - can_handle = is_centralite_button + can_handle = require("zigbee-multi-button.centralite.can_handle"), } return centralite_device_handler diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ecosmart/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ecosmart/can_handle.lua new file mode 100644 index 0000000000..217df6b26f --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ecosmart/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function ecosmart_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "LDS" and device:get_model() == "ZBT-CCTSwitch-D0001" then + return true, require("zigbee-multi-button.ecosmart") + end + return false +end + +return ecosmart_can_handle diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ecosmart/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ecosmart/init.lua index 8f7d4ec6d8..215adba69c 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ecosmart/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ecosmart/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" @@ -126,9 +116,7 @@ local ecosmart_button = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "LDS" and device:get_model() == "ZBT-CCTSwitch-D0001" - end + can_handle = require("zigbee-multi-button.ecosmart.can_handle"), } return ecosmart_button diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/fingerprints.lua new file mode 100644 index 0000000000..cf3903152d --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/fingerprints.lua @@ -0,0 +1,42 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_MULTI_BUTTON_FINGERPRINTS = { + { mfr = "CentraLite", model = "3450-L" }, + { mfr = "CentraLite", model = "3450-L2" }, + { mfr = "AduroSmart Eria", model = "ADUROLIGHT_CSC" }, + { mfr = "ADUROLIGHT", model = "ADUROLIGHT_CSC" }, + { mfr = "AduroSmart Eria", model = "Adurolight_NCC" }, + { mfr = "ADUROLIGHT", model = "Adurolight_NCC" }, + { mfr = "HEIMAN", model = "SceneSwitch-EM-3.0" }, + { mfr = "HEIMAN", model = "HS6SSA-W-EF-3.0" }, + { mfr = "HEIMAN", model = "HS6SSB-W-EF-3.0" }, + { mfr = "IKEA of Sweden", model = "TRADFRI on/off switch" }, + { mfr = "IKEA of Sweden", model = "TRADFRI open/close remote" }, + { mfr = "IKEA of Sweden", model = "TRADFRI remote control" }, + { mfr = "KE", model = "TRADFRI open/close remote" }, + { mfr = "\x02KE", model = "TRADFRI open/close remote" }, + { mfr = "SOMFY", model = "Situo 1 Zigbee" }, + { mfr = "SOMFY", model = "Situo 4 Zigbee" }, + { mfr = "LDS", model = "ZBT-CCTSwitch-D0001" }, + { mfr = "ShinaSystem", model = "MSM-300Z" }, + { mfr = "ShinaSystem", model = "BSM-300Z" }, + { mfr = "ShinaSystem", model = "SBM300ZB1" }, + { mfr = "ShinaSystem", model = "SBM300ZB2" }, + { mfr = "ShinaSystem", model = "SBM300ZB3" }, + { mfr = "ShinaSystem", model = "SBM300ZC1" }, + { mfr = "ShinaSystem", model = "SBM300ZC2" }, + { mfr = "ShinaSystem", model = "SBM300ZC3" }, + { mfr = "ShinaSystem", model = "SBM300ZC4" }, + { mfr = "ShinaSystem", model = "SQM300ZC4" }, + { mfr = "ROBB smarrt", model = "ROB_200-007-0" }, + { mfr = "ROBB smarrt", model = "ROB_200-008-0" }, + { mfr = "WALL HERO", model = "ACL-401SCA4" }, + { mfr = "Samsung Electronics", model = "SAMSUNG-ITM-Z-005" }, + { mfr = "Vimar", model = "RemoteControl_v1.0" }, + { mfr = "Linxura", model = "Smart Controller" }, + { mfr = "Linxura", model = "Aura Smart Button" }, + { mfr = "zunzunbee", model = "SSWZ8T" } +} + +return ZIGBEE_MULTI_BUTTON_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/heiman/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/heiman/can_handle.lua new file mode 100644 index 0000000000..09e0575266 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/heiman/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_heiman_button = function(opts, driver, device) + local FINGERPRINTS = require("zigbee-multi-button.heiman.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-multi-button.heiman") + end + end + return false +end + +return is_heiman_button diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/heiman/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/heiman/fingerprints.lua new file mode 100644 index 0000000000..68723ba700 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/heiman/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local HEIMAN_BUTTON_FINGERPRINTS = { + { mfr = "HEIMAN", model = "SceneSwitch-EM-3.0", endpoint_num = 0x04 }, + { mfr = "HEIMAN", model = "HS6SSA-W-EF-3.0", endpoint_num = 0x04 }, + { mfr = "HEIMAN", model = "HS6SSB-W-EF-3.0", endpoint_num = 0x03 }, +} + +return HEIMAN_BUTTON_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/heiman/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/heiman/init.lua index 3c32e33fbe..980472671f 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/heiman/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/heiman/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" @@ -22,25 +12,12 @@ local OnOff = clusters.OnOff local PowerConfiguration = clusters.PowerConfiguration local Scenes = clusters.Scenes -local HEIMAN_GROUP_CONFIGURE = "is_group_configured" - -local HEIMAN_BUTTON_FINGERPRINTS = { - { mfr = "HEIMAN", model = "SceneSwitch-EM-3.0", endpoint_num = 0x04 }, - { mfr = "HEIMAN", model = "HS6SSA-W-EF-3.0", endpoint_num = 0x04 }, - { mfr = "HEIMAN", model = "HS6SSB-W-EF-3.0", endpoint_num = 0x03 }, -} +local FINGERPRINTS = require("zigbee-multi-button.heiman.fingerprints") -local is_heiman_button = function(opts, driver, device) - for _, fingerprint in ipairs(HEIMAN_BUTTON_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end +local HEIMAN_GROUP_CONFIGURE = "is_group_configured" local function get_endpoint_num(device) - for _, fingerprint in ipairs(HEIMAN_BUTTON_FINGERPRINTS) do + for _, fingerprint in ipairs(FINGERPRINTS) do if device:get_model() == fingerprint.model then return fingerprint.endpoint_num end @@ -123,7 +100,7 @@ local heiman_device_handler = { } } }, - can_handle = is_heiman_button + can_handle = require("zigbee-multi-button.heiman.can_handle"), } return heiman_device_handler diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_on_off_switch/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_on_off_switch/can_handle.lua new file mode 100644 index 0000000000..1c9621e53b --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_on_off_switch/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() == "TRADFRI on/off switch" then + return true, require("zigbee-multi-button.ikea.TRADFRI_on_off_switch") + end + return false +end diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_on_off_switch.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_on_off_switch/init.lua similarity index 55% rename from drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_on_off_switch.lua rename to drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_on_off_switch/init.lua index c6862ef533..87dcfcd47a 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_on_off_switch.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_on_off_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 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -33,9 +22,7 @@ local on_off_switch = { }, } }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "TRADFRI on/off switch" - end + can_handle = require "zigbee-multi-button.ikea.TRADFRI_on_off_switch.can_handle" } return on_off_switch diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_open_close_remote.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_open_close_remote.lua deleted file mode 100644 index bed6086e43..0000000000 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_open_close_remote.lua +++ /dev/null @@ -1,36 +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" -local clusters = require "st.zigbee.zcl.clusters" -local button_utils = require "button_utils" - -local WindowCovering = clusters.WindowCovering - -local open_close_remote = { - NAME = "Open/Close Remote", - zigbee_handlers = { - cluster = { - [WindowCovering.ID] = { - [WindowCovering.server.commands.UpOrOpen.ID] = button_utils.build_button_handler("button1", capabilities.button.button.pushed), - [WindowCovering.server.commands.DownOrClose.ID] = button_utils.build_button_handler("button2", capabilities.button.button.pushed) - } - } - }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "TRADFRI open/close remote" - end -} - -return open_close_remote diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_open_close_remote/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_open_close_remote/can_handle.lua new file mode 100644 index 0000000000..aed0256524 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_open_close_remote/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() == "TRADFRI open/close remote" then + return true, require("zigbee-multi-button.ikea.TRADFRI_open_close_remote") + end + return false +end diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_open_close_remote/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_open_close_remote/init.lua new file mode 100644 index 0000000000..23a622eb43 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_open_close_remote/init.lua @@ -0,0 +1,23 @@ +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local button_utils = require "button_utils" + +local WindowCovering = clusters.WindowCovering + +local open_close_remote = { + NAME = "Open/Close Remote", + zigbee_handlers = { + cluster = { + [WindowCovering.ID] = { + [WindowCovering.server.commands.UpOrOpen.ID] = button_utils.build_button_handler("button1", capabilities.button.button.pushed), + [WindowCovering.server.commands.DownOrClose.ID] = button_utils.build_button_handler("button2", capabilities.button.button.pushed) + } + } + }, + can_handle = require "zigbee-multi-button.ikea.TRADFRI_open_close_remote.can_handle", +} + +return open_close_remote diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_remote_control/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_remote_control/can_handle.lua new file mode 100644 index 0000000000..d45924cfd1 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_remote_control/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() == "TRADFRI remote control" then + return true, require("zigbee-multi-button.ikea.TRADFRI_remote_control") + end + return false +end diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_remote_control.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_remote_control/init.lua similarity index 81% rename from drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_remote_control.lua rename to drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_remote_control/init.lua index 0d9f12a697..bc608749b0 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_remote_control.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_remote_control/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 capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -84,9 +73,7 @@ local remote_control = { lifecycle_handlers = { added = added_handler }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "TRADFRI remote control" - end + can_handle = require "zigbee-multi-button.ikea.TRADFRI_remote_control.can_handle" } diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/can_handle.lua new file mode 100644 index 0000000000..9e1b4676f2 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local can_handle_ikea = function(opts, driver, device) + local FINGERPRINTS = require("zigbee-multi-button.ikea.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr then + return true, require("zigbee-multi-button.ikea") + end + end + return false +end + +return can_handle_ikea diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/fingerprints.lua new file mode 100644 index 0000000000..f15d195eef --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local IKEA_MFG = { + { mfr = "IKEA of Sweden" }, + { mfr = "KE" }, + { mfr = "\02KE" } +} + +return IKEA_MFG diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/init.lua index 9a66a85991..97de983eff 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/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 capabilities = require "st.capabilities" local constants = require "st.zigbee.constants" @@ -30,20 +20,7 @@ local Groups = clusters.Groups local ENTRIES_READ = "ENTRIES_READ" -local IKEA_MFG = { - { mfr = "IKEA of Sweden" }, - { mfr = "KE" }, - { mfr = "\02KE" } -} -local can_handle_ikea = function(opts, driver, device) - for _, fingerprint in ipairs(IKEA_MFG) do - if device:get_manufacturer() == fingerprint.mfr then - return true - end - end - return false -end local do_configure = function(self, device) device:send(device_management.build_bind_request(device, PowerConfiguration.ID, self.environment_info.hub_zigbee_eui)) @@ -140,12 +117,8 @@ local ikea_of_sweden = { } } }, - sub_drivers = { - require("zigbee-multi-button.ikea.TRADFRI_remote_control"), - require("zigbee-multi-button.ikea.TRADFRI_on_off_switch"), - require("zigbee-multi-button.ikea.TRADFRI_open_close_remote") - }, - can_handle = can_handle_ikea + sub_drivers = require("zigbee-multi-button.ikea.sub_drivers"), + can_handle = require("zigbee-multi-button.ikea.can_handle"), } return ikea_of_sweden diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/sub_drivers.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/sub_drivers.lua new file mode 100644 index 0000000000..6d7d567775 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/ikea/sub_drivers.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_sub_driver = require "lazy_load_subdriver" + +local sub_drivers = { + lazy_load_sub_driver("zigbee-multi-button.ikea.TRADFRI_remote_control"), + lazy_load_sub_driver("zigbee-multi-button.ikea.TRADFRI_on_off_switch"), + lazy_load_sub_driver("zigbee-multi-button.ikea.TRADFRI_open_close_remote") +} + +return sub_drivers diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/init.lua index 539b03785b..84dc2af26e 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/init.lua @@ -1,67 +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 supported_values = require "zigbee-multi-button.supported_values" local button_utils = require "button_utils" -local ZIGBEE_MULTI_BUTTON_FINGERPRINTS = { - { mfr = "CentraLite", model = "3450-L" }, - { mfr = "CentraLite", model = "3450-L2" }, - { mfr = "AduroSmart Eria", model = "ADUROLIGHT_CSC" }, - { mfr = "ADUROLIGHT", model = "ADUROLIGHT_CSC" }, - { mfr = "AduroSmart Eria", model = "Adurolight_NCC" }, - { mfr = "ADUROLIGHT", model = "Adurolight_NCC" }, - { mfr = "HEIMAN", model = "SceneSwitch-EM-3.0" }, - { mfr = "HEIMAN", model = "HS6SSA-W-EF-3.0" }, - { mfr = "HEIMAN", model = "HS6SSB-W-EF-3.0" }, - { mfr = "IKEA of Sweden", model = "TRADFRI on/off switch" }, - { mfr = "IKEA of Sweden", model = "TRADFRI open/close remote" }, - { mfr = "IKEA of Sweden", model = "TRADFRI remote control" }, - { mfr = "KE", model = "TRADFRI open/close remote" }, - { mfr = "\x02KE", model = "TRADFRI open/close remote" }, - { mfr = "SOMFY", model = "Situo 1 Zigbee" }, - { mfr = "SOMFY", model = "Situo 4 Zigbee" }, - { mfr = "LDS", model = "ZBT-CCTSwitch-D0001" }, - { mfr = "ShinaSystem", model = "MSM-300Z" }, - { mfr = "ShinaSystem", model = "BSM-300Z" }, - { mfr = "ShinaSystem", model = "SBM300ZB1" }, - { mfr = "ShinaSystem", model = "SBM300ZB2" }, - { mfr = "ShinaSystem", model = "SBM300ZB3" }, - { mfr = "ShinaSystem", model = "SBM300ZC1" }, - { mfr = "ShinaSystem", model = "SBM300ZC2" }, - { mfr = "ShinaSystem", model = "SBM300ZC3" }, - { mfr = "ShinaSystem", model = "SBM300ZC4" }, - { mfr = "ShinaSystem", model = "SQM300ZC4" }, - { mfr = "ROBB smarrt", model = "ROB_200-007-0" }, - { mfr = "ROBB smarrt", model = "ROB_200-008-0" }, - { mfr = "WALL HERO", model = "ACL-401SCA4" }, - { mfr = "Samsung Electronics", model = "SAMSUNG-ITM-Z-005" }, - { mfr = "Vimar", model = "RemoteControl_v1.0" }, - { mfr = "Linxura", model = "Smart Controller" }, - { mfr = "Linxura", model = "Aura Smart Button" }, - { mfr = "zunzunbee", model = "SSWZ8T" } -} -local function can_handle_zigbee_multi_button(opts, driver, device, ...) - for _, fingerprint in ipairs(ZIGBEE_MULTI_BUTTON_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local function added_handler(self, device) local config = supported_values.get_device_parameters(device) @@ -87,22 +33,8 @@ local zigbee_multi_button = { lifecycle_handlers = { added = added_handler }, - can_handle = can_handle_zigbee_multi_button, - sub_drivers = { - require("zigbee-multi-button.ikea"), - require("zigbee-multi-button.somfy"), - require("zigbee-multi-button.ecosmart"), - require("zigbee-multi-button.centralite"), - require("zigbee-multi-button.adurosmart"), - require("zigbee-multi-button.heiman"), - require("zigbee-multi-button.shinasystems"), - require("zigbee-multi-button.robb"), - require("zigbee-multi-button.wallhero"), - require("zigbee-multi-button.SLED"), - require("zigbee-multi-button.vimar"), - require("zigbee-multi-button.linxura"), - require("zigbee-multi-button.zunzunbee") - } + can_handle = require("zigbee-multi-button.can_handle"), + sub_drivers = require("zigbee-multi-button.sub_drivers"), } return zigbee_multi_button diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/linxura/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/linxura/can_handle.lua new file mode 100644 index 0000000000..5d8aa4bd2e --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/linxura/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_linxura_button = function(opts, driver, device) + local FINGERPRINTS = require("zigbee-multi-button.linxura.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-multi-button.linxura") + end + end + return false +end + +return is_linxura_button diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/linxura/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/linxura/fingerprints.lua new file mode 100644 index 0000000000..0320a5f2dd --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/linxura/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local LINXURA_BUTTON_FINGERPRINTS = { + { mfr = "Linxura", model = "Smart Controller"}, + { mfr = "Linxura", model = "Aura Smart Button"} +} + +return LINXURA_BUTTON_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/linxura/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/linxura/init.lua index 0e7cf44e93..cb1dd362a2 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/linxura/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/linxura/init.lua @@ -1,25 +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 2024 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local IASZone = (require "st.zigbee.zcl.clusters").IASZone local log = require "log" -local LINXURA_BUTTON_FINGERPRINTS = { - { mfr = "Linxura", model = "Smart Controller"}, - { mfr = "Linxura", model = "Aura Smart Button"} -} local configuration = { { @@ -31,14 +17,6 @@ local configuration = { reportable_change = 1 } } -local is_linxura_button = function(opts, driver, device) - for _, fingerprint in ipairs(LINXURA_BUTTON_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local function present_value_attr_handler(driver, device, zone_status, zb_rx) log.info("present_value_attr_handler The current value is: ", zone_status.value) @@ -84,7 +62,7 @@ local linxura_device_handler = { } }, - can_handle = is_linxura_button + can_handle = require("zigbee-multi-button.linxura.can_handle"), } return linxura_device_handler diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/robb/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/robb/can_handle.lua new file mode 100644 index 0000000000..7b30164eaa --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/robb/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle(opts, driver, device, ...) + local ROBB_MFR_STRING = "ROBB smarrt" + local WIRELESS_REMOTE_FINGERPRINTS = require "zigbee-multi-button.robb.fingerprints" + + if device:get_manufacturer() == ROBB_MFR_STRING and WIRELESS_REMOTE_FINGERPRINTS[device:get_model()] then + return true, require("zigbee-multi-button.robb") + else + return false + end +end + +return can_handle diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/robb/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/robb/fingerprints.lua new file mode 100644 index 0000000000..0b9e1b3f51 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/robb/fingerprints.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local WIRELESS_REMOTE_FINGERPRINTS = { + ["ROB_200-008-0"] = { + endpoints = 2, + buttons = 4 + }, + ["ROB_200-007-0"] = { + endpoints = 4, + buttons = 8 + } +} + +return WIRELESS_REMOTE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/robb/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/robb/init.lua index dddef6f595..4aca7d95aa 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/robb/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/robb/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 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" @@ -18,7 +21,6 @@ Each button-row represents one endpoint. The 8x remote control has four endpoint That means each endpoint has two buttons. --]] -local ROBB_MFR_STRING = "ROBB smarrt" local WIRELESS_REMOTE_FINGERPRINTS = { ["ROB_200-008-0"] = { endpoints = 2, @@ -30,13 +32,6 @@ local WIRELESS_REMOTE_FINGERPRINTS = { } } -local function can_handle(opts, driver, device, ...) - if device:get_manufacturer() == ROBB_MFR_STRING and WIRELESS_REMOTE_FINGERPRINTS[device:get_model()] then - return true - else - return false - end -end local button_push_handler = function(addF) return function(driver, device, zb_rx) @@ -186,7 +181,7 @@ local robb_wireless_control = { } } }, - can_handle = can_handle + can_handle = require("zigbee-multi-button.robb.can_handle"), } return robb_wireless_control diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/shinasystems/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/shinasystems/can_handle.lua new file mode 100644 index 0000000000..d62ba886ad --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/shinasystems/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_shinasystem_button = function(opts, driver, device) + local FINGERPRINTS = require("zigbee-multi-button.shinasystems.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-multi-button.shinasystems") + end + end + return false +end + +return is_shinasystem_button diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/shinasystems/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/shinasystems/fingerprints.lua new file mode 100644 index 0000000000..5d0d1abb6f --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/shinasystems/fingerprints.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local SHINASYSTEM_BUTTON_FINGERPRINTS = { + { mfr = "ShinaSystem", model = "MSM-300Z", endpoint_num = 0x04 }, + { mfr = "ShinaSystem", model = "BSM-300Z", endpoint_num = 0x01 }, + { mfr = "ShinaSystem", model = "SBM300ZB1", endpoint_num = 0x01 }, + { mfr = "ShinaSystem", model = "SBM300ZB2", endpoint_num = 0x02 }, + { mfr = "ShinaSystem", model = "SBM300ZB3", endpoint_num = 0x03 }, + { mfr = "ShinaSystem", model = "SBM300ZC1", endpoint_num = 0x01 }, + { mfr = "ShinaSystem", model = "SBM300ZC2", endpoint_num = 0x02 }, + { mfr = "ShinaSystem", model = "SBM300ZC3", endpoint_num = 0x03 }, + { mfr = "ShinaSystem", model = "SBM300ZC4", endpoint_num = 0x04 }, + { mfr = "ShinaSystem", model = "SQM300ZC4", endpoint_num = 0x04 } +} + +return SHINASYSTEM_BUTTON_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/shinasystems/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/shinasystems/init.lua index 33528b3641..5d5b920db9 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/shinasystems/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/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 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -20,30 +10,11 @@ local OnOff = clusters.OnOff local device_management = require "st.zigbee.device_management" local Groups = clusters.Groups -local SHINASYSTEM_BUTTON_FINGERPRINTS = { - { mfr = "ShinaSystem", model = "MSM-300Z", endpoint_num = 0x04 }, - { mfr = "ShinaSystem", model = "BSM-300Z", endpoint_num = 0x01 }, - { mfr = "ShinaSystem", model = "SBM300ZB1", endpoint_num = 0x01 }, - { mfr = "ShinaSystem", model = "SBM300ZB2", endpoint_num = 0x02 }, - { mfr = "ShinaSystem", model = "SBM300ZB3", endpoint_num = 0x03 }, - { mfr = "ShinaSystem", model = "SBM300ZC1", endpoint_num = 0x01 }, - { mfr = "ShinaSystem", model = "SBM300ZC2", endpoint_num = 0x02 }, - { mfr = "ShinaSystem", model = "SBM300ZC3", endpoint_num = 0x03 }, - { mfr = "ShinaSystem", model = "SBM300ZC4", endpoint_num = 0x04 }, - { mfr = "ShinaSystem", model = "SQM300ZC4", endpoint_num = 0x04 } -} -local is_shinasystem_button = function(opts, driver, device) - for _, fingerprint in ipairs(SHINASYSTEM_BUTTON_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local function get_ep_num_shinasystem_button(device) - for _, fingerprint in ipairs(SHINASYSTEM_BUTTON_FINGERPRINTS) do + local FINGERPRINTS = require("zigbee-multi-button.shinasystems.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do if device:get_model() == fingerprint.model then return fingerprint.endpoint_num end @@ -92,7 +63,7 @@ local shinasystem_device_handler = { } } }, - can_handle = is_shinasystem_button + can_handle = require("zigbee-multi-button.shinasystems.can_handle"), } return shinasystem_device_handler diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/can_handle.lua new file mode 100644 index 0000000000..55e2919fef --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function somfy_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "SOMFY" then + return true, require("zigbee-multi-button.somfy") + end + return false +end + +return somfy_can_handle diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/init.lua index 7236d22491..f4b7951a8f 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/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 constants = require "st.zigbee.constants" @@ -65,13 +55,8 @@ local somfy = { [mgmt_bind_resp.MGMT_BIND_RESPONSE] = zdo_binding_table_handler } }, - sub_drivers = { - require("zigbee-multi-button.somfy.somfy_situo_1"), - require("zigbee-multi-button.somfy.somfy_situo_4") - }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "SOMFY" - end + sub_drivers = require("zigbee-multi-button.somfy.sub_drivers"), + can_handle = require("zigbee-multi-button.somfy.can_handle"), } return somfy diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_1/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_1/can_handle.lua new file mode 100644 index 0000000000..45584d2f13 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_1/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() == "Situo 1 Zigbee" then + return true, require("zigbee-multi-button.somfy.somfy_situo_1") + end + return false +end diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_1.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_1/init.lua similarity index 78% rename from drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_1.lua rename to drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_1/init.lua index de20408306..b81791038e 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_1.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_1/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 capabilities = require "st.capabilities" local constants = require "st.zigbee.constants" @@ -62,9 +51,7 @@ local somfy_handler = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "Situo 1 Zigbee" - end + can_handle = require("zigbee-multi-button.somfy.somfy_situo_1.can_handle"), } return somfy_handler diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_4/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_4/can_handle.lua new file mode 100644 index 0000000000..15b70ad23e --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_4/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() == "Situo 4 Zigbee" then + return true, require("zigbee-multi-button.somfy.somfy_situo_4") + end + return false +end diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_4.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_4/init.lua similarity index 83% rename from drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_4.lua rename to drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_4/init.lua index cd9c59bf90..ca7892d578 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_4.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/somfy_situo_4/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 capabilities = require "st.capabilities" local constants = require "st.zigbee.constants" @@ -103,9 +92,7 @@ local somfy_situo_4_handler = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "Situo 4 Zigbee" - end + can_handle = require("zigbee-multi-button.somfy.somfy_situo_4.can_handle"), } return somfy_situo_4_handler diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/sub_drivers.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/sub_drivers.lua new file mode 100644 index 0000000000..c1c88d1e7c --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/somfy/sub_drivers.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_subdriver = require "lazy_load_subdriver" + +local sub_drivers = { + lazy_load_subdriver("zigbee-multi-button.somfy.somfy_situo_1"), + lazy_load_subdriver("zigbee-multi-button.somfy.somfy_situo_4"), +} + +return sub_drivers diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sub_drivers.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sub_drivers.lua new file mode 100644 index 0000000000..d8d3611ba3 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sub_drivers.lua @@ -0,0 +1,20 @@ +-- 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-multi-button.ikea"), + lazy_load_if_possible("zigbee-multi-button.somfy"), + lazy_load_if_possible("zigbee-multi-button.ecosmart"), + lazy_load_if_possible("zigbee-multi-button.centralite"), + lazy_load_if_possible("zigbee-multi-button.adurosmart"), + lazy_load_if_possible("zigbee-multi-button.heiman"), + lazy_load_if_possible("zigbee-multi-button.shinasystems"), + lazy_load_if_possible("zigbee-multi-button.robb"), + lazy_load_if_possible("zigbee-multi-button.wallhero"), + lazy_load_if_possible("zigbee-multi-button.SLED"), + lazy_load_if_possible("zigbee-multi-button.vimar"), + lazy_load_if_possible("zigbee-multi-button.linxura"), + lazy_load_if_possible("zigbee-multi-button.zunzunbee"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/supported_values.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/supported_values.lua index 172a7c1ca3..813859f891 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/supported_values.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/supported_values.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 devices = { BUTTON_PUSH_HELD_2 = { diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/vimar/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/vimar/can_handle.lua new file mode 100644 index 0000000000..8317f95893 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/vimar/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function vimar_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Vimar" and device:get_model() == "RemoteControl_v1.0" then + return true, require("zigbee-multi-button.vimar") + end + return false +end + +return vimar_can_handle diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/vimar/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/vimar/init.lua index 3df8cc4d71..aa7ba729a1 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/vimar/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/vimar/init.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 + local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -85,9 +75,7 @@ local vimar_remote_control = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Vimar" and device:get_model() == "RemoteControl_v1.0" - end + can_handle = require("zigbee-multi-button.vimar.can_handle"), } return vimar_remote_control diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/wallhero/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/wallhero/can_handle.lua new file mode 100644 index 0000000000..633871874d --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/wallhero/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_wallhero_button(opts, driver, device, ...) + local FINGERPRINTS = require("zigbee-multi-button.wallhero.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-multi-button.wallhero") + end + end + return false +end + +return can_handle_wallhero_button diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/wallhero/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/wallhero/fingerprints.lua new file mode 100644 index 0000000000..6b6ca8b00f --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/wallhero/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FINGERPRINTS = { + { mfr = "WALL HERO", model = "ACL-401SCA4" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/wallhero/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/wallhero/init.lua index 71b01c84f8..e562aef77d 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/wallhero/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/wallhero/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 log = require "log" @@ -19,18 +9,7 @@ local zcl_clusters = require "st.zigbee.zcl.clusters" local Scenes = zcl_clusters.Scenes -local FINGERPRINTS = { - { mfr = "WALL HERO", model = "ACL-401SCA4" } -} -local function can_handle_wallhero_button(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 scenes_cluster_handler(driver, device, zb_rx) local additional_fields = { @@ -97,7 +76,7 @@ local wallhero_button = { } } }, - can_handle = can_handle_wallhero_button + can_handle = require("zigbee-multi-button.wallhero.can_handle"), } return wallhero_button diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/zunzunbee/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/zunzunbee/can_handle.lua new file mode 100644 index 0000000000..0277725380 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/zunzunbee/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Check if a given device matches the supported fingerprints +local function is_zunzunbee_button(opts, driver, device) + local ZUNZUNBEE_BUTTON_FINGERPRINTS = require "zigbee-multi-button.zunzunbee.fingerprints" + for _, fingerprint in ipairs(ZUNZUNBEE_BUTTON_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-multi-button.zunzunbee") + end + end + return false +end + +return is_zunzunbee_button diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/zunzunbee/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/zunzunbee/fingerprints.lua new file mode 100644 index 0000000000..b893f4c1e6 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/zunzunbee/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- List of supported device fingerprints +local ZUNZUNBEE_BUTTON_FINGERPRINTS = { + { mfr = "zunzunbee", model = "SSWZ8T" } +} + +return ZUNZUNBEE_BUTTON_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/zunzunbee/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/zunzunbee/init.lua index 7bb209a622..e8fa24a63e 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/zunzunbee/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/zunzunbee/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 Tone = capabilities.tone @@ -30,26 +20,11 @@ local battery_config = utils.deep_copy(battery_defaults.default_percentage_confi battery_config.reportable_change = 0x02 battery_config.data_type = clusters.PowerConfiguration.attributes.BatteryVoltage.base_type --- List of supported device fingerprints -local ZUNZUNBEE_BUTTON_FINGERPRINTS = { - { mfr = "zunzunbee", model = "SSWZ8T" } -} - -- Initialize device attributes local function init_handler(self, device) device:add_configured_attribute(battery_config) end --- Check if a given device matches the supported fingerprints -local function is_zunzunbee_button(opts, driver, device) - for _, fingerprint in ipairs(ZUNZUNBEE_BUTTON_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end - -- Generate and emit button events based on IAS Zone status attribute local function generate_button_event_from_zone_status(driver, device, zone_status, zb_rx) local raw_value = tonumber(zone_status.value) @@ -163,7 +138,7 @@ local zunzunbee_device_handler = { } } }, - can_handle = is_zunzunbee_button + can_handle = require("zigbee-multi-button.zunzunbee.can_handle"), } return zunzunbee_device_handler From 84747bd4b6162caca6c28f58d6c90f02909b0990 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:10:23 -0600 Subject: [PATCH 100/538] Matter Switch: Include Ikea subdriver support for knob capability (#2678) --- .../matter-switch/profiles/ikea-scroll.yml | 6 + .../src/sub_drivers/ikea_scroll/init.lua | 10 + .../scroll_handlers/event_handlers.lua | 58 +++ .../scroll_utils/device_configuration.lua | 11 +- .../ikea_scroll/scroll_utils/fields.lua | 29 +- .../ikea_scroll/scroll_utils/utils.lua | 18 +- .../matter-switch/src/switch_utils/utils.lua | 20 +- .../src/test/test_ikea_scroll.lua | 415 +++++++++++++++++- 8 files changed, 545 insertions(+), 22 deletions(-) create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml index 4dc77d026d..cce4b68d2d 100644 --- a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml +++ b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml @@ -5,6 +5,8 @@ components: capabilities: - id: button version: 1 + - id: knob + version: 1 - id: battery version: 1 - id: firmwareUpdate @@ -18,6 +20,8 @@ components: capabilities: - id: button version: 1 + - id: knob + version: 1 categories: - name: RemoteController - id: group3 @@ -25,5 +29,7 @@ components: capabilities: - id: button version: 1 + - id: knob + version: 1 categories: - name: RemoteController 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 index 6e21a56ac0..5c2a009de1 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua @@ -1,9 +1,11 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local clusters = require "st.matter.clusters" 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 event_handlers = require "sub_drivers.ikea_scroll.scroll_handlers.event_handlers" local IkeaScrollLifecycleHandlers = {} @@ -44,6 +46,14 @@ local ikea_scroll_handler = { infoChanged = IkeaScrollLifecycleHandlers.info_changed, init = IkeaScrollLifecycleHandlers.device_init, }, + matter_handlers = { + event = { + [clusters.Switch.ID] = { + [clusters.Switch.events.MultiPressOngoing.ID] = event_handlers.multi_press_ongoing_handler, + [clusters.Switch.events.MultiPressComplete.ID] = event_handlers.multi_press_complete_handler, + } + } + }, can_handle = require("sub_drivers.ikea_scroll.can_handle") } diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua new file mode 100644 index 0000000000..7415da8f9b --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua @@ -0,0 +1,58 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local st_utils = require "st.utils" +local capabilities = require "st.capabilities" +local switch_utils = require "switch_utils.utils" +local generic_event_handlers = require "switch_handlers.event_handlers" +local scroll_fields = require "sub_drivers.ikea_scroll.scroll_utils.fields" + +local IkeaScrollEventHandlers = {} + +local function rotate_amount_event_helper(device, endpoint_id, num_presses_to_handle) + -- to cut down on checks, we can assume that if the endpoint is not in ENDPOINTS_UP_SCROLL, it is in ENDPOINTS_DOWN_SCROLL + local scroll_direction = switch_utils.tbl_contains(scroll_fields.ENDPOINTS_UP_SCROLL, endpoint_id) and 1 or -1 + local scroll_amount = st_utils.clamp_value(scroll_direction * scroll_fields.PER_SCROLL_EVENT_ROTATION * num_presses_to_handle, -100, 100) + device:emit_event_for_endpoint(endpoint_id, capabilities.knob.rotateAmount(scroll_amount, {state_change = true})) +end + +-- Used by ENDPOINTS_UP_SCROLL and ENDPOINTS_DOWN_SCROLL, not ENDPOINTS_PUSH +function IkeaScrollEventHandlers.multi_press_ongoing_handler(driver, device, ib, response) + if switch_utils.tbl_contains(scroll_fields.ENDPOINTS_PUSH, ib.endpoint_id) then + -- Ignore MultiPressOngoing events from push endpoints. + device.log.debug("Received MultiPressOngoing event from push endpoint, ignoring.") + else + local cur_num_presses_counted = ib.data and ib.data.elements and ib.data.elements.current_number_of_presses_counted.value or 0 + local num_presses_to_handle = cur_num_presses_counted - (device:get_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED) or 0) + if num_presses_to_handle > 0 then + device:set_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED, cur_num_presses_counted) + rotate_amount_event_helper(device, ib.endpoint_id, num_presses_to_handle) + end + end +end + +function IkeaScrollEventHandlers.multi_press_complete_handler(driver, device, ib, response) + if switch_utils.tbl_contains(scroll_fields.ENDPOINTS_PUSH, ib.endpoint_id) then + generic_event_handlers.multi_press_complete_handler(driver, device, ib, response) + else + local total_num_presses_counted = ib.data and ib.data.elements and ib.data.elements.total_number_of_presses_counted.value or 0 + local num_presses_to_handle = total_num_presses_counted - (device:get_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED) or 0) + if num_presses_to_handle > 0 then + rotate_amount_event_helper(device, ib.endpoint_id, num_presses_to_handle) + end + -- reset the LATEST_NUMBER_OF_PRESSES_COUNTED to nil at the end of a MultiPress chain. + device:set_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED, nil) + end +end + +function IkeaScrollEventHandlers.initial_press_handler(driver, device, ib, response) + if switch_utils.tbl_contains(scroll_fields.ENDPOINTS_PUSH, ib.endpoint_id) then + generic_event_handlers.initial_press_handler(driver, device, ib, response) + else + -- Ignore InitialPress events from non-push endpoints. Presently, we want to solely + -- rely on MultiPressOngoing events to handle rotation for those endpoints. + device.log.debug("Received InitialPress event from scroll endpoint, ignoring.") + end +end + +return IkeaScrollEventHandlers 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 index cd2cee49ce..457804d495 100644 --- 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 @@ -11,19 +11,22 @@ 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], + main = {scroll_fields.ENDPOINTS_PUSH[1], scroll_fields.ENDPOINTS_UP_SCROLL[1], scroll_fields.ENDPOINTS_DOWN_SCROLL[1]}, + group2 = {scroll_fields.ENDPOINTS_PUSH[2], scroll_fields.ENDPOINTS_UP_SCROLL[2], scroll_fields.ENDPOINTS_DOWN_SCROLL[2]}, + group3 = {scroll_fields.ENDPOINTS_PUSH[3], scroll_fields.ENDPOINTS_UP_SCROLL[3], scroll_fields.ENDPOINTS_DOWN_SCROLL[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 + for _, ep in ipairs(scroll_fields.ENDPOINTS_PUSH) 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 + for _, ep in ipairs(scroll_fields.ENDPOINTS_UP_SCROLL) do -- and by extension, ENDPOINTS_DOWN_SCROLL + device:emit_event_for_endpoint(ep, capabilities.knob.supportedAttributes({"rotateAmount"}, {visibility = {displayed = false}})) + end end function IkeaScrollConfiguration.match_profile(driver, device) 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 index fff0a1cce4..58580328b1 100644 --- 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 @@ -1,6 +1,7 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local st_utils = require "st.utils" local clusters = require "st.matter.clusters" local IkeaScrollFields = {} @@ -8,14 +9,36 @@ 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} +-- Generic Switch Endpoints used for basic push functionality +IkeaScrollFields.ENDPOINTS_PUSH = {3, 6, 9} --- Required Events for the ENDPOINTS_PRESS. +-- Generic Switch Endpoints used for Up Scroll functionality +IkeaScrollFields.ENDPOINTS_UP_SCROLL = {1, 4, 7} + +-- Generic Switch Endpoints used for Down Scroll functionality +IkeaScrollFields.ENDPOINTS_DOWN_SCROLL = {2, 5, 8} + +-- Maximum number of presses at a time +IkeaScrollFields.MAX_SCROLL_PRESSES = 18 + +-- Amount to rotate per scroll event +IkeaScrollFields.PER_SCROLL_EVENT_ROTATION = st_utils.round(1 / IkeaScrollFields.MAX_SCROLL_PRESSES * 100) + +-- Field to track the latest number of presses counted during a single scroll event sequence +IkeaScrollFields.LATEST_NUMBER_OF_PRESSES_COUNTED = "__latest_number_of_presses_counted" + +-- Required Events for the ENDPOINTS_PUSH. IkeaScrollFields.switch_press_subscribed_events = { clusters.Switch.events.InitialPress.ID, clusters.Switch.events.MultiPressComplete.ID, clusters.Switch.events.LongPress.ID, } +-- Required Events for the ENDPOINTS_UP_SCROLL and ENDPOINTS_DOWN_SCROLL. Adds a +-- MultiPressOngoing subscription to handle step functionality in real-time +IkeaScrollFields.switch_scroll_subscribed_events = { + clusters.Switch.events.MultiPressOngoing.ID, + clusters.Switch.events.MultiPressComplete.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 index 67ba2acba5..9a9f95228b 100644 --- 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 @@ -7,12 +7,24 @@ 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 +-- override subscribe function in 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 _, ep_push in ipairs(scroll_fields.ENDPOINTS_PUSH) 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) + local ib = im.InteractionInfoBlock(ep_push, clusters.Switch.ID, nil, switch_event) + subscribe_request:with_info_block(ib) + end + end + for _, ep_up in ipairs(scroll_fields.ENDPOINTS_UP_SCROLL) do + for _, switch_event in ipairs(scroll_fields.switch_scroll_subscribed_events) do + local ib = im.InteractionInfoBlock(ep_up, clusters.Switch.ID, nil, switch_event) + subscribe_request:with_info_block(ib) + end + end + for _, ep_down in ipairs(scroll_fields.ENDPOINTS_DOWN_SCROLL) do + for _, switch_event in ipairs(scroll_fields.switch_scroll_subscribed_events) do + local ib = im.InteractionInfoBlock(ep_down, clusters.Switch.ID, nil, switch_event) subscribe_request:with_info_block(ib) end end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index da5376f031..0592d9a342 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -207,7 +207,8 @@ 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. +--- to a single endpoint. This extension also handles the case that multiple endpoints map to the +--- same component --- --- @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) } @@ -220,10 +221,19 @@ function utils.endpoint_to_component(device, ep_info) 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 + elseif type(map_info) == "table" then + if type(map_info.endpoint_id) == "number" then + map_info = {map_info} + end + for _, ep_map_info in ipairs(map_info) do + if type(ep_map_info) == "number" and ep_map_info == ep_info.endpoint_id then + return component + elseif type(ep_map_info) == "table" and ep_map_info.endpoint_id == ep_info.endpoint_id + and (not ep_map_info.cluster_id or (ep_map_info.cluster_id == ep_info.cluster_id + and (not ep_map_info.attribute_ids or utils.tbl_contains(ep_map_info.attribute_ids, ep_info.attribute_id)))) then + return component + end + end end end return "main" diff --git a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua index c560386e37..78f99c37fe 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua @@ -122,18 +122,28 @@ local mock_ikea_scroll = test.mock_device.build_test_matter_device({ } }) -local ENDPOINTS_PRESS = { 3, 6, 9 } +local ENDPOINTS_PUSH = { 3, 6, 9 } +local ENDPOINTS_SCROLL = {1, 2, 4, 5, 7, 8} -- the ikea scroll subdriver has overriden subscribe behavior local function ikea_scroll_subscribe() - local CLUSTER_SUBSCRIBE_LIST ={ - clusters.Switch.events.InitialPress, + local CLUSTER_SUBSCRIBE_LIST_PUSH ={ + 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 + local CLUSTER_SUBSCRIBE_LIST_SCROLL = { + clusters.Switch.server.events.MultiPressOngoing, + clusters.Switch.server.events.MultiPressComplete, + } + local subscribe_request = CLUSTER_SUBSCRIBE_LIST_PUSH[1]:subscribe(mock_ikea_scroll, ENDPOINTS_PUSH[1]) + for _, ep_press in ipairs(ENDPOINTS_PUSH) do + for _, event in ipairs(CLUSTER_SUBSCRIBE_LIST_PUSH) do + subscribe_request:merge(event:subscribe(mock_ikea_scroll, ep_press)) + end + end + for _, ep_press in ipairs(ENDPOINTS_SCROLL) do + for _, event in ipairs(CLUSTER_SUBSCRIBE_LIST_SCROLL) do subscribe_request:merge(event:subscribe(mock_ikea_scroll, ep_press)) end end @@ -149,6 +159,9 @@ local function expect_configure_buttons() 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}))) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("main", capabilities.knob.supportedAttributes({"rotateAmount"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group2", capabilities.knob.supportedAttributes({"rotateAmount"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group3", capabilities.knob.supportedAttributes({"rotateAmount"}, {visibility = {displayed = false}}))) end local function test_init() @@ -221,4 +234,392 @@ test.register_message_test( } ) -test.run_registered_tests() \ No newline at end of file +test.register_message_test( + "Ikea Scroll Positive rotateAmount events on main are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(12, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(18, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {new_position = 5, total_number_of_presses_counted = 5, previous_position = 0} + ) + }, + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(12, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(18, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {new_position = 5, total_number_of_presses_counted = 5, previous_position = 0} + ) + }, + } + } +) + +test.register_message_test( + "Ikea Scroll Negative rotateAmount events on main are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[2], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(-12, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[2], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(-18, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[2], {new_position = 5, total_number_of_presses_counted = 5, previous_position = 0} + ) + }, + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[2], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(-12, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[2], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(-18, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[2], {new_position = 5, total_number_of_presses_counted = 5, previous_position = 0} + ) + }, + } + } +) + +test.register_message_test( + "Ikea Scroll Positive rotateAmount events on group2 are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[3], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(12, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[3], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(18, {state_change = true})) + } + } +) + +test.register_message_test( + "Ikea Scroll Negative rotateAmount events on group2 are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[4], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(-12, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[4], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(-18, {state_change = true})) + } + } +) + +test.register_message_test( + "Ikea Scroll Positive rotateAmount events on group3 are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[5], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(12, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[5], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(18, {state_change = true})) + } + } +) + +test.register_message_test( + "Ikea Scroll Negative rotateAmount events on group3 are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[6], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(-12, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[6], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(-18, {state_change = true})) + } + } +) + +test.register_message_test( + "Ikea Scroll Long Press Push events on main are handled correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_PUSH[1], {new_position = 1} + ) + }, + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_PUSH[1], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.button.button.held({state_change = true})) + }, + } +) + +test.register_message_test( + "Ikea Scroll MultiPressComplete Push events on group2 are handled correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_PUSH[2], {new_position = 1} + ) + }, + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_PUSH[2], {total_number_of_presses_counted = 1, previous_position = 0} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.button.button.pushed({state_change = true})) + }, + } +) + +test.run_registered_tests() From 48b01998512145c9755d03d0381136aa4fada002 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Wed, 11 Feb 2026 15:30:19 -0800 Subject: [PATCH 101/538] WWSTCERT-10319 GELUBU Door Lock S93 --- drivers/SmartThings/zigbee-lock/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/zigbee-lock/fingerprints.yml b/drivers/SmartThings/zigbee-lock/fingerprints.yml index 86870aa962..5c04d78ce5 100644 --- a/drivers/SmartThings/zigbee-lock/fingerprints.yml +++ b/drivers/SmartThings/zigbee-lock/fingerprints.yml @@ -1,4 +1,10 @@ zigbeeManufacturer: + # GELUBU + - id: "GELUBU/S93" + deviceLabel: GELUBU Door Lock S93 + manufacturer: GELUBU + model: S93 + deviceProfileName: lock-battery # YALE - id: "Yale YRD220/240" deviceLabel: "Yale Door Lock" From 2779abc23d10beb89fcb916fb817fcfd9143070b Mon Sep 17 00:00:00 2001 From: Hunsup Jung Date: Tue, 3 Feb 2026 13:59:41 +0900 Subject: [PATCH 102/538] Add vid/pid of the Schlage Sense Pro to new-matter-lock Signed-off-by: Hunsup Jung --- .../SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index bddc7b1fd1..799c20b9ae 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -30,6 +30,7 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x10E1, 0x2002}, -- VDA {0x1421, 0x0042}, -- Kwikset Halo Select Plus {0x1421, 0x0081}, -- Kwikset Aura Reach + {0x1236, 0xa538}, -- Schlage Sense Pro } return NEW_MATTER_LOCK_PRODUCTS From 19588410e5b83f8c9f2703643c83f6891e9dc227 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:11 -0600 Subject: [PATCH 103/538] CHAD-17077: zigbee-thermostat lazy load subdrivers --- .../src/aqara/can_handle.lua | 14 ++++++++ .../src/aqara/fingerprints.lua | 8 +++++ .../zigbee-thermostat/src/aqara/init.lua | 31 +++------------- .../src/danfoss/can_handle.lua | 14 ++++++++ .../src/danfoss/fingerprints.lua | 8 +++++ .../zigbee-thermostat/src/danfoss/init.lua | 18 +++------- .../src/fidure/can_handle.lua | 11 ++++++ .../zigbee-thermostat/src/fidure/init.lua | 20 +++-------- .../zigbee-thermostat/src/init.lua | 31 +++------------- .../src/lazy_load_subdriver.lua | 15 ++++++++ .../src/leviton/can_handle.lua | 11 ++++++ .../zigbee-thermostat/src/leviton/init.lua | 20 +++-------- .../src/lux-konoz/can_handle.lua | 14 ++++++++ .../src/lux-konoz/fingerprints.lua | 8 +++++ .../zigbee-thermostat/src/lux-konoz/init.lua | 29 +++------------ .../zigbee-thermostat/src/popp/can_handle.lua | 14 ++++++++ .../src/popp/fingerprints.lua | 15 ++++++++ .../zigbee-thermostat/src/popp/init.lua | 36 +++---------------- .../src/resideo_korea/can_handle.lua | 11 ++++++ .../src/resideo_korea/init.lua | 20 +++-------- .../src/sinope/can_handle.lua | 13 +++++++ .../zigbee-thermostat/src/sinope/init.lua | 27 +++----------- .../can_handle.lua | 14 ++++++++ .../fingerprints.lua | 9 +++++ .../src/stelpro-ki-zigbee-thermostat/init.lua | 30 +++------------- .../src/stelpro/can_handle.lua | 14 ++++++++ .../src/stelpro/fingerprints.lua | 10 ++++++ .../zigbee-thermostat/src/stelpro/init.lua | 33 +++-------------- .../stelpro_maestrostat/can_handle.lua | 14 ++++++++ .../stelpro_maestrostat/fingerprints.lua | 6 ++++ .../init.lua} | 30 ++-------------- .../src/stelpro/stelpro_sorb/can_handle.lua | 14 ++++++++ .../src/stelpro/stelpro_sorb/fingerprints.lua | 7 ++++ .../init.lua} | 31 ++-------------- .../src/stelpro/sub_drivers.lua | 9 +++++ .../zigbee-thermostat/src/sub_drivers.lua | 19 ++++++++++ .../src/test/test_aqara_thermostat.lua | 15 ++------ .../src/test/test_centralite_thermostat.lua | 15 ++------ .../src/test/test_danfoss_thermostat.lua | 5 ++- .../src/test/test_fidure_thermostat.lua | 15 ++------ .../src/test/test_leviton_rc.lua | 15 ++------ .../src/test/test_popp_thermostat.lua | 3 ++ .../src/test/test_resideo_dt300st_m000.lua | 15 ++------ .../test/test_sinope_th1300_thermostat.lua | 15 ++------ .../test/test_sinope_th1400_thermostat.lua | 15 ++------ .../src/test/test_sinope_thermostat.lua | 15 ++------ .../test_stelpro_ki_zigbee_thermostat.lua | 15 ++------ .../src/test/test_stelpro_thermostat.lua | 15 ++------ .../src/test/test_vimar_thermostat.lua | 15 ++------ .../src/test/test_zenwithin_thermostat.lua | 15 ++------ .../src/test/test_zigbee_thermostat.lua | 15 ++------ .../src/vimar/can_handle.lua | 17 +++++++++ .../zigbee-thermostat/src/vimar/init.lua | 27 +++----------- .../src/zenwithin/can_handle.lua | 11 ++++++ .../zigbee-thermostat/src/zenwithin/init.lua | 20 +++-------- 55 files changed, 394 insertions(+), 512 deletions(-) create mode 100644 drivers/SmartThings/zigbee-thermostat/src/aqara/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/aqara/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/danfoss/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/danfoss/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/fidure/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/leviton/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/lux-konoz/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/lux-konoz/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/popp/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/popp/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/resideo_korea/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/sinope/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/stelpro/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/stelpro/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat/fingerprints.lua rename drivers/SmartThings/zigbee-thermostat/src/stelpro/{stelpro_maestrostat.lua => stelpro_maestrostat/init.lua} (67%) create mode 100644 drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb/fingerprints.lua rename drivers/SmartThings/zigbee-thermostat/src/stelpro/{stelpro_sorb.lua => stelpro_sorb/init.lua} (66%) create mode 100644 drivers/SmartThings/zigbee-thermostat/src/stelpro/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/vimar/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/zenwithin/can_handle.lua diff --git a/drivers/SmartThings/zigbee-thermostat/src/aqara/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/aqara/can_handle.lua new file mode 100644 index 0000000000..e4453597ed --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/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-thermostat/src/aqara/fingerprints.lua b/drivers/SmartThings/zigbee-thermostat/src/aqara/fingerprints.lua new file mode 100644 index 0000000000..30f1243e76 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/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.airrtc.agl001" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-thermostat/src/aqara/init.lua b/drivers/SmartThings/zigbee-thermostat/src/aqara/init.lua index 662d337b82..b17b3d9e1c 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/aqara/init.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 + local data_types = require "st.zigbee.data_types" local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" @@ -34,9 +24,6 @@ local PRIVATE_ANTIFREEZE_MODE_TEMPERATURE_SETTING_ID = 0x0279 local PRIVATE_VALVE_RESULT_CALIBRATION_ID = 0x027B local PRIVATE_BATTERY_ENERGY_ID = 0x040A -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.airrtc.agl001" } -} local preference_map = { ["stse.notificationOfValveTest"] = { @@ -82,14 +69,6 @@ local function device_info_changed(driver, device, event, args) end end -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 supported_thermostat_modes_handler(driver, device, value) device:emit_event(capabilities.thermostatMode.supportedThermostatModes({ @@ -277,7 +256,7 @@ local aqara_radiator_thermostat_e1_handler = { [capabilities.refresh.commands.refresh.NAME] = do_refresh, } }, - can_handle = is_aqara_products + can_handle = require("aqara.can_handle"), } -return aqara_radiator_thermostat_e1_handler \ No newline at end of file +return aqara_radiator_thermostat_e1_handler diff --git a/drivers/SmartThings/zigbee-thermostat/src/danfoss/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/danfoss/can_handle.lua new file mode 100644 index 0000000000..2f9ba97b72 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/danfoss/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_danfoss_thermostat = function(opts, driver, device) + local FINGERPRINTS = require("danfoss.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("danfoss") + end + end + return false +end + +return is_danfoss_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/danfoss/fingerprints.lua b/drivers/SmartThings/zigbee-thermostat/src/danfoss/fingerprints.lua new file mode 100644 index 0000000000..26386b21a9 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/danfoss/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local DANFOSS_THERMOSTAT_FINGERPRINTS = { + { mfr = "Danfoss", model = "eTRV0100" } +} + +return DANFOSS_THERMOSTAT_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-thermostat/src/danfoss/init.lua b/drivers/SmartThings/zigbee-thermostat/src/danfoss/init.lua index c5b181955f..f5b0d5434c 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/danfoss/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/danfoss/init.lua @@ -1,19 +1,11 @@ +-- 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 PowerConfiguration = clusters.PowerConfiguration -local DANFOSS_THERMOSTAT_FINGERPRINTS = { - { mfr = "Danfoss", model = "eTRV0100" } -} -local is_danfoss_thermostat = function(opts, driver, device) - for _, fingerprint in ipairs(DANFOSS_THERMOSTAT_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local danfoss_thermostat = { NAME = "Danfoss Thermostat Handler", @@ -27,7 +19,7 @@ local danfoss_thermostat = { lifecycle_handlers = { init = battery_defaults.build_linear_voltage_init(2.4, 3.2) }, - can_handle = is_danfoss_thermostat + can_handle = require("danfoss.can_handle"), } -return danfoss_thermostat \ No newline at end of file +return danfoss_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/fidure/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/fidure/can_handle.lua new file mode 100644 index 0000000000..24ee0ea0e2 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/fidure/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function fidure_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Fidure" and device:get_model() == "A1732R3" then + return true, require("fidure") + end + return false +end + +return fidure_can_handle diff --git a/drivers/SmartThings/zigbee-thermostat/src/fidure/init.lua b/drivers/SmartThings/zigbee-thermostat/src/fidure/init.lua index 8b46c65e73..5d87089aa1 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/fidure/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/fidure/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" @@ -38,9 +28,7 @@ local fidure_thermostat = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Fidure" and device:get_model() == "A1732R3" - end + can_handle = require("fidure.can_handle"), } return fidure_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/init.lua b/drivers/SmartThings/zigbee-thermostat/src/init.lua index 0a5e82350e..b1766e7892 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/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 + -- Zigbee Driver utilities local ZigbeeDriver = require "st.zigbee" @@ -362,20 +352,7 @@ local zigbee_thermostat_driver = { doConfigure = do_configure, added = device_added }, - sub_drivers = { - require("zenwithin"), - require("fidure"), - require("sinope"), - require("stelpro-ki-zigbee-thermostat"), - require("stelpro"), - require("lux-konoz"), - require("leviton"), - require("danfoss"), - require("popp"), - require("vimar"), - require("resideo_korea"), - require("aqara") - }, + sub_drivers = require("sub_drivers"), health_check = false, } diff --git a/drivers/SmartThings/zigbee-thermostat/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-thermostat/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/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-thermostat/src/leviton/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/leviton/can_handle.lua new file mode 100644 index 0000000000..054aaa821b --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/leviton/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function leviton_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "HAI" and device:get_model() == "65A01-1" then + return true, require("leviton") + end + return false +end + +return leviton_can_handle diff --git a/drivers/SmartThings/zigbee-thermostat/src/leviton/init.lua b/drivers/SmartThings/zigbee-thermostat/src/leviton/init.lua index 9b1150ae25..4b35916229 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/leviton/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/leviton/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 Thermostat = clusters.Thermostat @@ -128,9 +118,7 @@ local leviton_thermostat = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "HAI" and device:get_model() == "65A01-1" - end + can_handle = require("leviton.can_handle"), } return leviton_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/lux-konoz/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/lux-konoz/can_handle.lua new file mode 100644 index 0000000000..b7f7e5220f --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/lux-konoz/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_lux_konoz = function(opts, driver, device) + local FINGERPRINTS = require("lux-konoz.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("lux-konoz") + end + end + return false +end + +return is_lux_konoz diff --git a/drivers/SmartThings/zigbee-thermostat/src/lux-konoz/fingerprints.lua b/drivers/SmartThings/zigbee-thermostat/src/lux-konoz/fingerprints.lua new file mode 100644 index 0000000000..01bb0bac8c --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/lux-konoz/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local LUX_KONOZ_THERMOSTAT_FINGERPRINTS = { + { mfr = "LUX", model = "KONOZ" } +} + +return LUX_KONOZ_THERMOSTAT_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-thermostat/src/lux-konoz/init.lua b/drivers/SmartThings/zigbee-thermostat/src/lux-konoz/init.lua index 815639985c..0609128ac2 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/lux-konoz/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/lux-konoz/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 Thermostat = clusters.Thermostat @@ -19,18 +9,7 @@ local capabilities = require "st.capabilities" local ThermostatMode = capabilities.thermostatMode -local LUX_KONOZ_THERMOSTAT_FINGERPRINTS = { - { mfr = "LUX", model = "KONOZ" } -} -local is_lux_konoz = function(opts, driver, device) - for _, fingerprint in ipairs(LUX_KONOZ_THERMOSTAT_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end -- LUX KONOz reports extra ["auto", "emergency heat"] which, actually, aren't supported local supported_thermostat_modes_handler = function(driver, device, supported_modes) @@ -46,7 +25,7 @@ local lux_konoz = { } } }, - can_handle = is_lux_konoz + can_handle = require("lux-konoz.can_handle"), } return lux_konoz diff --git a/drivers/SmartThings/zigbee-thermostat/src/popp/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/popp/can_handle.lua new file mode 100644 index 0000000000..907350cd1c --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/popp/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_popp_thermostat = function(opts, driver, device) + local FINGERPRINTS = require("popp.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("popp") + end + end + return false +end + +return is_popp_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/popp/fingerprints.lua b/drivers/SmartThings/zigbee-thermostat/src/popp/fingerprints.lua new file mode 100644 index 0000000000..16a5cc0942 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/popp/fingerprints.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local POPP_THERMOSTAT_FINGERPRINTS = { + { + mfr = "D5X84YU", + model = "eT093WRO" + }, + { + mfr = "D5X84YU", + model = "eT093WRG" + } +} + +return POPP_THERMOSTAT_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-thermostat/src/popp/init.lua b/drivers/SmartThings/zigbee-thermostat/src/popp/init.lua index 007fb722b0..f842fe8c2b 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/popp/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/popp/init.lua @@ -1,17 +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. --- Zigbee driver utilities +-- Copyright 2023 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" local data_types = require "st.zigbee.data_types" @@ -34,14 +23,6 @@ local ThermostatMode = capabilities.thermostatMode local TemperatureAlarm = capabilities.temperatureAlarm local Switch = capabilities.switch -local POPP_THERMOSTAT_FINGERPRINTS = { { - mfr = "D5X84YU", - model = "eT093WRO" -}, { - mfr = "D5X84YU", - model = "eT093WRG" -} } - local STORED_HEAT_MODE = "stored_heat_mode" local MFG_CODE = 0x1246 @@ -114,15 +95,6 @@ local PREFERENCE_TABLES = { local SUPPORTED_MODES = { ThermostatMode.thermostatMode.heat.NAME, ThermostatMode.thermostatMode.eco.NAME } -local is_popp_thermostat = function(opts, driver, device) - for _, fingerprint in ipairs(POPP_THERMOSTAT_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end - -- Helpers -- has member check function @@ -425,7 +397,7 @@ local popp_thermostat = { doConfigure = do_configure, infoChanged = info_changed }, - can_handle = is_popp_thermostat + can_handle = require("popp.can_handle"), } return popp_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/resideo_korea/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/resideo_korea/can_handle.lua new file mode 100644 index 0000000000..94eee72e87 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/resideo_korea/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function resideo_korea_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Resideo Korea" and device:get_model() == "DT300ST-M000" then + return true, require("resideo_korea") + end + return false +end + +return resideo_korea_can_handle diff --git a/drivers/SmartThings/zigbee-thermostat/src/resideo_korea/init.lua b/drivers/SmartThings/zigbee-thermostat/src/resideo_korea/init.lua index a1e68ef02a..14221dd223 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/resideo_korea/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/resideo_korea/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 device_lib = require "st.device" local device_management = require "st.zigbee.device_management" @@ -178,9 +168,7 @@ local resideo_thermostat = { [capabilities.refresh.commands.refresh.NAME] = do_refresh } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Resideo Korea" and device:get_model() == "DT300ST-M000" - end + can_handle = require("resideo_korea.can_handle"), } return resideo_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/sinope/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/sinope/can_handle.lua new file mode 100644 index 0000000000..fdc43dab3b --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/sinope/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_sinope_thermostat = 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_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/sinope/init.lua b/drivers/SmartThings/zigbee-thermostat/src/sinope/init.lua index bef41f2b9c..7926b1e0c7 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/sinope/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/sinope/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 clusters = require "st.zigbee.zcl.clusters" @@ -26,8 +16,6 @@ local ThermostatOperatingState = capabilities.thermostatOperatingState local ThermostatHeatingSetpoint = capabilities.thermostatHeatingSetpoint local TemperatureMeasurement = capabilities.temperatureMeasurement -local SINOPE_TECHNOLOGIES_MFR_STRING = "Sinope Technologies" - local SINOPE_CUSTOM_CLUSTER = 0xFF01 local MFR_TIME_FORMAT_ATTRIBUTE = 0x0114 local MFR_AIR_FLOOR_MODE_ATTRIBUTE = 0x0105 @@ -91,13 +79,6 @@ local PREFERENCE_TABLES = { } } -local is_sinope_thermostat = function(opts, driver, device) - if device:get_manufacturer() == SINOPE_TECHNOLOGIES_MFR_STRING then - return true - else - return false - end -end local do_refresh = function(self, device) local attributes = { @@ -176,7 +157,7 @@ local sinope_thermostat = { doConfigure = do_configure, infoChanged = info_changed }, - can_handle = is_sinope_thermostat + can_handle = require("sinope.can_handle"), } return sinope_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/can_handle.lua new file mode 100644 index 0000000000..3f0bebb126 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_stelpro_ki_zigbee_thermostat = function(opts, driver, device) + local FINGERPRINTS = require("stelpro-ki-zigbee-thermostat.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("stelpro-ki-zigbee-thermostat") + end + end + return false +end + +return is_stelpro_ki_zigbee_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/fingerprints.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/fingerprints.lua new file mode 100644 index 0000000000..2cf1a884d7 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local STELPRO_KI_ZIGBEE_THERMOSTAT_FINGERPRINTS = { + { mfr = "Stelpro", model = "STZB402+" }, + { mfr = "Stelpro", model = "ST218" }, +} + +return STELPRO_KI_ZIGBEE_THERMOSTAT_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua index bcc09b271c..7dd1d5f0d2 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-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 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local device_management = require "st.zigbee.device_management" @@ -30,10 +20,6 @@ local ThermostatHeatingSetpoint = capabilities.thermostatHeatingSetpoint local TemperatureMeasurement = capabilities.temperatureMeasurement local TemperatureAlarm = capabilities.temperatureAlarm -local STELPRO_KI_ZIGBEE_THERMOSTAT_FINGERPRINTS = { - { mfr = "Stelpro", model = "STZB402+" }, - { mfr = "Stelpro", model = "ST218" }, -} -- The Groovy DTH stored the raw Celsius values because it was responsible for converting -- to Farenheit if the user's location necessitated. Right now the driver only operates @@ -60,14 +46,6 @@ local THERMOSTAT_MODE_MAP = { [ThermostatSystemMode.EMERGENCY_HEATING] = ThermostatMode.thermostatMode.eco } -local is_stelpro_ki_zigbee_thermostat = function(opts, driver, device) - for _, fingerprint in ipairs(STELPRO_KI_ZIGBEE_THERMOSTAT_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local function has_member(haystack, needle) for _, value in ipairs(haystack) do @@ -342,7 +320,7 @@ local stelpro_ki_zigbee_thermostat = { added = device_added, doConfigure = do_configure }, - can_handle = is_stelpro_ki_zigbee_thermostat + can_handle = require("stelpro-ki-zigbee-thermostat.can_handle"), } return stelpro_ki_zigbee_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro/can_handle.lua new file mode 100644 index 0000000000..117d8ca51d --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_stelpro_thermostat = function(opts, driver, device) + local FINGERPRINTS = require("stelpro.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("stelpro") + end + end + return false +end + +return is_stelpro_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro/fingerprints.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro/fingerprints.lua new file mode 100644 index 0000000000..9271bba198 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local STELPRO_THERMOSTAT_FINGERPRINTS = { + { mfr = "Stelpro", model = "MaestroStat" }, + { mfr = "Stelpro", model = "SORB" }, + { mfr = "Stelpro", model = "SonomaStyle" } +} + +return STELPRO_THERMOSTAT_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro/init.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro/init.lua index 063f423517..d186c46b67 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/stelpro/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro/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" @@ -25,20 +15,7 @@ local RX_HEAT_VALUE = 0x7fff local FREEZE_ALRAM_TEMPERATURE = 0 local HEAT_ALRAM_TEMPERATURE = 50 -local STELPRO_THERMOSTAT_FINGERPRINTS = { - { mfr = "Stelpro", model = "MaestroStat" }, - { mfr = "Stelpro", model = "SORB" }, - { mfr = "Stelpro", model = "SonomaStyle" } -} -local is_stelpro_thermostat = function(opts, driver, device) - for _, fingerprint in ipairs(STELPRO_THERMOSTAT_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local function get_temperature(temperature) return temperature / 100 @@ -128,8 +105,8 @@ local stelpro_thermostat = { added = device_added, infoChanged = info_changed }, - sub_drivers = { require("stelpro.stelpro_sorb"), require("stelpro.stelpro_maestrostat") }, - can_handle = is_stelpro_thermostat + sub_drivers = require("stelpro.sub_drivers"), + can_handle = require("stelpro.can_handle"), } return stelpro_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat/can_handle.lua new file mode 100644 index 0000000000..f9410f80ff --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_stelpro_thermostat = function(opts, driver, device) + local FINGERPRINTS = require "stelpro.stelpro_maestrostat.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("stelpro.stelpro_maestrostat") + end + end + return false +end + +return is_stelpro_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat/fingerprints.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat/fingerprints.lua new file mode 100644 index 0000000000..600fe09180 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "Stelpro", model = "MaestroStat" }, +} diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat/init.lua similarity index 67% rename from drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat.lua rename to drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat/init.lua index c4d81b944a..fecb34666a 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_maestrostat/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 capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -20,19 +9,6 @@ local RelativeHumidity = clusters.RelativeHumidity local Thermostat = clusters.Thermostat local ThermostatUserInterfaceConfiguration = clusters.ThermostatUserInterfaceConfiguration -local STELPRO_THERMOSTAT_FINGERPRINTS = { - { mfr = "Stelpro", model = "MaestroStat" }, -} - -local is_stelpro_thermostat = function(opts, driver, device) - for _, fingerprint in ipairs(STELPRO_THERMOSTAT_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end - local do_refresh = function(self, device) local attributes = { Thermostat.attributes.LocalTemperature, @@ -74,7 +50,7 @@ local stelpro_maestro_othermostat = { added = device_added, doConfigure = do_configure }, - can_handle = is_stelpro_thermostat + can_handle = require("stelpro.stelpro_maestrostat.can_handle") } return stelpro_maestro_othermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb/can_handle.lua new file mode 100644 index 0000000000..d9e99178e5 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_stelpro_sorb_thermostat = function(opts, driver, device) + local FINGERPRINTS = require("stelpro.stelpro_sorb.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("stelpro.stelpro_sorb") + end + end + return false +end + +return is_stelpro_sorb_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb/fingerprints.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb/fingerprints.lua new file mode 100644 index 0000000000..eb8731f6dc --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb/fingerprints.lua @@ -0,0 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "Stelpro", model = "SORB" }, + { mfr = "Stelpro", model = "SonomaStyle" } +} diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb/init.lua similarity index 66% rename from drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb.lua rename to drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb/init.lua index 504d4351e0..39c370960f 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro/stelpro_sorb/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 capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -20,20 +9,6 @@ local RelativeHumidity = clusters.RelativeHumidity local Thermostat = clusters.Thermostat local ThermostatUserInterfaceConfiguration = clusters.ThermostatUserInterfaceConfiguration -local STELPRO_THERMOSTAT_FINGERPRINTS = { - { mfr = "Stelpro", model = "SORB" }, - { mfr = "Stelpro", model = "SonomaStyle" } -} - -local is_stelpro_sorb_thermostat = function(opts, driver, device) - for _, fingerprint in ipairs(STELPRO_THERMOSTAT_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end - local do_refresh = function(self, device) local attributes = { Thermostat.attributes.LocalTemperature, @@ -75,7 +50,7 @@ local stelpro_sorb_thermostat = { added = device_added, doConfigure = do_configure }, - can_handle = is_stelpro_sorb_thermostat + can_handle = require("stelpro.stelpro_sorb.can_handle") } return stelpro_sorb_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro/sub_drivers.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro/sub_drivers.lua new file mode 100644 index 0000000000..aa48c7eda6 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro/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" + +return { + lazy_load_if_possible("stelpro.stelpro_sorb"), + lazy_load_if_possible("stelpro.stelpro_maestrostat") +} diff --git a/drivers/SmartThings/zigbee-thermostat/src/sub_drivers.lua b/drivers/SmartThings/zigbee-thermostat/src/sub_drivers.lua new file mode 100644 index 0000000000..7f62589c24 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/sub_drivers.lua @@ -0,0 +1,19 @@ +-- 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("zenwithin"), + lazy_load_if_possible("fidure"), + lazy_load_if_possible("sinope"), + lazy_load_if_possible("stelpro-ki-zigbee-thermostat"), + lazy_load_if_possible("stelpro"), + lazy_load_if_possible("lux-konoz"), + lazy_load_if_possible("leviton"), + lazy_load_if_possible("danfoss"), + lazy_load_if_possible("popp"), + lazy_load_if_possible("vimar"), + lazy_load_if_possible("resideo_korea"), + lazy_load_if_possible("aqara"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua index 84f305d428..e026edb5f8 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.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 zigbee_test_utils = require "integration_test.zigbee_test_utils" local cluster_base = require "st.zigbee.cluster_base" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_centralite_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_centralite_thermostat.lua index ce735209c4..206333ffa7 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_centralite_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_centralite_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 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-thermostat/src/test/test_danfoss_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_danfoss_thermostat.lua index 51782ec8e5..98727e563e 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_danfoss_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_danfoss_thermostat.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" @@ -104,4 +107,4 @@ test.register_message_test( } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_fidure_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_fidure_thermostat.lua index 6ceb8fdc52..1f7a03f049 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_fidure_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_fidure_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 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-thermostat/src/test/test_leviton_rc.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_leviton_rc.lua index c5a820bdba..6e6cdb0305 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_leviton_rc.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_leviton_rc.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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_popp_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_popp_thermostat.lua index 628dea32eb..ecff7883da 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_popp_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_popp_thermostat.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + require "integration_test" -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua index 0d8365a521..4c7f4bdcaf 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.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" local zigbee_test_utils = require "integration_test.zigbee_test_utils" diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1300_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1300_thermostat.lua index 2411d7638c..6805e6c604 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1300_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1300_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 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-thermostat/src/test/test_sinope_th1400_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1400_thermostat.lua index 88810a3c80..5ca78c3675 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1400_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1400_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 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-thermostat/src/test/test_sinope_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua index 9a5ba8aed5..0717fbb9fa 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_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 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-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua index d0951b2958..afb36720a3 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_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 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-thermostat/src/test/test_stelpro_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_thermostat.lua index ab7f4c19cc..3380856f1c 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_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 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-thermostat/src/test/test_vimar_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_vimar_thermostat.lua index d41d4f23aa..7b2a0cc8f6 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_vimar_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_vimar_thermostat.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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua index bd16d17a62..638f3f7ff4 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_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 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-thermostat/src/test/test_zigbee_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat.lua index 01f1a0ba74..380eacc1ce 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_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 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-thermostat/src/vimar/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/vimar/can_handle.lua new file mode 100644 index 0000000000..3aa0478f46 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/vimar/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local vimar_thermostat_can_handle = function(opts, driver, device) + local VIMAR_THERMOSTAT_FINGERPRINT = { + mfr = "Vimar", + model = "WheelThermostat_v1.0" + } + + if device:get_manufacturer() == VIMAR_THERMOSTAT_FINGERPRINT.mfr and + device:get_model() == VIMAR_THERMOSTAT_FINGERPRINT.model then + return true, require("vimar") + end + return false +end + +return vimar_thermostat_can_handle diff --git a/drivers/SmartThings/zigbee-thermostat/src/vimar/init.lua b/drivers/SmartThings/zigbee-thermostat/src/vimar/init.lua index b4070cf569..7ad274449f 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/vimar/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/vimar/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 device_management = require "st.zigbee.device_management" local clusters = require "st.zigbee.zcl.clusters" @@ -38,11 +28,6 @@ local VIMAR_THERMOSTAT_MODE_MAP = { [ThermostatSystemMode.HEAT] = ThermostatMode.thermostatMode.heat, } -local VIMAR_THERMOSTAT_FINGERPRINT = { - mfr = "Vimar", - model = "WheelThermostat_v1.0" -} - -- NOTE: This is a global variable to use in order to save the current thermostat profile local VIMAR_CURRENT_PROFILE = "_vimarThermostatCurrentProfile" @@ -50,10 +35,6 @@ local VIMAR_THERMOSTAT_HEATING_PROFILE = "thermostat-fanless-heating-no-fw" local VIMAR_THERMOSTAT_COOLING_PROFILE = "thermostat-fanless-cooling-no-fw" -local vimar_thermostat_can_handle = function(opts, driver, device) - return device:get_manufacturer() == VIMAR_THERMOSTAT_FINGERPRINT.mfr and - device:get_model() == VIMAR_THERMOSTAT_FINGERPRINT.model -end local vimar_thermostat_supported_modes_handler = function(driver, device, supported_modes) device:emit_event( @@ -182,7 +163,7 @@ local vimar_thermostat_subdriver = { } }, doConfigure = vimar_thermostat_do_configure, - can_handle = vimar_thermostat_can_handle + can_handle = require("vimar.can_handle"), } return vimar_thermostat_subdriver diff --git a/drivers/SmartThings/zigbee-thermostat/src/zenwithin/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/zenwithin/can_handle.lua new file mode 100644 index 0000000000..818764c88a --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/zenwithin/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function zenwithin_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Zen Within" and device:get_model() == "Zen-01" then + return true, require("zenwithin") + end + return false +end + +return zenwithin_can_handle diff --git a/drivers/SmartThings/zigbee-thermostat/src/zenwithin/init.lua b/drivers/SmartThings/zigbee-thermostat/src/zenwithin/init.lua index e4b11de8be..1b71db2609 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/zenwithin/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/zenwithin/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 utils = require "st.utils" @@ -213,9 +203,7 @@ local zenwithin_thermostat = { infoChanged = info_changed, init = battery_defaults.build_linear_voltage_init(BAT_MIN, BAT_MAX) }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Zen Within" and device:get_model() == "Zen-01" - end + can_handle = require("zenwithin.can_handle"), } return zenwithin_thermostat From 9a714175757dea23539df997566506121325e578 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:27 -0600 Subject: [PATCH 104/538] CHAD-17092: zwave-sensor lazy loading of sub-drivers --- .../src/aeotec-multisensor/can_handle.lua | 15 ++++++ .../src/aeotec-multisensor/fingerprints.lua | 9 ++++ .../src/aeotec-multisensor/init.lua | 37 ++------------ .../multisensor-6/can_handle.lua | 11 ++++ .../aeotec-multisensor/multisensor-6/init.lua | 23 ++------- .../multisensor-7/can_handle.lua | 12 +++++ .../aeotec-multisensor/multisensor-7/init.lua | 24 ++------- .../src/aeotec-multisensor/sub_drivers.lua | 9 ++++ .../src/aeotec-water-sensor/can_handle.lua | 15 ++++++ .../src/aeotec-water-sensor/fingerprints.lua | 11 ++++ .../src/aeotec-water-sensor/init.lua | 34 ++----------- .../src/apiv6_bugfix/can_handle.lua | 35 +++++++++++++ .../zwave-sensor/src/apiv6_bugfix/init.lua | 33 ++---------- .../zwave-sensor/src/configurations.lua | 16 ++---- .../src/enerwave-motion-sensor/can_handle.lua | 12 +++++ .../src/enerwave-motion-sensor/init.lua | 27 ++-------- .../can_handle.lua | 17 +++++++ .../everspring-motion-light-sensor/init.lua | 32 ++---------- .../can_handle.lua | 15 ++++++ .../ezmultipli-multipurpose-sensor/init.lua | 32 +++--------- .../fibaro-door-window-sensor/can_handle.lua | 15 ++++++ .../can_handle.lua | 14 ++++++ .../fingerprints.lua | 8 +++ .../fibaro-door-window-sensor-1/init.lua | 30 ++--------- .../can_handle.lua | 14 ++++++ .../fingerprints.lua | 10 ++++ .../fibaro-door-window-sensor-2/init.lua | 32 ++---------- .../fingerprints.lua | 15 ++++++ .../src/fibaro-door-window-sensor/init.lua | 43 ++-------------- .../fibaro-door-window-sensor/sub_drivers.lua | 9 ++++ .../src/fibaro-flood-sensor/can_handle.lua | 14 ++++++ .../src/fibaro-flood-sensor/init.lua | 30 ++--------- .../src/fibaro-motion-sensor/can_handle.lua | 15 ++++++ .../src/fibaro-motion-sensor/init.lua | 29 ++--------- .../src/firmware-version/can_handle.lua | 21 ++++++++ .../src/firmware-version/init.lua | 34 ++----------- .../can_handle.lua | 20 ++++++++ .../glentronics-water-leak-sensor/init.lua | 36 ++----------- .../src/homeseer-multi-sensor/can_handle.lua | 20 ++++++++ .../src/homeseer-multi-sensor/init.lua | 36 ++----------- drivers/SmartThings/zwave-sensor/src/init.lua | 50 ++----------------- .../zwave-sensor/src/lazy_load_subdriver.lua | 18 +++++++ .../zwave-sensor/src/preferences.lua | 16 ++---- .../src/sensative-strip/can_handle.lua | 14 ++++++ .../zwave-sensor/src/sensative-strip/init.lua | 27 ++-------- .../zwave-sensor/src/sub_drivers.lua | 26 ++++++++++ .../src/test/test_aeon_multisensor.lua | 16 ++---- .../src/test/test_aeotec_multisensor_6.lua | 16 ++---- .../src/test/test_aeotec_multisensor_7.lua | 16 ++---- .../src/test/test_aeotec_multisensor_gen5.lua | 16 ++---- .../src/test/test_aeotec_water_sensor.lua | 16 ++---- .../src/test/test_aeotec_water_sensor_7.lua | 16 ++---- .../src/test/test_enerwave_motion_sensor.lua | 16 ++---- .../src/test/test_everpsring_sp817.lua | 16 ++---- .../src/test/test_everspring_PIR_sensor.lua | 16 ++---- .../src/test/test_everspring_ST814.lua | 16 ++---- .../test_everspring_illuminance_sensor.lua | 16 ++---- .../test_everspring_motion_light_sensor.lua | 16 ++---- .../test_ezmultipli_multipurpose_sensor.lua | 16 ++---- .../test/test_fibaro_door_window_sensor.lua | 16 ++---- .../test/test_fibaro_door_window_sensor_1.lua | 16 ++---- .../test/test_fibaro_door_window_sensor_2.lua | 16 ++---- ...ro_door_window_sensor_with_temperature.lua | 16 ++---- .../src/test/test_fibaro_flood_sensor.lua | 16 ++---- .../src/test/test_fibaro_flood_sensor_zw5.lua | 16 ++---- .../src/test/test_fibaro_motion_sensor.lua | 16 ++---- .../test/test_fibaro_motion_sensor_zw5.lua | 16 ++---- .../src/test/test_generic_sensor.lua | 16 ++---- .../test_glentronics_water_leak_sensor.lua | 16 ++---- .../src/test/test_homeseer_multi_sensor.lua | 16 ++---- .../src/test/test_no_wakeup_poll.lua | 18 ++----- .../src/test/test_sensative_strip.lua | 16 ++---- .../test_smartthings_water_leak_sensor.lua | 16 ++---- .../src/test/test_v1_contact_event.lua | 16 ++---- .../src/test/test_vision_motion_detector.lua | 16 ++---- .../src/test/test_zooz_4_in_1_sensor.lua | 16 ++---- .../test/test_zwave_motion_light_sensor.lua | 16 ++---- .../test_zwave_motion_temp_light_sensor.lua | 16 ++---- .../src/test/test_zwave_sensor.lua | 16 ++---- .../src/test/test_zwave_water_sensor.lua | 16 ++---- .../src/timed-tamper-clear/can_handle.lua | 23 +++++++++ .../src/timed-tamper-clear/init.lua | 36 ++----------- .../src/v1-contact-event/can_handle.lua | 22 ++++++++ .../src/v1-contact-event/init.lua | 31 ++---------- .../src/vision-motion-detector/can_handle.lua | 17 +++++++ .../src/vision-motion-detector/init.lua | 33 ++---------- .../src/wakeup-no-poll/can_handle.lua | 12 +++++ .../zwave-sensor/src/wakeup-no-poll/init.lua | 32 +++--------- .../src/zooz-4-in-1-sensor/can_handle.lua | 15 ++++++ .../src/zooz-4-in-1-sensor/fingerprints.lua | 10 ++++ .../src/zooz-4-in-1-sensor/init.lua | 33 ++---------- .../zwave-water-leak-sensor/can_handle.lua | 15 ++++++ .../zwave-water-leak-sensor/fingerprints.lua | 19 +++++++ .../src/zwave-water-leak-sensor/init.lua | 43 ++-------------- 94 files changed, 742 insertions(+), 1160 deletions(-) create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua new file mode 100644 index 0000000000..99cea3c0b3 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_aeotec_multisensor(opts, self, device, ...) + local FINGERPRINTS = require("aeotec-multisensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("aeotec-multisensor") + return true, subdriver, require("aeotec-multisensor") + end + end + return false +end + +return can_handle_aeotec_multisensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua new file mode 100644 index 0000000000..9436a85979 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_MULTISENSOR_FINGERPRINTS = { + { manufacturerId = 0x0086, productId = 0x0064 }, -- MultiSensor 6 + { manufacturerId = 0x0371, productId = 0x0018 }, -- MultiSensor 7 +} + +return AEOTEC_MULTISENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua index edd01c7553..d1759a41e0 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -18,21 +7,6 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) -local AEOTEC_MULTISENSOR_FINGERPRINTS = { - { manufacturerId = 0x0086, productId = 0x0064 }, -- MultiSensor 6 - { manufacturerId = 0x0371, productId = 0x0018 }, -- MultiSensor 7 -} - -local function can_handle_aeotec_multisensor(opts, self, device, ...) - for _, fingerprint in ipairs(AEOTEC_MULTISENSOR_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("aeotec-multisensor") - return true, subdriver - end - end - return false -end - local function notification_report_handler(self, device, cmd) local event if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then @@ -61,12 +35,9 @@ local aeotec_multisensor = { [Notification.REPORT] = notification_report_handler } }, - sub_drivers = { - require("aeotec-multisensor/multisensor-6"), - require("aeotec-multisensor/multisensor-7") - }, + sub_drivers = require("aeotec-multisensor.sub_drivers"), NAME = "aeotec multisensor", - can_handle = can_handle_aeotec_multisensor + can_handle = require("aeotec-multisensor.can_handle"), } return aeotec_multisensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua new file mode 100644 index 0000000000..d86e9c8b3a --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_multisensor_6(opts, self, device, ...) +local MULTISENSOR_6_PRODUCT_ID = 0x0064 + if device.zwave_product_id == MULTISENSOR_6_PRODUCT_ID then + return true, require("aeotec-multisensor.multisensor-6") + end + return false +end +return can_handle_multisensor_6 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua index 1b9d4d6b97..4174b3b14e 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -19,12 +10,8 @@ local cc = require "st.zwave.CommandClass" local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) local WakeUp = (require "st.zwave.CommandClass.WakeUp")({version = 2}) -local MULTISENSOR_6_PRODUCT_ID = 0x0064 local PREFERENCE_NUM = 9 -local function can_handle_multisensor_6(opts, self, device, ...) - return device.zwave_product_id == MULTISENSOR_6_PRODUCT_ID -end local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher @@ -62,7 +49,7 @@ local multisensor_6 = { } }, NAME = "aeotec multisensor 6", - can_handle = can_handle_multisensor_6 + can_handle = require("aeotec-multisensor.multisensor-6.can_handle"), } return multisensor_6 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua new file mode 100644 index 0000000000..f109d0e31c --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_multisensor_7(opts, self, device, ...) + local MULTISENSOR_7_PRODUCT_ID = 0x0018 + if device.zwave_product_id == MULTISENSOR_7_PRODUCT_ID then + return true, require("aeotec-multisensor.multisensor-7") + end + return false +end + +return can_handle_multisensor_7 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua index 2d2bf4e36e..c3dc69178f 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -19,13 +10,8 @@ local cc = require "st.zwave.CommandClass" local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 2 }) -local MULTISENSOR_7_PRODUCT_ID = 0x0018 local PREFERENCE_NUM = 10 -local function can_handle_multisensor_7(opts, self, device, ...) - return device.zwave_product_id == MULTISENSOR_7_PRODUCT_ID -end - local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher --This is done to help the hub correctly set the checkInterval for migrated devices. @@ -62,7 +48,7 @@ local multisensor_7 = { } }, NAME = "aeotec multisensor 7", - can_handle = can_handle_multisensor_7 + can_handle = require("aeotec-multisensor.multisensor-7.can_handle"), } return multisensor_7 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua new file mode 100644 index 0000000000..396f53fe86 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/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("aeotec-multisensor/multisensor-6"), + lazy_load_if_possible("aeotec-multisensor/multisensor-7"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua new file mode 100644 index 0000000000..1bdf9f12a8 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_zwave_water_temp_humidity_sensor(opts, driver, device, ...) + local FINGERPRINTS = require("aeotec-water-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("aeotec-water-sensor") + return true, subdriver, require("aeotec-water-sensor") + end + end + return false +end + +return can_handle_zwave_water_temp_humidity_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua new file mode 100644 index 0000000000..423d87754e --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS = { + { manufacturerId = 0x0371, productType = 0x0002, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro EU + { manufacturerId = 0x0371, productType = 0x0102, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro US + { manufacturerId = 0x0371, productType = 0x0202, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro AU + { manufacturerId = 0x0371, productId = 0x0012 } -- Aeotec Water Sensor 7 +} + +return ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua index 9d883ea3c2..4c7a86e708 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -18,23 +9,8 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) -local ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS = { - { manufacturerId = 0x0371, productType = 0x0002, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro EU - { manufacturerId = 0x0371, productType = 0x0102, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro US - { manufacturerId = 0x0371, productType = 0x0202, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro AU - { manufacturerId = 0x0371, productId = 0x0012 } -- Aeotec Water Sensor 7 -} --- Determine whether the passed device is zwave water temperature humidiry sensor -local function can_handle_zwave_water_temp_humidity_sensor(opts, driver, device, ...) - for _, fingerprint in ipairs(ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("aeotec-water-sensor") - return true, subdriver - end - end - return false -end --- Default handler for notification command class reports --- @@ -68,7 +44,7 @@ local zwave_water_temp_humidity_sensor = { }, }, NAME = "zwave water temp humidity sensor", - can_handle = can_handle_zwave_water_temp_humidity_sensor + can_handle = require("aeotec-water-sensor.can_handle"), } return zwave_water_temp_humidity_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua new file mode 100644 index 0000000000..4913e9a25e --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua @@ -0,0 +1,35 @@ +-- 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 }) + +-- doing refresh would cause incorrect state for device, see comments in wakeup-no-poll +local NORTEK_FP = {mfr = 0x014F, prod = 0x2001, model = 0x0102} -- NorTek open/close sensor +local POPP_THERMOSTAT_FP = {mfr = 0x0002, prod = 0x0115, model = 0xA010} --Popp thermostat +local AEOTEC_MULTISENSOR_6_FP = {mfr = 0x0086, model = 0x0064} --Aeotec multisensor 6 +local AEOTEC_MULTISENSOR_7_FP = {mfr = 0x0371, model = 0x0018} --Aeotec multisensor 7 +local ENERWAVE_MOTION_FP = {mfr = 0x011A} --Enerwave motion sensor +local HOMESEER_MULTI_SENSOR_FP = {mfr = 0x001E, prod = 0x0002, model = 0x0001} -- Homeseer multi sensor HSM100 +local SENSATIVE_STRIP_FP = {mfr = 0x019A, model = 0x000A} +local FPS = {NORTEK_FP, POPP_THERMOSTAT_FP, + AEOTEC_MULTISENSOR_6_FP, AEOTEC_MULTISENSOR_7_FP, + ENERWAVE_MOTION_FP, HOMESEER_MULTI_SENSOR_FP, SENSATIVE_STRIP_FP} + +local function can_handle(opts, driver, device, cmd, ...) + local version = require "version" + if version.api == 6 and + cmd.cmd_class == cc.WAKE_UP and + cmd.cmd_id == WakeUp.NOTIFICATION then + + for _, fp in ipairs(FPS) do + if device:id_match(fp.mfr, fp.prod, fp.model) then return false end + end + local subdriver = require("apiv6_bugfix") + return true, subdriver + else + return false + end +end + +return can_handle diff --git a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua index 322333d565..94dc5975ab 100644 --- a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua @@ -1,34 +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 }) --- doing refresh would cause incorrect state for device, see comments in wakeup-no-poll -local NORTEK_FP = {mfr = 0x014F, prod = 0x2001, model = 0x0102} -- NorTek open/close sensor -local POPP_THERMOSTAT_FP = {mfr = 0x0002, prod = 0x0115, model = 0xA010} --Popp thermostat -local AEOTEC_MULTISENSOR_6_FP = {mfr = 0x0086, model = 0x0064} --Aeotec multisensor 6 -local AEOTEC_MULTISENSOR_7_FP = {mfr = 0x0371, model = 0x0018} --Aeotec multisensor 7 -local ENERWAVE_MOTION_FP = {mfr = 0x011A} --Enerwave motion sensor -local HOMESEER_MULTI_SENSOR_FP = {mfr = 0x001E, prod = 0x0002, model = 0x0001} -- Homeseer multi sensor HSM100 -local SENSATIVE_STRIP_FP = {mfr = 0x019A, model = 0x000A} -local FPS = {NORTEK_FP, POPP_THERMOSTAT_FP, - AEOTEC_MULTISENSOR_6_FP, AEOTEC_MULTISENSOR_7_FP, - ENERWAVE_MOTION_FP, HOMESEER_MULTI_SENSOR_FP, SENSATIVE_STRIP_FP} - -local function can_handle(opts, driver, device, cmd, ...) - local version = require "version" - if version.api == 6 and - cmd.cmd_class == cc.WAKE_UP and - cmd.cmd_id == WakeUp.NOTIFICATION then - - for _, fp in ipairs(FPS) do - if device:id_match(fp.mfr, fp.prod, fp.model) then return false end - end - local subdriver = require("apiv6_bugfix") - return true, subdriver - else - return false - end -end - local function wakeup_notification(driver, device, cmd) device:refresh() end @@ -40,7 +15,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-sensor/src/configurations.lua b/drivers/SmartThings/zwave-sensor/src/configurations.lua index 2883e70384..0a3c62ead8 100644 --- a/drivers/SmartThings/zwave-sensor/src/configurations.lua +++ b/drivers/SmartThings/zwave-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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass.Configuration diff --git a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua new file mode 100644 index 0000000000..a707d7493a --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_enerwave_motion_sensor(opts, driver, device, cmd, ...) + local ENERWAVE_MFR = 0x011A + if device.zwave_manufacturer_id == ENERWAVE_MFR then + local subdriver = require("enerwave-motion-sensor") + return true, subdriver, require("enerwave-motion-sensor") + else return false end +end + +return can_handle_enerwave_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua index 6fb712e3b0..012a8884a3 100644 --- a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-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 @@ -20,15 +10,6 @@ local Association = (require "st.zwave.CommandClass.Association")({version=2}) --- @type st.zwave.CommandClass.WakeUp local WakeUp = (require "st.zwave.CommandClass.WakeUp")({version=1}) -local ENERWAVE_MFR = 0x011A - -local function can_handle_enerwave_motion_sensor(opts, driver, device, cmd, ...) - if device.zwave_manufacturer_id == ENERWAVE_MFR then - local subdriver = require("enerwave-motion-sensor") - return true, subdriver - else return false end -end - local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher --This is done to help the hub correctly set the checkInterval for migrated devices. @@ -58,7 +39,7 @@ local enerwave_motion_sensor = { doConfigure = do_configure }, NAME = "enerwave_motion_sensor", - can_handle = can_handle_enerwave_motion_sensor + can_handle = require("enerwave-motion-sensor.can_handle") } return enerwave_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua new file mode 100644 index 0000000000..c9fd2eafd1 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_everspring_motion_light(opts, driver, device, ...) + local EVERSPRING_MOTION_LIGHT_FINGERPRINT = { mfr = 0x0060, prod = 0x0012, model = 0x0001 } + if device:id_match( + EVERSPRING_MOTION_LIGHT_FINGERPRINT.mfr, + EVERSPRING_MOTION_LIGHT_FINGERPRINT.prod, + EVERSPRING_MOTION_LIGHT_FINGERPRINT.model + ) then + local subdriver = require("everspring-motion-light-sensor") + return true, subdriver + end + return false +end + +return can_handle_everspring_motion_light diff --git a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua index 1b11aadabe..8baa3756d6 100644 --- a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua @@ -1,34 +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 capabilities = require "st.capabilities" local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=2,strict=true}) local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({version=2}) -local EVERSPRING_MOTION_LIGHT_FINGERPRINT = { mfr = 0x0060, prod = 0x0012, model = 0x0001 } - -local function can_handle_everspring_motion_light(opts, driver, device, ...) - if device:id_match( - EVERSPRING_MOTION_LIGHT_FINGERPRINT.mfr, - EVERSPRING_MOTION_LIGHT_FINGERPRINT.prod, - EVERSPRING_MOTION_LIGHT_FINGERPRINT.model - ) then - local subdriver = require("everspring-motion-light-sensor") - return true, subdriver - else return false end -end - local function device_added(driver, device) device:emit_event(capabilities.motionSensor.motion.inactive()) device:send(SwitchBinary:Get({})) @@ -40,7 +18,7 @@ local everspring_motion_light = { lifecycle_handlers = { added = device_added }, - can_handle = can_handle_everspring_motion_light + can_handle = require("everspring-motion-light-sensor.can_handle"), } return everspring_motion_light diff --git a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua new file mode 100644 index 0000000000..5596894fbb --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_ezmultipli_multipurpose_sensor(opts, driver, device, ...) + local EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0004, productId = 0x0001 } + if device:id_match(EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.manufacturerId, + EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productType, + EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productId) then + local subdriver = require("ezmultipli-multipurpose-sensor") + return true, subdriver + end + return false +end + +return can_handle_ezmultipli_multipurpose_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua index 1e4b3bf0ce..f4cac30faa 100644 --- a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/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 capabilities = require "st.capabilities" --- @type st.utils @@ -28,17 +19,6 @@ local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=2}) local CAP_CACHE_KEY = "st.capabilities." .. capabilities.colorControl.ID -local EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0004, productId = 0x0001 } - -local function can_handle_ezmultipli_multipurpose_sensor(opts, driver, device, ...) - if device:id_match(EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.manufacturerId, - EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productType, - EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productId) then - local subdriver = require("ezmultipli-multipurpose-sensor") - return true, subdriver - else return false end -end - local function basic_report_handler(driver, device, cmd) local event local value = (cmd.args.target_value ~= nil) and cmd.args.target_value or cmd.args.value @@ -102,7 +82,7 @@ local ezmultipli_multipurpose_sensor = { [capabilities.colorControl.commands.setColor.NAME] = set_color } }, - can_handle = can_handle_ezmultipli_multipurpose_sensor + can_handle = require("ezmultipli-multipurpose-sensor.can_handle"), } -return ezmultipli_multipurpose_sensor \ No newline at end of file +return ezmultipli_multipurpose_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua new file mode 100644 index 0000000000..c088fd2b2f --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_door_window_sensor(opts, driver, device, ...) + local FINGERPRINTS = require("fibaro-door-window-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.prod, fingerprint.productId) then + local subdriver = require("fibaro-door-window-sensor") + return true, subdriver, require("fibaro-door-window-sensor") + end + end + return false +end + +return can_handle_fibaro_door_window_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua new file mode 100644 index 0000000000..992ea8fd9d --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_door_window_sensor_1(opts, driver, device, cmd, ...) + local FINGERPRINTS = require("fibaro-door-window-sensor.fibaro-door-window-sensor-1.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("fibaro-door-window-sensor.fibaro-door-window-sensor-1") + end + end + return false +end + +return can_handle_fibaro_door_window_sensor_1 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua new file mode 100644 index 0000000000..50727133bb --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS = { + { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } +} + +return FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua index 698fffcceb..4dbca58919 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/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 capabilities = require "st.capabilities" local cc = require "st.zwave.CommandClass" @@ -21,19 +10,6 @@ local SensorAlarm = (require "st.zwave.CommandClass.SensorAlarm")({ version = 1 local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 1 }) local configurationsMap = require "configurations" -local FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS = { - { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } -} - -local function can_handle_fibaro_door_window_sensor_1(opts, driver, device, cmd, ...) - for _, fingerprint in ipairs(FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -end - local function sensor_alarm_report_handler(driver, device, cmd) if (cmd.args.sensor_state == SensorAlarm.sensor_state.ALARM) then device:emit_event(capabilities.tamperAlert.tamper.detected()) @@ -92,7 +68,7 @@ local fibaro_door_window_sensor_1 = { [capabilities.refresh.ID] = { [capabilities.refresh.commands.refresh.NAME] = do_refresh }, - can_handle = can_handle_fibaro_door_window_sensor_1 + can_handle = require("fibaro-door-window-sensor.fibaro-door-window-sensor-1.can_handle"), } return fibaro_door_window_sensor_1 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua new file mode 100644 index 0000000000..4493496f94 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_door_window_sensor_2(opts, driver, device, cmd, ...) + local FINGERPRINTS = require("fibaro-door-window-sensor.fibaro-door-window-sensor-2.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("fibaro-door-window-sensor.fibaro-door-window-sensor-2") + end + end + return false +end + +return can_handle_fibaro_door_window_sensor_2 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua new file mode 100644 index 0000000000..6103c107d1 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS = { + { manufacturerId = 0x010F, productType = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe + { manufacturerId = 0x010F, productType = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA + { manufacturerId = 0x010F, productType = 0x0702, productId = 0x3000 } -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ +} + +return FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua index 16c5ec2017..250203b0cd 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -18,21 +7,6 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Alarm local Alarm = (require "st.zwave.CommandClass.Alarm")({ version = 2 }) -local FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS = { - { manufacturerId = 0x010F, productType = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe - { manufacturerId = 0x010F, productType = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA - { manufacturerId = 0x010F, productType = 0x0702, productId = 0x3000 } -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ -} - -local function can_handle_fibaro_door_window_sensor_2(opts, driver, device, cmd, ...) - for _, fingerprint in ipairs(FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -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) @@ -83,7 +57,7 @@ local fibaro_door_window_sensor_2 = { lifecycle_handlers = { added = device_added }, - can_handle = can_handle_fibaro_door_window_sensor_2, + can_handle = require("fibaro-door-window-sensor.fibaro-door-window-sensor-2.can_handle"), } return fibaro_door_window_sensor_2 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua new file mode 100644 index 0000000000..699df3f623 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS = { + { manufacturerId = 0x010F, prod = 0x0700, productId = 0x1000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / Europe + { manufacturerId = 0x010F, prod = 0x0700, productId = 0x2000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / NA + { manufacturerId = 0x010F, prod = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe + { manufacturerId = 0x010F, prod = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA + { manufacturerId = 0x010F, prod = 0x0702, productId = 0x3000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ + { manufacturerId = 0x010F, prod = 0x0701, productId = 0x2001 }, -- Fibaro Open/Closed Sensor with temperature (FGK-10X) / NA + { manufacturerId = 0x010F, prod = 0x0701, productId = 0x1001 }, -- Fibaro Open/Closed Sensor + { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } -- Fibaro Open/Closed Sensor +} + +return FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua index 86cf865348..6c30508fd1 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/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 cc = require "st.zwave.CommandClass" local capabilities = require "st.capabilities" @@ -24,27 +13,6 @@ local preferencesMap = require "preferences" local FIBARO_DOOR_WINDOW_SENSOR_WAKEUP_INTERVAL = 21600 --seconds -local FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS = { - { manufacturerId = 0x010F, prod = 0x0700, productId = 0x1000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / Europe - { manufacturerId = 0x010F, prod = 0x0700, productId = 0x2000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / NA - { manufacturerId = 0x010F, prod = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe - { manufacturerId = 0x010F, prod = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA - { manufacturerId = 0x010F, prod = 0x0702, productId = 0x3000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ - { manufacturerId = 0x010F, prod = 0x0701, productId = 0x2001 }, -- Fibaro Open/Closed Sensor with temperature (FGK-10X) / NA - { manufacturerId = 0x010F, prod = 0x0701, productId = 0x1001 }, -- Fibaro Open/Closed Sensor - { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } -- Fibaro Open/Closed Sensor -} - -local function can_handle_fibaro_door_window_sensor(opts, driver, device, ...) - for _, fingerprint in ipairs(FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.prod, fingerprint.productId) then - local subdriver = require("fibaro-door-window-sensor") - return true, subdriver - end - end - return false -end - local function parameterNumberToParameterName(preferences,parameterNumber) for id, parameter in pairs(preferences) do if parameter.parameter_number == parameterNumber then @@ -154,11 +122,8 @@ local fibaro_door_window_sensor = { [capabilities.refresh.commands.refresh.NAME] = do_refresh } }, - sub_drivers = { - require("fibaro-door-window-sensor/fibaro-door-window-sensor-1"), - require("fibaro-door-window-sensor/fibaro-door-window-sensor-2") - }, - can_handle = can_handle_fibaro_door_window_sensor + sub_drivers = require("fibaro-door-window-sensor.sub_drivers"), + can_handle = require("fibaro-door-window-sensor.can_handle"), } return fibaro_door_window_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua new file mode 100644 index 0000000000..0c4ddd4e43 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-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("fibaro-door-window-sensor/fibaro-door-window-sensor-1"), + lazy_load_if_possible("fibaro-door-window-sensor/fibaro-door-window-sensor-2"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua new file mode 100644 index 0000000000..341fbcd6f9 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_flood_sensor(opts, driver, device, ...) + local FIBARO_MFR_ID = 0x010F + local FIBARO_FLOOD_PROD_TYPES = { 0x0000, 0x0B00 } + if device:id_match(FIBARO_MFR_ID, FIBARO_FLOOD_PROD_TYPES, nil) then + local subdriver = require("fibaro-flood-sensor") + return true, subdriver + end + return false +end + +return can_handle_fibaro_flood_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua index 144be985ae..9a0a8e7eaa 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -26,17 +17,6 @@ local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = local preferences = require "preferences" local configurations = require "configurations" -local FIBARO_MFR_ID = 0x010F -local FIBARO_FLOOD_PROD_TYPES = { 0x0000, 0x0B00 } - -local function can_handle_fibaro_flood_sensor(opts, driver, device, ...) - if device:id_match(FIBARO_MFR_ID, FIBARO_FLOOD_PROD_TYPES, nil) then - local subdriver = require("fibaro-flood-sensor") - return true, subdriver - else return false end -end - - local function basic_set_handler(self, device, cmd) local value = cmd.args.target_value and cmd.args.target_value or cmd.args.value device:emit_event(value == 0xFF and capabilities.waterSensor.water.wet() or capabilities.waterSensor.water.dry()) @@ -96,7 +76,7 @@ local fibaro_flood_sensor = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_fibaro_flood_sensor + can_handle = require("fibaro-flood-sensor.can_handle"), } return fibaro_flood_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua new file mode 100644 index 0000000000..cd3f647394 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_motion_sensor(opts, driver, device, ...) + + local FIBARO_MOTION_MFR = 0x010F + local FIBARO_MOTION_PROD = 0x0800 + if device:id_match(FIBARO_MOTION_MFR, FIBARO_MOTION_PROD) then + local subdriver = require("fibaro-motion-sensor") + return true, subdriver, require("fibaro-motion-sensor") + end + return false +end + +return can_handle_fibaro_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua index ae45f5a27b..ed035bde18 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-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 + --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -18,15 +8,6 @@ local cc = require "st.zwave.CommandClass" local SensorAlarm = (require "st.zwave.CommandClass.SensorAlarm")({ version = 1 }) local capabilities = require "st.capabilities" -local FIBARO_MOTION_MFR = 0x010F -local FIBARO_MOTION_PROD = 0x0800 - -local function can_handle_fibaro_motion_sensor(opts, driver, device, ...) - if device:id_match(FIBARO_MOTION_MFR, FIBARO_MOTION_PROD) then - local subdriver = require("fibaro-motion-sensor") - return true, subdriver - else return false end -end local function sensor_alarm_report(driver, device, cmd) if (cmd.args.sensor_state ~= SensorAlarm.sensor_state.NO_ALARM) then @@ -43,7 +24,7 @@ local fibaro_motion_sensor = { [SensorAlarm.REPORT] = sensor_alarm_report } }, - can_handle = can_handle_fibaro_motion_sensor + can_handle = require("fibaro-motion-sensor.can_handle") } -return fibaro_motion_sensor \ No newline at end of file +return fibaro_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua new file mode 100644 index 0000000000..3ecdc2baf0 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua @@ -0,0 +1,21 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" + +--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 +} + +return function(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 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua b/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua index 058a7f955c..528cf7da45 100644 --- a/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/firmware-version/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" --- @type st.zwave.CommandClass @@ -20,22 +10,6 @@ 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) @@ -73,7 +47,7 @@ end local firmware_version = { NAME = "firmware_version", - can_handle = can_handle_fw, + can_handle = require("firmware-version.can_handle"), lifecycle_handlers = { added = added_handler, diff --git a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua new file mode 100644 index 0000000000..e24d7b9cf2 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua @@ -0,0 +1,20 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +--- Determine whether the passed device is glentronics water leak sensor +--- +--- @param driver Driver driver instance +--- @param device Device device isntance +--- @return boolean true if the device proper, else false +local function can_handle_glentronics_water_leak_sensor(opts, driver, device, ...) + local GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS = { manufacturerId = 0x0084, productType = 0x0093, productId = 0x0114 } -- glentronics water leak sensor + if device:id_match( + GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.manufacturerId, + GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productType, + GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productId) then + return true, require("glentronics-water-leak-sensor") + end + return false +end + +return can_handle_glentronics_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua index 3dba7351d6..7400d889b7 100644 --- a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -18,23 +9,6 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) -local GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS = { manufacturerId = 0x0084, productType = 0x0093, productId = 0x0114 } -- glentronics water leak sensor - ---- Determine whether the passed device is glentronics water leak sensor ---- ---- @param driver Driver driver instance ---- @param device Device device isntance ---- @return boolean true if the device proper, else false -local function can_handle_glentronics_water_leak_sensor(opts, driver, device, ...) - if device:id_match( - GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.manufacturerId, - GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productType, - GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productId) then - local subdriver = require("glentronics-water-leak-sensor") - return true, subdriver - else return false end -end - local function notification_report_handler(self, device, cmd) local event if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then @@ -78,7 +52,7 @@ local glentronics_water_leak_sensor = { added = device_added }, NAME = "glentronics water leak sensor", - can_handle = can_handle_glentronics_water_leak_sensor + can_handle = require("glentronics-water-leak-sensor.can_handle"), } return glentronics_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua new file mode 100644 index 0000000000..992c1f7c7f --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua @@ -0,0 +1,20 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +--- Determine whether the passed device is homeseer multi sensor +--- +--- @param driver Driver driver instance +--- @param device Device device instance +--- @return boolean true if the device proper, else false +local function can_handle_homeseer_multi_sensor(opts, driver, device, ...) + local HOMESEER_MULTI_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0002, productId = 0x0001 } -- Homeseer multi sensor HSM100 + if device:id_match( + HOMESEER_MULTI_SENSOR_FINGERPRINTS.manufacturerId, + HOMESEER_MULTI_SENSOR_FINGERPRINTS.productType, + HOMESEER_MULTI_SENSOR_FINGERPRINTS.productId) then + return true, require("homeseer-multi-sensor") + end + return false +end + +return can_handle_homeseer_multi_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua index 2330f28106..f89e6a7870 100644 --- a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -22,23 +13,6 @@ local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({version = 5}) local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1}) -local HOMESEER_MULTI_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0002, productId = 0x0001 } -- Homeseer multi sensor HSM100 - ---- Determine whether the passed device is homeseer multi sensor ---- ---- @param driver Driver driver instance ---- @param device Device device instance ---- @return boolean true if the device proper, else false -local function can_handle_homeseer_multi_sensor(opts, driver, device, ...) - if device:id_match( - HOMESEER_MULTI_SENSOR_FINGERPRINTS.manufacturerId, - HOMESEER_MULTI_SENSOR_FINGERPRINTS.productType, - HOMESEER_MULTI_SENSOR_FINGERPRINTS.productId) then - local subdriver = require("homeseer-multi-sensor") - return true, subdriver - else return false end -end - local function basic_set_handler(self, device, cmd) if cmd.args.value ~= nil then device:emit_event(cmd.args.value == 0xFF and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) @@ -87,7 +61,7 @@ local homeseer_multi_sensor = { init = device_init, }, NAME = "homeseer multi sensor", - can_handle = can_handle_homeseer_multi_sensor + can_handle = require("homeseer-multi-sensor.can_handle"), } return homeseer_multi_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/init.lua b/drivers/SmartThings/zwave-sensor/src/init.lua index 213aa8c389..2c18e4c2ed 100644 --- a/drivers/SmartThings/zwave-sensor/src/init.lua +++ b/drivers/SmartThings/zwave-sensor/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 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -27,19 +16,6 @@ local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) local preferences = require "preferences" local configurations = require "configurations" -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 - --- Handle preference changes --- --- @param driver st.zwave.Driver @@ -134,27 +110,7 @@ local driver_template = { capabilities.powerMeter, capabilities.smokeDetector }, - sub_drivers = { - lazy_load_if_possible("zooz-4-in-1-sensor"), - lazy_load_if_possible("vision-motion-detector"), - lazy_load_if_possible("fibaro-flood-sensor"), - lazy_load_if_possible("aeotec-water-sensor"), - lazy_load_if_possible("glentronics-water-leak-sensor"), - lazy_load_if_possible("homeseer-multi-sensor"), - lazy_load_if_possible("fibaro-door-window-sensor"), - lazy_load_if_possible("sensative-strip"), - lazy_load_if_possible("enerwave-motion-sensor"), - lazy_load_if_possible("aeotec-multisensor"), - lazy_load_if_possible("zwave-water-leak-sensor"), - lazy_load_if_possible("everspring-motion-light-sensor"), - lazy_load_if_possible("ezmultipli-multipurpose-sensor"), - lazy_load_if_possible("fibaro-motion-sensor"), - 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"), - }, + sub_drivers = require("sub_drivers"), lifecycle_handlers = { added = added_handler, init = device_init, diff --git a/drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/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-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index 9585b6ffe9..70293b10fa 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/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 + --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) diff --git a/drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua new file mode 100644 index 0000000000..e639c32c94 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_sensative_strip(opts, driver, device, cmd, ...) + local SENSATIVE_MFR = 0x019A + local SENSATIVE_MODEL = 0x000A + if device:id_match(SENSATIVE_MFR, nil, SENSATIVE_MODEL) then + local subdriver = require("sensative-strip") + return true, subdriver, require("sensative-strip") + end + return false +end + +return can_handle_sensative_strip diff --git a/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua b/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua index 73f1cb8459..7fd9fb2258 100644 --- a/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/sensative-strip/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 + --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -19,20 +9,11 @@ local Configuration = (require "st.zwave.CommandClass.Configuration")({ version --- @type st.zwave.CommandClass.WakeUp local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) -local SENSATIVE_MFR = 0x019A -local SENSATIVE_MODEL = 0x000A local LEAKAGE_ALARM_PARAM = 12 local LEAKAGE_ALARM_OFF = 0 local SENSATIVE_COMFORT_PROFILE = "illuminance-temperature" local CONFIG_REPORT_RECEIVED = "configReportReceived" -local function can_handle_sensative_strip(opts, driver, device, cmd, ...) - if device:id_match(SENSATIVE_MFR, nil, SENSATIVE_MODEL) then - local subdriver = require("sensative-strip") - return true, subdriver - else return false end -end - local function configuration_report(driver, device, cmd) local parameter_number = cmd.args.parameter_number local configuration_value = cmd.args.configuration_value @@ -75,7 +56,7 @@ local sensative_strip = { doConfigure = do_configure }, NAME = "sensative_strip", - can_handle = can_handle_sensative_strip + can_handle = require("sensative-strip.can_handle") } return sensative_strip diff --git a/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua new file mode 100644 index 0000000000..9504479304 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua @@ -0,0 +1,26 @@ +-- 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("zooz-4-in-1-sensor"), + lazy_load_if_possible("vision-motion-detector"), + lazy_load_if_possible("fibaro-flood-sensor"), + lazy_load_if_possible("aeotec-water-sensor"), + lazy_load_if_possible("glentronics-water-leak-sensor"), + lazy_load_if_possible("homeseer-multi-sensor"), + lazy_load_if_possible("fibaro-door-window-sensor"), + lazy_load_if_possible("sensative-strip"), + lazy_load_if_possible("enerwave-motion-sensor"), + lazy_load_if_possible("aeotec-multisensor"), + lazy_load_if_possible("zwave-water-leak-sensor"), + lazy_load_if_possible("everspring-motion-light-sensor"), + lazy_load_if_possible("ezmultipli-multipurpose-sensor"), + lazy_load_if_possible("fibaro-motion-sensor"), + 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"), +} diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua index 2d83fd6e25..cfdb08f89d 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.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-sensor/src/test/test_aeotec_multisensor_6.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua index 02c2adf751..e78e63e214 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_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-sensor/src/test/test_aeotec_multisensor_7.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua index 7fd57e42b2..5bbd1b4f0f 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.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/src/test/test_aeotec_multisensor_gen5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua index 3b73ff17c3..57584e779c 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.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-sensor/src/test/test_aeotec_water_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua index 8af011f51f..e58badc224 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_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-sensor/src/test/test_aeotec_water_sensor_7.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua index a5f44ff12e..eb951cef9b 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.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/src/test/test_enerwave_motion_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua index a90655669d..bf2d800817 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_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 zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua index 45e0672c11..87d87b75b5 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.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-sensor/src/test/test_everspring_PIR_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua index 1cb2735a31..c6192bcddf 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_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-sensor/src/test/test_everspring_ST814.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua index 097016fc81..c576543902 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.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-sensor/src/test/test_everspring_illuminance_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua index 04103db0db..f6db164cfe 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_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 zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua index 17a627a736..6e04140010 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_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 zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua index 74b6d6196c..2e0ead247a 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_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 zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua index 248c22ab60..d3f51b0bb5 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_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-sensor/src/test/test_fibaro_door_window_sensor_1.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua index 6904f318a9..d597b72bdc 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.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/src/test/test_fibaro_door_window_sensor_2.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua index bc78bd02ca..2bbe391a62 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.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/src/test/test_fibaro_door_window_sensor_with_temperature.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua index 2b27d66868..0fd2d2c8df 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.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/src/test/test_fibaro_flood_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua index 7a9743a69e..98edcbf9f6 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_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-sensor/src/test/test_fibaro_flood_sensor_zw5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua index 45cde29b8f..0ad90d3479 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.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-sensor/src/test/test_fibaro_motion_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua index 24a7f31eaf..6d1f459361 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua index e514a2f2e5..4b322b315d 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.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/src/test/test_generic_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua index 643c69dd1f..24524d9106 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_generic_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-sensor/src/test/test_glentronics_water_leak_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua index 747c4cbce1..5ba9ee537e 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua index f57bfe7950..52a273f0b7 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_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-sensor/src/test/test_no_wakeup_poll.lua b/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua index 9818f1422c..65f30fb7a8 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.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 capabilities = require "st.capabilities" @@ -113,4 +103,4 @@ test.register_message_test( } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua b/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua index 873909df23..a5be01cb81 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.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-sensor/src/test/test_smartthings_water_leak_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua index 2321b5bd09..1b89db9f9f 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua +++ b/drivers/SmartThings/zwave-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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua b/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua index 866508e3d6..b4de025737 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua b/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua index e1b82a6b38..1cf3da742a 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.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/src/test/test_zooz_4_in_1_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua index 1bef24c5d6..c0cc183c41 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_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-sensor/src/test/test_zwave_motion_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua index 4ff11090c9..4b149c9be7 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_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-sensor/src/test/test_zwave_motion_temp_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua index d918f47179..2256522888 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_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-sensor/src/test/test_zwave_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua index a390c35db9..3719a31f1c 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_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-sensor/src/test/test_zwave_water_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua index e6caf1f07c..2a8eea8eab 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_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-sensor/src/timed-tamper-clear/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua new file mode 100644 index 0000000000..c05cbdcf7d --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua @@ -0,0 +1,23 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_tamper_event(opts, driver, device, cmd, ...) + local cc = require "st.zwave.CommandClass" + local Notification = (require "st.zwave.CommandClass.Notification")({ version = 4 }) + local FIBARO_DOOR_WINDOW_MFR_ID = 0x010F + + if device.zwave_manufacturer_id ~= FIBARO_DOOR_WINDOW_MFR_ID and + 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 + return true, require("timed-tamper-clear") + end + return false +end + +return can_handle_tamper_event 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..c2420791b5 100644 --- a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua @@ -1,16 +1,7 @@ --- 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 + + --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -20,23 +11,6 @@ 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 - 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 - end -end -- This behavior is from zwave-door-window-sensor.groovy. We've seen this behavior -- in Ecolink and several other z-wave sensors that do not send tamper clear events @@ -60,7 +34,7 @@ local timed_tamper_clear = { } }, NAME = "timed tamper clear", - can_handle = can_handle_tamper_event + can_handle = require("timed-tamper-clear.can_handle"), } return timed_tamper_clear diff --git a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua new file mode 100644 index 0000000000..2fa45d8015 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua @@ -0,0 +1,22 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_v1_contact_event(opts, driver, device, cmd, ...) + local cc = require "st.zwave.CommandClass" + local Notification = (require "st.zwave.CommandClass.Notification")({ version = 4 }) + + if 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.v1_alarm_type == 0x07 then + local subdriver = require("v1-contact-event") + return true, subdriver, require("v1-contact-event") + else + return false + end +end + +return can_handle_v1_contact_event diff --git a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua index 40efc7633e..887ac32bf0 100644 --- a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/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 2023 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -18,20 +7,6 @@ local cc = require "st.zwave.CommandClass" local Notification = (require "st.zwave.CommandClass.Notification")({ version = 4 }) local capabilities = require "st.capabilities" -local function can_handle_v1_contact_event(opts, driver, device, cmd, ...) - if 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.v1_alarm_type == 0x07 then - local subdriver = require("v1-contact-event") - return true, subdriver - else - return false - end -end -- This behavior is from zwave-door-window-sensor.groovy, where it is -- indicated that certain monoprice sensors had this behavior. Also, @@ -53,7 +28,7 @@ local v1_contact_event = { } }, NAME = "v1 contact event", - can_handle = can_handle_v1_contact_event + can_handle = require("v1-contact-event.can_handle"), } return v1_contact_event diff --git a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua new file mode 100644 index 0000000000..d270a7954d --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +--- Determine whether the passed device is zwave-plus-motion-temp-sensor +local function can_handle_vision_motion_detector(opts, driver, device, ...) + local VISION_MOTION_DETECTOR_FINGERPRINTS = { manufacturerId = 0x0109, productType = 0x2002, productId = 0x0205 } -- Vision Motion Detector ZP3102 + if device:id_match( + VISION_MOTION_DETECTOR_FINGERPRINTS.manufacturerId, + VISION_MOTION_DETECTOR_FINGERPRINTS.productType, + VISION_MOTION_DETECTOR_FINGERPRINTS.productId + ) then + return true, require("vision-motion-detector") + end + return false +end + +return can_handle_vision_motion_detector diff --git a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua index 320bf3824f..72934362c5 100644 --- a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -22,20 +13,6 @@ local Configuration = (require "st.zwave.CommandClass.Configuration")({ version --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) -local VISION_MOTION_DETECTOR_FINGERPRINTS = { manufacturerId = 0x0109, productType = 0x2002, productId = 0x0205 } -- Vision Motion Detector ZP3102 - ---- Determine whether the passed device is zwave-plus-motion-temp-sensor -local function can_handle_vision_motion_detector(opts, driver, device, ...) - if device:id_match( - VISION_MOTION_DETECTOR_FINGERPRINTS.manufacturerId, - VISION_MOTION_DETECTOR_FINGERPRINTS.productType, - VISION_MOTION_DETECTOR_FINGERPRINTS.productId - ) then - local subdriver = require("vision-motion-detector") - return true, subdriver - else return false end -end - --- Handler for notification report command class from sensor --- --- @param self st.zwave.Driver @@ -83,7 +60,7 @@ local vision_motion_detector = { doConfigure = do_configure, }, NAME = "Vision motion detector", - can_handle = can_handle_vision_motion_detector + can_handle = require("vision-motion-detector.can_handle"), } return vision_motion_detector diff --git a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua new file mode 100644 index 0000000000..15ac66d439 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle(opts, driver, device, ...) + local fingerprint = {manufacturerId = 0x014F, productType = 0x2001, productId = 0x0102} -- NorTek open/close sensor + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("wakeup-no-poll") + end + return false +end + +return can_handle diff --git a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua index 59d298a0e4..1270cd4557 100644 --- a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua @@ -1,16 +1,7 @@ --- 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" --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -21,17 +12,6 @@ local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({version = 2 --- @type st.zwave.CommandClass.Battery local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) -local fingerprint = {manufacturerId = 0x014F, productType = 0x2001, productId = 0x0102} -- NorTek open/close sensor - -local function can_handle(opts, driver, device, ...) - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("wakeup-no-poll") - return true, subdriver - else - return false - end -end - -- Nortek open/closed sensors _always_ respond with "open" when polled, and they are polled after wakeup local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher @@ -53,7 +33,7 @@ local wakeup_no_poll = { [WakeUp.NOTIFICATION] = wakeup_notification } }, - can_handle = can_handle + can_handle = require("wakeup-no-poll.can_handle"), } -return wakeup_no_poll \ No newline at end of file +return wakeup_no_poll diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua new file mode 100644 index 0000000000..17c80b70fb --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_zooz_4_in_1_sensor(opts, driver, device, ...) + local FINGERPRINTS = require("zooz-4-in-1-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("zooz-4-in-1-sensor") + return true, subdriver, require("zooz-4-in-1-sensor") + end + end + return false +end + +return can_handle_zooz_4_in_1_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua new file mode 100644 index 0000000000..12d853b147 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZOOZ_4_IN_1_FINGERPRINTS = { + { manufacturerId = 0x027A, productType = 0x2021, productId = 0x2101 }, -- Zooz 4-in-1 sensor + { manufacturerId = 0x0109, productType = 0x2021, productId = 0x2101 }, -- ZP3111US 4-in-1 Motion + { manufacturerId = 0x0060, productType = 0x0001, productId = 0x0004 } -- Everspring Immune Pet PIR Sensor SP815 +} + +return ZOOZ_4_IN_1_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua index 5d4570e525..7321c3f9b4 100644 --- a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -22,22 +13,8 @@ local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ ve --- @type st.utils local utils = require "st.utils" -local ZOOZ_4_IN_1_FINGERPRINTS = { - { manufacturerId = 0x027A, productType = 0x2021, productId = 0x2101 }, -- Zooz 4-in-1 sensor - { manufacturerId = 0x0109, productType = 0x2021, productId = 0x2101 }, -- ZP3111US 4-in-1 Motion - { manufacturerId = 0x0060, productType = 0x0001, productId = 0x0004 } -- Everspring Immune Pet PIR Sensor SP815 -} --- Determine whether the passed device is zooz_4_in_1_sensor -local function can_handle_zooz_4_in_1_sensor(opts, driver, device, ...) - for _, fingerprint in ipairs(ZOOZ_4_IN_1_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("zooz-4-in-1-sensor") - return true, subdriver - end - end - return false -end --- Handler for notification report command class --- @@ -109,7 +86,7 @@ local zooz_4_in_1_sensor = { } }, NAME = "zooz 4 in 1 sensor", - can_handle = can_handle_zooz_4_in_1_sensor + can_handle = require("zooz-4-in-1-sensor.can_handle"), } return zooz_4_in_1_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua new file mode 100644 index 0000000000..ad7e736a7c --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_water_leak_sensor(opts, driver, device, ...) + local FINGERPRINTS = require("zwave-water-leak-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("zwave-water-leak-sensor") + return true, subdriver, require("zwave-water-leak-sensor") + end + end + return false +end + +return can_handle_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua new file mode 100644 index 0000000000..c07bdb52cb --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua @@ -0,0 +1,19 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local WATER_LEAK_SENSOR_FINGERPRINTS = { + {mfr = 0x0084, prod = 0x0063, model = 0x010C}, -- SmartThings Water Leak Sensor + {mfr = 0x0084, prod = 0x0053, model = 0x0216}, -- FortrezZ Water Leak Sensor + {mfr = 0x021F, prod = 0x0003, model = 0x0085}, -- Dome Leak Sensor + {mfr = 0x0258, prod = 0x0003, model = 0x0085}, -- NEO Coolcam Water Sensor + {mfr = 0x0258, prod = 0x0003, model = 0x1085}, -- NEO Coolcam Water Sensor + {mfr = 0x0258, prod = 0x0003, model = 0x2085}, -- NEO Coolcam Water Sensor + {mfr = 0x0086, prod = 0x0002, model = 0x007A}, -- Aeotec Water Sensor 6 (EU) + {mfr = 0x0086, prod = 0x0102, model = 0x007A}, -- Aeotec Water Sensor 6 (US) + {mfr = 0x0086, prod = 0x0202, model = 0x007A}, -- Aeotec Water Sensor 6 (AU) + {mfr = 0x000C, prod = 0x0201, model = 0x000A}, -- HomeSeer LS100+ Water Sensor + {mfr = 0x0173, prod = 0x4C47, model = 0x4C44}, -- Leak Gopher Z-Wave Leak Detector + {mfr = 0x027A, prod = 0x7000, model = 0xE002} -- Zooz ZSE42 XS Water Leak Sensor +} + +return WATER_LEAK_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua index 1eefab7479..4575de352a 100644 --- a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua @@ -1,47 +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 cc = require "st.zwave.CommandClass" local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) - -local WATER_LEAK_SENSOR_FINGERPRINTS = { - {mfr = 0x0084, prod = 0x0063, model = 0x010C}, -- SmartThings Water Leak Sensor - {mfr = 0x0084, prod = 0x0053, model = 0x0216}, -- FortrezZ Water Leak Sensor - {mfr = 0x021F, prod = 0x0003, model = 0x0085}, -- Dome Leak Sensor - {mfr = 0x0258, prod = 0x0003, model = 0x0085}, -- NEO Coolcam Water Sensor - {mfr = 0x0258, prod = 0x0003, model = 0x1085}, -- NEO Coolcam Water Sensor - {mfr = 0x0258, prod = 0x0003, model = 0x2085}, -- NEO Coolcam Water Sensor - {mfr = 0x0086, prod = 0x0002, model = 0x007A}, -- Aeotec Water Sensor 6 (EU) - {mfr = 0x0086, prod = 0x0102, model = 0x007A}, -- Aeotec Water Sensor 6 (US) - {mfr = 0x0086, prod = 0x0202, model = 0x007A}, -- Aeotec Water Sensor 6 (AU) - {mfr = 0x000C, prod = 0x0201, model = 0x000A}, -- HomeSeer LS100+ Water Sensor - {mfr = 0x0173, prod = 0x4C47, model = 0x4C44}, -- Leak Gopher Z-Wave Leak Detector - {mfr = 0x027A, prod = 0x7000, model = 0xE002} -- Zooz ZSE42 XS Water Leak Sensor -} - -local function can_handle_water_leak_sensor(opts, driver, device, ...) - for _, fingerprint in ipairs(WATER_LEAK_SENSOR_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("zwave-water-leak-sensor") - return true, subdriver - 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 device:emit_event(value == 0xFF and capabilities.waterSensor.water.wet() or capabilities.waterSensor.water.dry()) @@ -54,7 +17,7 @@ local water_leak_sensor = { [Basic.SET] = basic_set_handler } }, - can_handle = can_handle_water_leak_sensor + can_handle = require("zwave-water-leak-sensor.can_handle"), } return water_leak_sensor From 13c64b210b78edd9c527ae038147eff6366c2d07 Mon Sep 17 00:00:00 2001 From: cjswedes Date: Thu, 12 Feb 2026 13:10:33 -0600 Subject: [PATCH 105/538] Fixup tests for new native handlers in 60 --- .../src/test/test_all_capability_zigbee_bulb.lua | 16 ++++++++++++++++ .../zigbee-switch/src/test/test_zll_rgb_bulb.lua | 2 ++ .../src/test/test_generic_sensor.lua | 8 ++++++++ 3 files changed, 26 insertions(+) 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 70dfc96780..b581c5c61e 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 @@ -233,6 +233,14 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_device:generate_test_message("main", capabilities.colorControl.hue(0)) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "hue" } + } } } ) @@ -250,6 +258,14 @@ 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/zigbee-switch/src/test/test_zll_rgb_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_zll_rgb_bulb.lua index 3435b22288..a8384d1b13 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 @@ -205,6 +205,7 @@ for _, data in ipairs(test_data) do if data.saturation ~= nil then test.socket.zigbee:__queue_receive({mock_device.id, ColorControl.attributes.CurrentSaturation:build_test_attr_report(mock_device, math.ceil(data.saturation / 100 * 0xFE))}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.saturation(data.saturation))) + mock_device:expect_native_attr_handler_registration("colorControl", "saturation") end test.timer.__create_and_queue_test_time_advance_timer(0.2, "oneshot") @@ -249,6 +250,7 @@ for _, data in ipairs(test_data) do if data.hue ~= nil then test.socket.zigbee:__queue_receive({mock_device.id, ColorControl.attributes.CurrentHue:build_test_attr_report(mock_device, math.ceil(data.hue / 100 * 0xFE))}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.hue(data.hue))) + mock_device:expect_native_attr_handler_registration("colorControl", "hue") end test.timer.__create_and_queue_test_time_advance_timer(0.2, "oneshot") diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua index 643c69dd1f..e6fff57b0d 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua @@ -1548,6 +1548,14 @@ test.register_message_test( mock_device, Meter:Get({scale = 0}) ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_device, + Meter:Get({scale = 4}) + ) } }, { From 6863a78c336c9d5fb3eac6713db25202b81749a3 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Tue, 16 Dec 2025 13:06:17 -0600 Subject: [PATCH 106/538] add statelessStep capability support --- .../light-color-level-1800K-6500K.yml | 4 + .../light-color-level-2000K-7000K.yml | 4 + .../light-color-level-2200K-6500K.yml | 4 + .../light-color-level-2700K-6500K.yml | 4 + .../profiles/light-color-level-fan.yml | 4 + ...-level-illuminance-motion-1000K-15000K.yml | 4 + .../light-color-level-illuminance-motion.yml | 4 + .../profiles/light-color-level.yml | 4 + .../profiles/light-level-2-button.yml | 2 + .../profiles/light-level-3-button.yml | 2 + .../profiles/light-level-4-button.yml | 2 + .../profiles/light-level-5-button.yml | 2 + .../profiles/light-level-6-button.yml | 2 + .../profiles/light-level-7-button.yml | 2 + .../profiles/light-level-8-button.yml | 2 + ...ight-level-ColorTemperature-1500-9000k.yml | 4 + .../profiles/light-level-button.yml | 2 + ...ght-level-colorTemperature-2200K-6500K.yml | 4 + ...ght-level-colorTemperature-2700K-6500K.yml | 4 + ...ght-level-colorTemperature-2710k-6500k.yml | 4 + .../profiles/light-level-colorTemperature.yml | 4 + .../light-level-energy-powerConsumption.yml | 2 + .../profiles/light-level-motion.yml | 2 + ...ht-level-power-energy-powerConsumption.yml | 2 + .../profiles/light-level-power.yml | 2 + .../matter-switch/profiles/light-level.yml | 2 + .../plug-level-energy-powerConsumption.yml | 2 + ...ug-level-power-energy-powerConsumption.yml | 2 + .../profiles/plug-level-power.yml | 2 + .../matter-switch/profiles/plug-level.yml | 2 + .../profiles/switch-color-level.yml | 4 + .../switch-level-colorTemperature.yml | 4 + .../matter-switch/profiles/switch-level.yml | 2 + .../SmartThings/matter-switch/src/init.lua | 6 + .../switch_handlers/capability_handlers.lua | 36 +++- .../matter-switch/src/switch_utils/fields.lua | 17 +- .../test/test_light_illuminance_motion.lua | 10 +- .../src/test/test_matter_light_fan.lua | 4 +- .../test_matter_multi_button_switch_mcd.lua | 4 +- .../src/test/test_matter_switch.lua | 18 +- .../test_multi_switch_parent_child_lights.lua | 6 +- .../test_multi_switch_parent_child_plugs.lua | 6 +- .../src/test/test_stateless_step.lua | 163 ++++++++++++++++++ 43 files changed, 331 insertions(+), 35 deletions(-) create mode 100644 drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-1800K-6500K.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-1800K-6500K.yml index 58c8e0fca6..a0f5196b12 100755 --- a/drivers/SmartThings/matter-switch/profiles/light-color-level-1800K-6500K.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-1800K-6500K.yml @@ -11,12 +11,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 1800, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-2000K-7000K.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-2000K-7000K.yml index 4772e22f66..7424d241e2 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-color-level-2000K-7000K.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-2000K-7000K.yml @@ -11,12 +11,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2000, 7000 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-2200K-6500K.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-2200K-6500K.yml index 4977423135..a098cdd06f 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-color-level-2200K-6500K.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-2200K-6500K.yml @@ -11,12 +11,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-2700K-6500K.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-2700K-6500K.yml index dbef511bbb..1b469ae8e8 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-color-level-2700K-6500K.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-2700K-6500K.yml @@ -11,12 +11,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2700, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-fan.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-fan.yml index 2fabc23bd7..2f91bcb04e 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-color-level-fan.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-fan.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2700, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: fanMode diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion-1000K-15000K.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion-1000K-15000K.yml index dd48e4e81a..fb7a4fca94 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion-1000K-15000K.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion-1000K-15000K.yml @@ -7,12 +7,16 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 1000, 15000 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: motionSensor diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion.yml index 999e64a047..50f68f60fc 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion.yml @@ -6,12 +6,16 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: motionSensor diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level.yml index 572686ffdd..2f0f673bee 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-color-level.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-2-button.yml b/drivers/SmartThings/matter-switch/profiles/light-level-2-button.yml index 7c8b60ef56..6a4fbcc9d0 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-2-button.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-2-button.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-3-button.yml b/drivers/SmartThings/matter-switch/profiles/light-level-3-button.yml index 59600efd72..9e3106d9de 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-3-button.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-3-button.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-4-button.yml b/drivers/SmartThings/matter-switch/profiles/light-level-4-button.yml index b49b7f2254..43bcec5955 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-4-button.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-4-button.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-5-button.yml b/drivers/SmartThings/matter-switch/profiles/light-level-5-button.yml index ee55a6a394..2b98b9784d 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-5-button.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-5-button.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-6-button.yml b/drivers/SmartThings/matter-switch/profiles/light-level-6-button.yml index 805c97763e..230b2ea341 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-6-button.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-6-button.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-7-button.yml b/drivers/SmartThings/matter-switch/profiles/light-level-7-button.yml index 5cd1666a5f..13b3ee9443 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-7-button.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-7-button.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-8-button.yml b/drivers/SmartThings/matter-switch/profiles/light-level-8-button.yml index 4636359e92..a2dc732257 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-8-button.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-8-button.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-ColorTemperature-1500-9000k.yml b/drivers/SmartThings/matter-switch/profiles/light-level-ColorTemperature-1500-9000k.yml index f19e55ec9c..effe5f93b6 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-ColorTemperature-1500-9000k.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-ColorTemperature-1500-9000k.yml @@ -11,12 +11,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 1500, 9000 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-button.yml b/drivers/SmartThings/matter-switch/profiles/light-level-button.yml index 9fc53f642b..8334e32feb 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-button.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-button.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature-2200K-6500K.yml b/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature-2200K-6500K.yml index 79d6556485..2541fddc09 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature-2200K-6500K.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature-2200K-6500K.yml @@ -11,12 +11,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature-2700K-6500K.yml b/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature-2700K-6500K.yml index 75b2bd488c..7c845e8238 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature-2700K-6500K.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature-2700K-6500K.yml @@ -11,12 +11,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2700, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature-2710k-6500k.yml b/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature-2710k-6500k.yml index b49179036f..5ccc67ab4c 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature-2710k-6500k.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature-2710k-6500k.yml @@ -11,12 +11,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2710, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature.yml b/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature.yml index 4d130281d7..5681a97e9e 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-energy-powerConsumption.yml b/drivers/SmartThings/matter-switch/profiles/light-level-energy-powerConsumption.yml index 03963ccbd2..b2ddcc0a60 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-energy-powerConsumption.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-energy-powerConsumption.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: energyMeter version: 1 - id: powerConsumptionReport diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-motion.yml b/drivers/SmartThings/matter-switch/profiles/light-level-motion.yml index bdea457c21..54ce30abdf 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-motion.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-motion.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: motionSensor version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-power-energy-powerConsumption.yml b/drivers/SmartThings/matter-switch/profiles/light-level-power-energy-powerConsumption.yml index f6c45ed1f7..31c6d44ee1 100755 --- a/drivers/SmartThings/matter-switch/profiles/light-level-power-energy-powerConsumption.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-power-energy-powerConsumption.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: powerMeter version: 1 - id: energyMeter diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-power.yml b/drivers/SmartThings/matter-switch/profiles/light-level-power.yml index 23625ada16..86d61c0345 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-power.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-power.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: powerMeter version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-switch/profiles/light-level.yml b/drivers/SmartThings/matter-switch/profiles/light-level.yml index e266f497c9..3b286f8262 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/plug-level-energy-powerConsumption.yml b/drivers/SmartThings/matter-switch/profiles/plug-level-energy-powerConsumption.yml index 86bd861bce..4ae29d2320 100644 --- a/drivers/SmartThings/matter-switch/profiles/plug-level-energy-powerConsumption.yml +++ b/drivers/SmartThings/matter-switch/profiles/plug-level-energy-powerConsumption.yml @@ -6,6 +6,8 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: energyMeter version: 1 - id: powerConsumptionReport diff --git a/drivers/SmartThings/matter-switch/profiles/plug-level-power-energy-powerConsumption.yml b/drivers/SmartThings/matter-switch/profiles/plug-level-power-energy-powerConsumption.yml index 17e7d6b7a0..477fb8b769 100644 --- a/drivers/SmartThings/matter-switch/profiles/plug-level-power-energy-powerConsumption.yml +++ b/drivers/SmartThings/matter-switch/profiles/plug-level-power-energy-powerConsumption.yml @@ -6,6 +6,8 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: powerMeter version: 1 - id: energyMeter diff --git a/drivers/SmartThings/matter-switch/profiles/plug-level-power.yml b/drivers/SmartThings/matter-switch/profiles/plug-level-power.yml index d175930a92..8bb1c56361 100644 --- a/drivers/SmartThings/matter-switch/profiles/plug-level-power.yml +++ b/drivers/SmartThings/matter-switch/profiles/plug-level-power.yml @@ -6,6 +6,8 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: powerMeter version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-switch/profiles/plug-level.yml b/drivers/SmartThings/matter-switch/profiles/plug-level.yml index 0d888b843f..90fa2ece47 100644 --- a/drivers/SmartThings/matter-switch/profiles/plug-level.yml +++ b/drivers/SmartThings/matter-switch/profiles/plug-level.yml @@ -6,6 +6,8 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/switch-color-level.yml b/drivers/SmartThings/matter-switch/profiles/switch-color-level.yml index f1f9e78438..c684f632c9 100644 --- a/drivers/SmartThings/matter-switch/profiles/switch-color-level.yml +++ b/drivers/SmartThings/matter-switch/profiles/switch-color-level.yml @@ -6,12 +6,16 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-switch/profiles/switch-level-colorTemperature.yml b/drivers/SmartThings/matter-switch/profiles/switch-level-colorTemperature.yml index 42e3ef6257..9cba419d02 100644 --- a/drivers/SmartThings/matter-switch/profiles/switch-level-colorTemperature.yml +++ b/drivers/SmartThings/matter-switch/profiles/switch-level-colorTemperature.yml @@ -6,12 +6,16 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/profiles/switch-level.yml b/drivers/SmartThings/matter-switch/profiles/switch-level.yml index 8f3b9f5e5c..827fcdd898 100644 --- a/drivers/SmartThings/matter-switch/profiles/switch-level.yml +++ b/drivers/SmartThings/matter-switch/profiles/switch-level.yml @@ -6,6 +6,8 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index e8d1b8330a..cac42e4483 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -298,6 +298,12 @@ local matter_driver_template = { [capabilities.level.ID] = { [capabilities.level.commands.setLevel.NAME] = capability_handlers.handle_set_level }, + [capabilities.statelessColorTemperatureStep.ID] = { + [capabilities.statelessColorTemperatureStep.commands.stepColorTemperatureByPercent.NAME] = capability_handlers.handle_step_color_temperature_by_percent, + }, + [capabilities.statelessSwitchLevelStep.ID] = { + [capabilities.statelessSwitchLevelStep.commands.stepLevel.NAME] = capability_handlers.handle_step_level, + }, [capabilities.switch.ID] = { [capabilities.switch.commands.off.NAME] = capability_handlers.handle_switch_off, [capabilities.switch.commands.on.NAME] = capability_handlers.handle_switch_on, diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua index f96686b73a..3c65e3e8c0 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua @@ -47,6 +47,17 @@ function CapabilityHandlers.handle_switch_set_level(driver, device, cmd) end +-- [[ STATELESS SWITCH LEVEL STEP CAPABILITY COMMANDS ]] -- + +function CapabilityHandlers.handle_step_level(driver, device, cmd) + local step_size = st_utils.round((cmd.args and cmd.args.stepSize or 0)/100.0 * 254) + if step_size == 0 then return end + local endpoint_id = device:component_to_endpoint(cmd.component) + local step_mode = step_size > 0 and clusters.LevelControl.types.StepMode.UP or clusters.LevelControl.types.StepMode.DOWN + device:send(clusters.LevelControl.server.commands.Step(device, endpoint_id, step_mode, math.abs(step_size), fields.TRANSITION_TIME_FAST, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF)) +end + + -- [[ COLOR CONTROL CAPABILITY COMMANDS ]] -- function CapabilityHandlers.handle_set_color(driver, device, cmd) @@ -56,10 +67,10 @@ function CapabilityHandlers.handle_set_color(driver, device, cmd) if switch_utils.tbl_contains(huesat_endpoints, endpoint_id) then local hue = switch_utils.convert_huesat_st_to_matter(cmd.args.color.hue) local sat = switch_utils.convert_huesat_st_to_matter(cmd.args.color.saturation) - req = clusters.ColorControl.server.commands.MoveToHueAndSaturation(device, endpoint_id, hue, sat, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.OPTIONS_OVERRIDE) + req = clusters.ColorControl.server.commands.MoveToHueAndSaturation(device, endpoint_id, hue, sat, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) else local x, y, _ = st_utils.safe_hsv_to_xy(cmd.args.color.hue, cmd.args.color.saturation) - req = clusters.ColorControl.server.commands.MoveToColor(device, endpoint_id, x, y, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.OPTIONS_OVERRIDE) + req = clusters.ColorControl.server.commands.MoveToColor(device, endpoint_id, x, y, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) end device:send(req) end @@ -69,7 +80,7 @@ function CapabilityHandlers.handle_set_hue(driver, device, cmd) local huesat_endpoints = device:get_endpoints(clusters.ColorControl.ID, {feature_bitmap = clusters.ColorControl.FeatureMap.HUE_AND_SATURATION}) if switch_utils.tbl_contains(huesat_endpoints, endpoint_id) then local hue = switch_utils.convert_huesat_st_to_matter(cmd.args.hue) - local req = clusters.ColorControl.server.commands.MoveToHue(device, endpoint_id, hue, 0, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.OPTIONS_OVERRIDE) + local req = clusters.ColorControl.server.commands.MoveToHue(device, endpoint_id, hue, 0, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) device:send(req) else device.log.warn("Device does not support huesat features on its color control cluster") @@ -81,7 +92,7 @@ function CapabilityHandlers.handle_set_saturation(driver, device, cmd) local huesat_endpoints = device:get_endpoints(clusters.ColorControl.ID, {feature_bitmap = clusters.ColorControl.FeatureMap.HUE_AND_SATURATION}) if switch_utils.tbl_contains(huesat_endpoints, endpoint_id) then local sat = switch_utils.convert_huesat_st_to_matter(cmd.args.saturation) - local req = clusters.ColorControl.server.commands.MoveToSaturation(device, endpoint_id, sat, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.OPTIONS_OVERRIDE) + local req = clusters.ColorControl.server.commands.MoveToSaturation(device, endpoint_id, sat, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) device:send(req) else device.log.warn("Device does not support huesat features on its color control cluster") @@ -103,12 +114,27 @@ function CapabilityHandlers.handle_set_color_temperature(driver, device, cmd) elseif max_temp_kelvin ~= nil and temp_in_kelvin >= max_temp_kelvin then temp_in_mired = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) end - local req = clusters.ColorControl.server.commands.MoveToColorTemperature(device, endpoint_id, temp_in_mired, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.OPTIONS_OVERRIDE) + local req = clusters.ColorControl.server.commands.MoveToColorTemperature(device, endpoint_id, temp_in_mired, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) device:set_field(fields.MOST_RECENT_TEMP, cmd.args.temperature, {persist = true}) device:send(req) end +-- [[ STATELESS COLOR TEMPERATURE STEP CAPABILITY COMMANDS ]] -- + +function CapabilityHandlers.handle_step_color_temperature_by_percent(driver, device, cmd) + local step_percent_change = cmd.args and cmd.args.stepSize or 0 + if step_percent_change == 0 then return end + local endpoint_id = device:component_to_endpoint(cmd.component) + -- before the Matter 1.3 lua libs update (HUB FW 55), there was no ColorControl StepModeEnum type defined + local step_mode = step_percent_change > 0 and (clusters.ColorControl.types.StepModeEnum and clusters.ColorControl.types.StepModeEnum.DOWN or 3) or (clusters.ColorControl.types.StepModeEnum and clusters.ColorControl.types.StepModeEnum.UP or 1) + local min_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) or fields.COLOR_TEMPERATURE_MIRED_MIN -- default min mireds + local max_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MAX, endpoint_id) or fields.COLOR_TEMPERATURE_MIRED_MAX -- default max mireds + local step_size_in_mireds = st_utils.round((max_mireds - min_mireds) * (math.abs(step_percent_change)/100.0)) + device:send(clusters.ColorControl.server.commands.StepColorTemperature(device, endpoint_id, step_mode, step_size_in_mireds, fields.TRANSITION_TIME_FAST, min_mireds, max_mireds, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF)) +end + + -- [[ VALVE CAPABILITY COMMANDS ]] -- function CapabilityHandlers.handle_valve_open(driver, device, cmd) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index dcc3403780..6eb03b1472 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -1,6 +1,8 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local st_utils = require "st.utils" + local SwitchFields = {} SwitchFields.MOST_RECENT_TEMP = "mostRecentTemp" @@ -13,8 +15,8 @@ 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.COLOR_TEMPERATURE_MIRED_MAX = st_utils.round(SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MIN) +SwitchFields.COLOR_TEMPERATURE_MIRED_MIN = st_utils.round(SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MAX) SwitchFields.SWITCH_LEVEL_LIGHTING_MIN = 1 SwitchFields.CURRENT_HUESAT_ATTR_MIN = 0 @@ -186,10 +188,13 @@ 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.TRANSITION_TIME = 0 -- number of 10ths of a second +SwitchFields.TRANSITION_TIME_FAST = 3 -- 0.3 seconds + +-- For Level/Color Control cluster commands, this field indicates which bits in the OptionsOverride field are valid. In this case, we specify that the ExecuteIfOff option (bit 1) may be overridden. SwitchFields.OPTIONS_MASK = 0x01 -SwitchFields.OPTIONS_OVERRIDE = 0x01 +-- the OptionsOverride field's first bit overrides the ExecuteIfOff option, defining whether the command should take effect when the device is off. +SwitchFields.HANDLE_COMMAND_IF_OFF = 0x01 +SwitchFields.IGNORE_COMMAND_IF_OFF = 0x00 return SwitchFields 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 a6d660de4e..abc98591fc 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 @@ -9,7 +9,7 @@ local st_utils = require "st.utils" local clusters = require "st.matter.clusters" local TRANSITION_TIME = 0 local OPTIONS_MASK = 0x01 -local OPTIONS_OVERRIDE = 0x01 +local HANDLE_COMMAND_IF_OFF = 0x01 local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("light-color-level-illuminance-motion.yml"), @@ -319,7 +319,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToHueAndSaturation(mock_device, 1, hue, sat, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToHueAndSaturation(mock_device, 1, hue, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { @@ -391,7 +391,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToHue(mock_device, 1, hue, 0, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToHue(mock_device, 1, hue, 0, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, } @@ -413,7 +413,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToSaturation(mock_device, 1, sat, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToSaturation(mock_device, 1, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, } @@ -435,7 +435,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 556, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 556, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { 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 676a376726..e1af3ad52b 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 @@ -9,7 +9,7 @@ local version = require "version" local TRANSITION_TIME = 0 local OPTIONS_MASK = 0x01 -local OPTIONS_OVERRIDE = 0x01 +local HANDLE_COMMAND_IF_OFF = 0x01 local mock_device_ep1 = 1 local mock_device_ep2 = 2 @@ -178,7 +178,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, mock_device_ep1, 556, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, mock_device_ep1, 556, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { 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 d412c2b35e..db1ae04bfb 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 @@ -9,7 +9,7 @@ local clusters = require "st.matter.generated.zap_clusters" local TRANSITION_TIME = 0 local OPTIONS_MASK = 0x01 -local OPTIONS_OVERRIDE = 0x01 +local HANDLE_COMMAND_IF_OFF = 0x01 local button_attr = capabilities.button.button @@ -366,7 +366,7 @@ test.register_coroutine_test( }) 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) + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, mock_device_ep5, 556, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) }) test.socket.matter:__queue_receive({ mock_device.id, 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 86edf45403..6cdf530dbf 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -9,7 +9,7 @@ local st_utils = require "st.utils" local clusters = require "st.matter.clusters" local TRANSITION_TIME = 0 local OPTIONS_MASK = 0x01 -local OPTIONS_OVERRIDE = 0x01 +local HANDLE_COMMAND_IF_OFF = 0x01 local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("switch-color-level.yml"), @@ -451,7 +451,7 @@ test.register_coroutine_test( test.socket.matter:__expect_send( { mock_device_no_hue_sat.id, - clusters.ColorControl.server.commands.MoveToColor(mock_device_no_hue_sat, 1, 15182, 21547, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToColor(mock_device_no_hue_sat, 1, 15182, 21547, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } ) test.socket.matter:__queue_receive( @@ -513,7 +513,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToHueAndSaturation(mock_device, 1, hue, sat, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToHueAndSaturation(mock_device, 1, hue, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { @@ -595,7 +595,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToHueAndSaturation(mock_device, 1, hue, sat, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToHueAndSaturation(mock_device, 1, hue, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { @@ -669,7 +669,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToHue(mock_device, 1, hue, 0, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToHue(mock_device, 1, hue, 0, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, } @@ -691,7 +691,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToSaturation(mock_device, 1, sat, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToSaturation(mock_device, 1, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, } @@ -713,7 +713,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 556, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 556, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { @@ -985,7 +985,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 165, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 165, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { @@ -1001,7 +1001,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 365, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 365, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } } } 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 3261360910..be01a7bf32 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 @@ -10,7 +10,7 @@ test.disable_startup_messages() local TRANSITION_TIME = 0 local OPTIONS_MASK = 0x01 -local OPTIONS_OVERRIDE = 0x01 +local HANDLE_COMMAND_IF_OFF = 0x01 local parent_ep = 10 local child1_ep = 20 @@ -496,7 +496,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, child2_ep, 556, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, child2_ep, 556, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { @@ -571,7 +571,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColor(mock_device, child2_ep, 15182, 21547, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToColor(mock_device, child2_ep, 15182, 21547, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { 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 92df754303..2370984c97 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 @@ -10,7 +10,7 @@ test.disable_startup_messages() local TRANSITION_TIME = 0 local OPTIONS_MASK = 0x01 -local OPTIONS_OVERRIDE = 0x01 +local HANDLE_COMMAND_IF_OFF = 0x01 local parent_ep = 10 local child1_ep = 20 @@ -489,7 +489,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, child2_ep, 556, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, child2_ep, 556, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { @@ -564,7 +564,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColor(mock_device, child2_ep, 15182, 21547, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + clusters.ColorControl.server.commands.MoveToColor(mock_device, child2_ep, 15182, 21547, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { diff --git a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua new file mode 100644 index 0000000000..a8a8e83b4b --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua @@ -0,0 +1,163 @@ +-- Copyright 2026 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 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 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, +} + +local function test_init() + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_color_temp) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_color_temp)) + end + end + test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request}) + test.mock_device.add_test_device(mock_device_color_temp) +end +test.set_test_init_function(test_init) + +local fields = require "switch_utils.fields" + +test.register_message_test( + "Color Temperature Step Command Test", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device_color_temp.id, + { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { 20 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device_color_temp.id, + clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.DOWN, 187, fields.TRANSITION_TIME_FAST, fields.COLOR_TEMPERATURE_MIRED_MIN, fields.COLOR_TEMPERATURE_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + }, + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device_color_temp.id, + { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { 90 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device_color_temp.id, + clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.DOWN, 840, fields.TRANSITION_TIME_FAST, fields.COLOR_TEMPERATURE_MIRED_MIN, fields.COLOR_TEMPERATURE_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + }, + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device_color_temp.id, + { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { -50 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device_color_temp.id, + clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.UP, 467, fields.TRANSITION_TIME_FAST, fields.COLOR_TEMPERATURE_MIRED_MIN, fields.COLOR_TEMPERATURE_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + }, + } + } +) + + +test.register_message_test( + "Level Step Command Test", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device_color_temp.id, + { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 25 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device_color_temp.id, + clusters.LevelControl.server.commands.Step(mock_device_color_temp, 1, clusters.LevelControl.types.StepModeEnum.UP, 64, fields.TRANSITION_TIME_FAST, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + }, + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device_color_temp.id, + { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { -50 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device_color_temp.id, + clusters.LevelControl.server.commands.Step(mock_device_color_temp, 1, clusters.LevelControl.types.StepModeEnum.DOWN, 127, fields.TRANSITION_TIME_FAST, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + }, + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device_color_temp.id, + { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 100 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device_color_temp.id, + clusters.LevelControl.server.commands.Step(mock_device_color_temp, 1, clusters.LevelControl.types.StepModeEnum.UP, 254, fields.TRANSITION_TIME_FAST, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + }, + } + } +) + +test.run_registered_tests() From 51f1132278bb930ee34c4b0ee77e167be67c226f Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:29:35 -0600 Subject: [PATCH 107/538] Matter Camera: Include all supported resolutions (#2707) This change adds logic for including the resolutions exposed by the VideoSensorParams and MinViewport attributes in supportedResolutions, rather than only looking at the resolution embedded within RateDistortionTradeOffPoints. --- .../camera_handlers/attribute_handlers.lua | 91 ++++++++----------- .../camera/camera_utils/fields.lua | 2 + .../sub_drivers/camera/camera_utils/utils.lua | 32 +++++++ .../src/test/test_matter_camera.lua | 38 +++++++- 4 files changed, 106 insertions(+), 57 deletions(-) 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 index 484e4c7a15..df99d50f94 100644 --- 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 @@ -113,9 +113,6 @@ 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 @@ -124,77 +121,63 @@ function CameraAttributeHandlers.rate_distortion_trade_off_points_handler(driver 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) + 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) + if max_encoded_pixel_rate and max_fps and device:get_field(camera_fields.MAX_RESOLUTION) and device:get_field(camera_fields.MIN_RESOLUTION) then + local supported_resolutions = camera_utils.build_supported_resolutions(device, max_encoded_pixel_rate, max_fps) + device:emit_event_for_endpoint(ib, capabilities.videoStreamSettings.supportedResolutions(supported_resolutions)) + end end function CameraAttributeHandlers.max_encoded_pixel_rate_handler(driver, device, ib, response) - local resolutions = device:get_field(camera_fields.SUPPORTED_RESOLUTIONS) + device:set_field(camera_fields.MAX_ENCODED_PIXEL_RATE, ib.data.value) 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)) + if max_fps and device:get_field(camera_fields.SUPPORTED_RESOLUTIONS) and device:get_field(camera_fields.MAX_RESOLUTION) and device:get_field(camera_fields.MIN_RESOLUTION) then + local supported_resolutions = camera_utils.build_supported_resolutions(device, ib.data.value, max_fps) + device:emit_event_for_endpoint(ib, capabilities.videoStreamSettings.supportedResolutions(supported_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 sensor_width = ib.data.elements.sensor_width.value + local sensor_height = ib.data.elements.sensor_height.value + local max_fps = ib.data.elements.max_fps.value + device:set_field(camera_fields.MAX_RESOLUTION, { + width = sensor_width, + height = sensor_height + }) + device:set_field(camera_fields.MAX_FRAMES_PER_SECOND, max_fps) + device:emit_event_for_endpoint(ib, capabilities.cameraViewportSettings.videoSensorParameters({ + width = sensor_width, + height = sensor_height, + maxFPS = max_fps + })) 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) + if max_encoded_pixel_rate and max_fps and device:get_field(camera_fields.SUPPORTED_RESOLUTIONS) and device:get_field(camera_fields.MIN_RESOLUTION) then + local supported_resolutions = camera_utils.build_supported_resolutions(device, max_encoded_pixel_rate, max_fps) + device:emit_event_for_endpoint(ib, capabilities.videoStreamSettings.supportedResolutions(supported_resolutions)) end end function CameraAttributeHandlers.min_viewport_handler(driver, device, ib, response) + if not ib.data.elements then return end device:emit_event_for_endpoint(ib, capabilities.cameraViewportSettings.minViewportResolution({ width = ib.data.elements.width.value, height = ib.data.elements.height.value })) + device:set_field(camera_fields.MIN_RESOLUTION, { + width = ib.data.elements.width.value, + height = ib.data.elements.height.value + }) + 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) + if max_encoded_pixel_rate and max_fps and device:get_field(camera_fields.SUPPORTED_RESOLUTIONS) and device:get_field(camera_fields.MAX_RESOLUTION) then + local supported_resolutions = camera_utils.build_supported_resolutions(device, max_encoded_pixel_rate, max_fps) + device:emit_event_for_endpoint(ib, capabilities.videoStreamSettings.supportedResolutions(supported_resolutions)) + end end function CameraAttributeHandlers.allocated_video_streams_handler(driver, device, ib, response) 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 index 677e2d5dd6..7598b89893 100644 --- 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 @@ -10,6 +10,8 @@ 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.MAX_RESOLUTION = "__max_resolution" +CameraFields.MIN_RESOLUTION = "__min_resolution" CameraFields.TRIGGERED_ZONES = "__triggered_zones" CameraFields.VIEWPORT = "__viewport" 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 index a93e757c16..1caa9737bb 100644 --- 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 @@ -102,6 +102,38 @@ function CameraUtils.compute_fps(max_encoded_pixel_rate, width, height, max_fps) return math.tointeger(math.floor(fps / fps_step) * fps_step) end +function CameraUtils.build_supported_resolutions(device, max_encoded_pixel_rate, max_fps) + local resolutions = {} + local added_resolutions = {} + + local function add_resolution(width, height) + local key = width .. "x" .. height + if not added_resolutions[key] then + local resolution = { width = width, height = height } + resolution.fps = CameraUtils.compute_fps(max_encoded_pixel_rate, width, height, max_fps) + table.insert(resolutions, resolution) + added_resolutions[key] = true + end + end + + local min_resolution = device:get_field(camera_fields.MIN_RESOLUTION) + if min_resolution then + add_resolution(min_resolution.width, min_resolution.height) + end + + local trade_off_resolutions = device:get_field(camera_fields.SUPPORTED_RESOLUTIONS) + for _, v in pairs(trade_off_resolutions or {}) do + add_resolution(v.width, v.height) + end + + local max_resolution = device:get_field(camera_fields.MAX_RESOLUTION) + if max_resolution then + add_resolution(max_resolution.width, max_resolution.height) + end + + return resolutions +end + function CameraUtils.profile_changed(synced_components, prev_components) if #synced_components ~= #prev_components then return true diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index db8d980fab..930cb069dc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -683,6 +683,18 @@ local function receive_max_encoded_pixel_rate() }) end +local function receive_min_viewport() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.MinViewportResolution:build_test_report_data( + mock_device, CAMERA_EP, clusters.CameraAvStreamManagement.types.VideoResolutionStruct({ + width = 1920, + height = 1080 + }) + ) + }) +end + local function receive_video_sensor_params() test.socket.matter:__queue_receive({ mock_device.id, @@ -697,6 +709,15 @@ local function receive_video_sensor_params() }) end +local function emit_min_viewport() + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.cameraViewportSettings.minViewportResolution({ + width = 1920, + height = 1080, + })) + ) +end + local function emit_video_sensor_parameters() test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.cameraViewportSettings.videoSensorParameters({ @@ -719,6 +740,11 @@ local function emit_supported_resolutions() width = 3840, height = 2160, fps = 15 + }, + { + width = 7360, + height = 4912, + fps = 0 } })) ) @@ -730,12 +756,14 @@ end -- 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", + "Rate Distortion Trade Off Points, MaxEncodedPixelRate, MinViewport, 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_min_viewport() + emit_min_viewport() receive_video_sensor_params() emit_video_sensor_parameters() emit_supported_resolutions() @@ -743,11 +771,13 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "Rate Distortion Trade Off Points, VideoSensorParams, MaxEncodedPixelRate reports should generate appropriate events", + "Rate Distortion Trade Off Points, MinViewport, VideoSensorParams, MaxEncodedPixelRate reports should generate appropriate events", function() update_device_profile() test.wait_for_events() receive_rate_distortion_trade_off_points() + receive_min_viewport() + emit_min_viewport() receive_video_sensor_params() emit_video_sensor_parameters() receive_max_encoded_pixel_rate() @@ -756,11 +786,13 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "MaxEncodedPixelRate, VideoSensorParams, Rate Distortion Trade Off Points reports should generate appropriate events", + "MaxEncodedPixelRate, MinViewport, 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_min_viewport() + emit_min_viewport() receive_video_sensor_params() emit_video_sensor_parameters() receive_rate_distortion_trade_off_points() From 3d0ff3458fbbc25ad24e92fb301d65f56e4e018e Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri, 13 Feb 2026 11:01:53 -0600 Subject: [PATCH 108/538] Matter: Improve component-changed comparisons for modular profile infoChanged events (#2775) --- .../matter-lock/src/new-matter-lock/init.lua | 33 ++++++-- .../air_quality_sensor_utils/utils.lua | 24 ++++-- .../sub_drivers/air_quality_sensor/init.lua | 3 +- .../SmartThings/matter-switch/src/init.lua | 3 +- .../sub_drivers/camera/camera_utils/utils.lua | 18 ----- .../src/sub_drivers/camera/init.lua | 2 +- .../src/switch_utils/device_configuration.lua | 1 - .../matter-switch/src/switch_utils/fields.lua | 2 - .../matter-switch/src/switch_utils/utils.lua | 27 +++++++ .../src/test/test_matter_light_fan.lua | 81 +++++++++++++++++-- 10 files changed, 149 insertions(+), 45 deletions(-) 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 11aa2b0884..a91790cedb 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -22,8 +22,6 @@ local MIN_EPOCH_S = 0 local MAX_EPOCH_S = 0xffffffff local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 -local MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED" - local RESPONSE_STATUS_MAP = { [DoorLock.types.DlStatus.SUCCESS] = "success", [DoorLock.types.DlStatus.FAILURE] = "failure", @@ -203,7 +201,6 @@ local function match_profile_modular(driver, device) table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) - device:set_field(MODULAR_PROFILE_UPDATED, true) end local function match_profile_switch(driver, device) @@ -241,11 +238,37 @@ local function match_profile_switch(driver, device) device:try_update_metadata({profile = profile_name}) end +local function profile_changed(latest_profile, previous_profile) + if latest_profile.id ~= previous_profile.id then + return true + end + for component_id, synced_component in pairs(latest_profile.components or {}) do + local prev_component = previous_profile.components[component_id] + if prev_component == nil then + return true + end + if #synced_component.capabilities ~= #prev_component.capabilities then + return true + end + -- Build a table of capability IDs from the previous component. Then, use this map to check + -- that all capabilities in the synced component existed in the previous component. + local prev_cap_ids = {} + for _, capability in ipairs(prev_component.capabilities or {}) do + prev_cap_ids[capability.id] = true + end + for _, capability in ipairs(synced_component.capabilities or {}) do + if not prev_cap_ids[capability.id] then + return true + end + end + end + return false +end + local function info_changed(driver, device, event, args) - if device.profile.id == args.old_st_store.profile.id and not device:get_field(MODULAR_PROFILE_UPDATED) then + if not profile_changed(device.profile, args.old_st_store.profile) then return end - device:set_field(MODULAR_PROFILE_UPDATED, nil) for cap_id, attributes in pairs(subscribed_attributes) do if device:supports_capability_by_id(cap_id) then for _, attr in ipairs(attributes) do 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 index 95ca80964c..5c1726d6d8 100644 --- 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 @@ -77,17 +77,26 @@ function AirQualitySensorUtils.set_supported_health_concern_values(device) end end -function AirQualitySensorUtils.profile_changed(synced_components, prev_components) - if #synced_components ~= #prev_components then +function AirQualitySensorUtils.profile_changed(latest_profile, previous_profile) + if latest_profile.id ~= previous_profile.id 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 + for component_id, synced_component in pairs(latest_profile.components or {}) do + local prev_component = previous_profile.components[component_id] + if prev_component == nil then return true end - for _, capability in pairs(component.capabilities or {}) do - if prev_components[component.id][capability.id] == nil then + if #synced_component.capabilities ~= #prev_component.capabilities then + return true + end + -- Build a table of capability IDs from the previous component. Then, use this map to check + -- that all capabilities in the synced component existed in the previous component. + local prev_cap_ids = {} + for _, capability in ipairs(prev_component.capabilities or {}) do + prev_cap_ids[capability.id] = true + end + for _, capability in ipairs(synced_component.capabilities or {}) do + if not prev_cap_ids[capability.id] then return true end end @@ -95,4 +104,5 @@ function AirQualitySensorUtils.profile_changed(synced_components, prev_component return false end + return AirQualitySensorUtils 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 index 98b8430c98..41c6514b40 100644 --- 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 @@ -66,8 +66,7 @@ function AirQualitySensorLifecycleHandlers.device_init(driver, device) end function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, args) - if device.profile.id ~= args.old_st_store.profile.id or - aqs_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) then + if aqs_utils.profile_changed(device.profile, args.old_st_store.profile) 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) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index cac42e4483..12ee2b3662 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -64,8 +64,7 @@ function SwitchLifecycleHandlers.driver_switched(driver, device) end function SwitchLifecycleHandlers.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 - device:set_field(fields.MODULAR_PROFILE_UPDATED, nil) + if switch_utils.profile_changed(device.profile, args.old_st_store.profile) then if device.network_type == device_lib.NETWORK_TYPE_MATTER then device:subscribe() button_cfg.configure_buttons(device, 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 index 1caa9737bb..5f3205d73b 100644 --- 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 @@ -134,24 +134,6 @@ function CameraUtils.build_supported_resolutions(device, max_encoded_pixel_rate, return resolutions 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 = {} diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index f13589ff41..c32e131572 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -47,7 +47,7 @@ function CameraLifecycleHandlers.driver_switched(driver, device) end function CameraLifecycleHandlers.info_changed(driver, device, event, args) - if camera_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) then + if switch_utils.profile_changed(device.profile, args.old_st_store.profile) 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 8542972320..98a300924f 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -238,7 +238,6 @@ function DeviceConfiguration.match_profile(driver, device) 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 6eb03b1472..ec620eaa65 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -147,8 +147,6 @@ SwitchFields.ELECTRICAL_SENSOR_EPS = "__electrical_sensor_eps" --- for an Electrical Sensor EP with a "primary" endpoint, used during device profiling. SwitchFields.ELECTRICAL_TAGS = "__electrical_tags" -SwitchFields.MODULAR_PROFILE_UPDATED = "__modular_profile_updated" - SwitchFields.profiling_data = { POWER_TOPOLOGY = "__power_topology", BATTERY_SUPPORT = "__battery_support", diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 0592d9a342..16d602cfc1 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -325,6 +325,33 @@ function utils.create_multi_press_values_list(size, supportsHeld) return list end +function utils.profile_changed(latest_profile, previous_profile) + if latest_profile.id ~= previous_profile.id then + return true + end + for component_id, synced_component in pairs(latest_profile.components or {}) do + local prev_component = previous_profile.components[component_id] + if prev_component == nil then + return true + end + if #synced_component.capabilities ~= #prev_component.capabilities then + return true + end + -- Build a table of capability IDs from the previous component. Then, use this map to check + -- that all capabilities in the synced component existed in the previous component. + local prev_cap_ids = {} + for _, capability in ipairs(prev_component.capabilities or {}) do + prev_cap_ids[capability.id] = true + end + for _, capability in ipairs(synced_component.capabilities or {}) do + if not prev_cap_ids[capability.id] then + return true + end + end + end + return false +end + function utils.detect_bridge(device) return #utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.AGGREGATOR) > 0 end 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 e1af3ad52b..b5ae89fd7a 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 @@ -16,7 +16,8 @@ local mock_device_ep2 = 2 local mock_device = test.mock_device.build_test_matter_device({ label = "Matter Fan Light", - profile = t_utils.get_profile_definition("fan-modular.yml", {}), + profile = t_utils.get_profile_definition("fan-modular.yml", + {enabled_optional_capabilities = {{"main", {"fanSpeedPercent", "fanMode"}}}}), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -58,6 +59,40 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) +local mock_device_capabilities_disabled = test.mock_device.build_test_matter_device({ + label = "Matter Fan Light", + profile = t_utils.get_profile_definition("fan-modular.yml", + {enabled_optional_capabilities = {{"main", {}}}}), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + matter_version = { + software = 1, + hardware = 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 = mock_device_ep2, + clusters = { + {cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 15}, + }, + device_types = { + {device_type_id = 0x002B, device_type_revision = 1,} -- Fan + } + } + } +}) + local CLUSTER_SUBSCRIBE_LIST ={ clusters.OnOff.attributes.OnOff, clusters.LevelControl.attributes.CurrentLevel, @@ -110,16 +145,48 @@ local function test_init() }) 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) +test.register_coroutine_test( + "Component-capability update without profile ID update should cause re-subscribe in infoChanged handler", function() + local cluster_subscribe_list ={ + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode, + clusters.FanControl.attributes.PercentCurrent, + } + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_capabilities_disabled) + for i, clus in ipairs(cluster_subscribe_list) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_capabilities_disabled)) end + end + test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed( + {profile = {id = "00000000-1111-2222-3333-000000000004", components = { main = {capabilities={{id="fanSpeedPercent", version=1}, {id="fanMode", version=1}, {id="firmwareUpdate", version=1}, {id="refresh", version=1}}}}}}) + ) + test.socket.matter:__expect_send({mock_device_capabilities_disabled.id, subscribe_request}) + end, + { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } +) + +test.register_coroutine_test( + "No component-capability update an no profile ID update should not cause a re-subscribe in infoChanged handler", function() + local cluster_subscribe_list ={ + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode, + clusters.FanControl.attributes.PercentCurrent, + } + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_capabilities_disabled) + for i, clus in ipairs(cluster_subscribe_list) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_capabilities_disabled)) end + end + test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed( + {profile = {id = "00000000-1111-2222-3333-000000000004", components = { main = {capabilities={{id="firmwareUpdate", version=1}, {id="refresh", version=1}}}}}}) + ) + end, + { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } +) + + test.register_coroutine_test( "Switch capability should send the appropriate commands", function() test.socket.capability:__queue_receive( From c26bfbed6f8c8ccc716c265f726b0a951652ad0a Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Fri, 13 Feb 2026 14:20:45 -0600 Subject: [PATCH 109/538] Updating zigbee-switch and zwave-switch to use the sub_driver.lua file method --- .../SmartThings/zigbee-switch/src/init.lua | 35 +---------------- .../zigbee-switch/src/sub_drivers.lua | 38 +++++++++++++++++++ drivers/SmartThings/zwave-switch/src/init.lua | 25 +----------- .../zwave-switch/src/sub_drivers.lua | 28 ++++++++++++++ 4 files changed, 68 insertions(+), 58 deletions(-) create mode 100644 drivers/SmartThings/zigbee-switch/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-switch/src/sub_drivers.lua diff --git a/drivers/SmartThings/zigbee-switch/src/init.lua b/drivers/SmartThings/zigbee-switch/src/init.lua index a7be3f8801..aa245f5910 100644 --- a/drivers/SmartThings/zigbee-switch/src/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/init.lua @@ -54,8 +54,6 @@ 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, @@ -69,38 +67,7 @@ local zigbee_switch_driver_template = { capabilities.relativeHumidityMeasurement, capabilities.temperatureMeasurement, }, - sub_drivers = { - lazy_load_if_possible("non_zigbee_devices"), - lazy_load_if_possible("hanssem"), - lazy_load_if_possible("aqara"), - lazy_load_if_possible("aqara-light"), - lazy_load_if_possible("ezex"), - lazy_load_if_possible("rexense"), - lazy_load_if_possible("sinope"), - lazy_load_if_possible("sinope-dimmer"), - lazy_load_if_possible("zigbee-dimmer-power-energy"), - lazy_load_if_possible("zigbee-metering-plug-power-consumption-report"), - lazy_load_if_possible("jasco"), - lazy_load_if_possible("multi-switch-no-master"), - lazy_load_if_possible("zigbee-dual-metering-switch"), - lazy_load_if_possible("rgb-bulb"), - lazy_load_if_possible("zigbee-dimming-light"), - lazy_load_if_possible("white-color-temp-bulb"), - lazy_load_if_possible("rgbw-bulb"), - (version.api < 16) and lazy_load_if_possible("zll-dimmer-bulb") or nil, - lazy_load_if_possible("ikea-xy-color-bulb"), - lazy_load_if_possible("zll-polling"), - lazy_load_if_possible("zigbee-switch-power"), - lazy_load_if_possible("ge-link-bulb"), - lazy_load_if_possible("bad_on_off_data_type"), - lazy_load_if_possible("robb"), - lazy_load_if_possible("wallhero"), - 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-IO") - }, + sub_drivers = require("sub_drivers"), zigbee_handlers = { global = { [SIMPLE_METERING_ID] = { diff --git a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua new file mode 100644 index 0000000000..c232b40329 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua @@ -0,0 +1,38 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +local version = require "version" + +local lazy_load_if_possible = require "lazy_load_subdriver" + +return { + lazy_load_if_possible("non_zigbee_devices"), + lazy_load_if_possible("hanssem"), + lazy_load_if_possible("aqara"), + lazy_load_if_possible("aqara-light"), + lazy_load_if_possible("ezex"), + lazy_load_if_possible("rexense"), + lazy_load_if_possible("sinope"), + lazy_load_if_possible("sinope-dimmer"), + lazy_load_if_possible("zigbee-dimmer-power-energy"), + lazy_load_if_possible("zigbee-metering-plug-power-consumption-report"), + lazy_load_if_possible("jasco"), + lazy_load_if_possible("multi-switch-no-master"), + lazy_load_if_possible("zigbee-dual-metering-switch"), + lazy_load_if_possible("rgb-bulb"), + lazy_load_if_possible("zigbee-dimming-light"), + lazy_load_if_possible("white-color-temp-bulb"), + lazy_load_if_possible("rgbw-bulb"), + (version.api < 16) and lazy_load_if_possible("zll-dimmer-bulb") or nil, + lazy_load_if_possible("ikea-xy-color-bulb"), + lazy_load_if_possible("zll-polling"), + lazy_load_if_possible("zigbee-switch-power"), + lazy_load_if_possible("ge-link-bulb"), + lazy_load_if_possible("bad_on_off_data_type"), + lazy_load_if_possible("robb"), + lazy_load_if_possible("wallhero"), + 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-IO") +} diff --git a/drivers/SmartThings/zwave-switch/src/init.lua b/drivers/SmartThings/zwave-switch/src/init.lua index 405600e962..26cca570a7 100644 --- a/drivers/SmartThings/zwave-switch/src/init.lua +++ b/drivers/SmartThings/zwave-switch/src/init.lua @@ -17,8 +17,6 @@ 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 @@ -120,28 +118,7 @@ local driver_template = { [SwitchMultilevel.STOP_LEVEL_CHANGE] = switch_multilevel_stop_level_change_handler } }, - sub_drivers = { - lazy_load_if_possible("eaton-accessory-dimmer"), - 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"), - lazy_load_if_possible("eaton-anyplace-switch"), - lazy_load_if_possible("fibaro-wall-plug-us"), - lazy_load_if_possible("dawon-wall-smart-switch"), - lazy_load_if_possible("zooz-power-strip"), - lazy_load_if_possible("aeon-smart-strip"), - lazy_load_if_possible("qubino-switches"), - lazy_load_if_possible("fibaro-double-switch"), - lazy_load_if_possible("fibaro-single-switch"), - lazy_load_if_possible("eaton-5-scene-keypad"), - lazy_load_if_possible("ecolink-switch"), - lazy_load_if_possible("multi-metering-switch"), - lazy_load_if_possible("zooz-zen-30-dimmer-relay"), - lazy_load_if_possible("multichannel-device"), - lazy_load_if_possible("aeotec-smart-switch"), - lazy_load_if_possible("aeotec-heavy-duty") - }, + sub_drivers = require("sub_drivers"), lifecycle_handlers = { init = device_init, infoChanged = info_changed, diff --git a/drivers/SmartThings/zwave-switch/src/sub_drivers.lua b/drivers/SmartThings/zwave-switch/src/sub_drivers.lua new file mode 100644 index 0000000000..b4cd3d57e9 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/sub_drivers.lua @@ -0,0 +1,28 @@ + +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" + +return { + lazy_load_if_possible("eaton-accessory-dimmer"), + 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"), + lazy_load_if_possible("eaton-anyplace-switch"), + lazy_load_if_possible("fibaro-wall-plug-us"), + lazy_load_if_possible("dawon-wall-smart-switch"), + lazy_load_if_possible("zooz-power-strip"), + lazy_load_if_possible("aeon-smart-strip"), + lazy_load_if_possible("qubino-switches"), + lazy_load_if_possible("fibaro-double-switch"), + lazy_load_if_possible("fibaro-single-switch"), + lazy_load_if_possible("eaton-5-scene-keypad"), + lazy_load_if_possible("ecolink-switch"), + lazy_load_if_possible("multi-metering-switch"), + lazy_load_if_possible("zooz-zen-30-dimmer-relay"), + lazy_load_if_possible("multichannel-device"), + lazy_load_if_possible("aeotec-smart-switch"), + lazy_load_if_possible("aeotec-heavy-duty") +} From 46495218acd0145dfdd03cdc8006d036cfb40653 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 10 Feb 2026 15:54:40 -0800 Subject: [PATCH 110/538] Merge pull request #2758 from SmartThingsCommunity/new_device/WWSTCERT-10212 WWSTCERT-10212 Osram SMART WIFI MATTER WALL SWITCH 1G --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index e418d34d3b..948279c3f3 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1837,6 +1837,11 @@ matterManufacturer: vendorId: 0x1189 productId: 0x0B93 deviceProfileName: light-color-level + - id: "4489/2194" + deviceLabel: SMART WIFI MATTER WALL SWITCH 1G + vendorId: 0x1189 + productId: 0x0892 + deviceProfileName: switch-binary #Shelly - id: "5264/1" deviceLabel: Shelly Plug S MTR Gen3 From 184561fce6ae1417be3f8af7334cc9a284f242bc Mon Sep 17 00:00:00 2001 From: Steven Green Date: Wed, 11 Feb 2026 15:25:00 -0800 Subject: [PATCH 111/538] Merge pull request #2759 from SmartThingsCommunity/new_device/WWSTCERT-10019 WWSTCERT-10019 WINDOWSTORY GATEWAY-MT --- drivers/SmartThings/matter-window-covering/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-window-covering/fingerprints.yml b/drivers/SmartThings/matter-window-covering/fingerprints.yml index 0de1e91af8..531dc8c36f 100644 --- a/drivers/SmartThings/matter-window-covering/fingerprints.yml +++ b/drivers/SmartThings/matter-window-covering/fingerprints.yml @@ -107,6 +107,12 @@ matterManufacturer: vendorId: 0x139C productId: 0xFA17 deviceProfileName: window-covering +#WINDOWSTORY + - id: "5496/6657" + deviceLabel: GATEWAY-MT + vendorId: 0x1578 + productId: 0x1A01 + deviceProfileName: window-covering-tilt #WISTAR - id: "5207/3" deviceLabel: WISTAR WSERD16-B Smart Tubular Motor From 4458b7f874e9600f3c508d826eba8fb32c9185ad Mon Sep 17 00:00:00 2001 From: Steven Green Date: Wed, 11 Feb 2026 16:02:32 -0800 Subject: [PATCH 112/538] Merge pull request #2772 from SmartThingsCommunity/new_device/WWSTCERT-10319 WWSTCERT-10319 GELUBU Door Lock S93 --- drivers/SmartThings/zigbee-lock/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/zigbee-lock/fingerprints.yml b/drivers/SmartThings/zigbee-lock/fingerprints.yml index 86870aa962..5c04d78ce5 100644 --- a/drivers/SmartThings/zigbee-lock/fingerprints.yml +++ b/drivers/SmartThings/zigbee-lock/fingerprints.yml @@ -1,4 +1,10 @@ zigbeeManufacturer: + # GELUBU + - id: "GELUBU/S93" + deviceLabel: GELUBU Door Lock S93 + manufacturer: GELUBU + model: S93 + deviceProfileName: lock-battery # YALE - id: "Yale YRD220/240" deviceLabel: "Yale Door Lock" From 95675a1315ba2ebca5116766acdf818fb4a8c159 Mon Sep 17 00:00:00 2001 From: Carter Swedal Date: Thu, 12 Feb 2026 14:36:02 -0600 Subject: [PATCH 113/538] Merge pull request #2774 from SmartThingsCommunity/chore/fixup-tests-for-60 Fixup tests for new native handlers in 60 --- .../src/test/test_all_capability_zigbee_bulb.lua | 16 ++++++++++++++++ .../zigbee-switch/src/test/test_zll_rgb_bulb.lua | 2 ++ .../src/test/test_generic_sensor.lua | 8 ++++++++ 3 files changed, 26 insertions(+) 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 70dfc96780..b581c5c61e 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 @@ -233,6 +233,14 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_device:generate_test_message("main", capabilities.colorControl.hue(0)) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "hue" } + } } } ) @@ -250,6 +258,14 @@ 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/zigbee-switch/src/test/test_zll_rgb_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_zll_rgb_bulb.lua index 3435b22288..a8384d1b13 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 @@ -205,6 +205,7 @@ for _, data in ipairs(test_data) do if data.saturation ~= nil then test.socket.zigbee:__queue_receive({mock_device.id, ColorControl.attributes.CurrentSaturation:build_test_attr_report(mock_device, math.ceil(data.saturation / 100 * 0xFE))}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.saturation(data.saturation))) + mock_device:expect_native_attr_handler_registration("colorControl", "saturation") end test.timer.__create_and_queue_test_time_advance_timer(0.2, "oneshot") @@ -249,6 +250,7 @@ for _, data in ipairs(test_data) do if data.hue ~= nil then test.socket.zigbee:__queue_receive({mock_device.id, ColorControl.attributes.CurrentHue:build_test_attr_report(mock_device, math.ceil(data.hue / 100 * 0xFE))}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.hue(data.hue))) + mock_device:expect_native_attr_handler_registration("colorControl", "hue") end test.timer.__create_and_queue_test_time_advance_timer(0.2, "oneshot") diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua index 643c69dd1f..e6fff57b0d 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua @@ -1548,6 +1548,14 @@ test.register_message_test( mock_device, Meter:Get({scale = 0}) ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_device, + Meter:Get({scale = 4}) + ) } }, { From d18914282669e4365164adf6699dfa5cdf818590 Mon Sep 17 00:00:00 2001 From: Zach Varberg Date: Wed, 18 Feb 2026 08:35:01 -0600 Subject: [PATCH 114/538] Revert "CHAD-17070: zigbee-lock lazy loading of subdrivers" This reverts commit aa71a9a9cd3b17cf00699b3dec8d24db6052ad83. --- .../zigbee-lock/src/configurations.lua | 15 ++++++-- drivers/SmartThings/zigbee-lock/src/init.lua | 23 ++++++++++--- .../zigbee-lock/src/lazy_load_subdriver.lua | 15 -------- .../src/lock-without-codes/can_handle.lua | 14 -------- .../src/lock-without-codes/fingerprints.lua | 9 ----- .../src/lock-without-codes/init.lua | 30 +++++++++++++--- .../zigbee-lock/src/lock_utils.lua | 15 ++++++-- .../zigbee-lock/src/samsungsds/can_handle.lua | 11 ------ .../zigbee-lock/src/samsungsds/init.lua | 20 ++++++++--- .../zigbee-lock/src/sub_drivers.lua | 11 ------ .../zigbee-lock/src/test/test_c2o_lock.lua | 15 ++++++-- .../src/test/test_generic_lock_migration.lua | 17 ++++++++-- ..._yale_fingerprint_bad_battery_reporter.lua | 15 ++++++-- .../zigbee-lock/src/test/test_zigbee_lock.lua | 15 ++++++-- .../test/test_zigbee_lock_code_migration.lua | 15 ++++++-- .../test_zigbee_yale-bad-battery-reporter.lua | 15 ++++++-- .../test_zigbee_yale-fingerprint-lock.lua | 15 ++++++-- .../zigbee-lock/src/test/test_zigbee_yale.lua | 15 ++++++-- .../src/yale-fingerprint-lock/can_handle.lua | 14 -------- .../yale-fingerprint-lock/fingerprints.lua | 11 ------ .../src/yale-fingerprint-lock/init.lua | 32 ++++++++++++++--- .../zigbee-lock/src/yale/can_handle.lua | 11 ------ .../SmartThings/zigbee-lock/src/yale/init.lua | 23 ++++++++++--- .../zigbee-lock/src/yale/sub_drivers.lua | 8 ----- .../yale-bad-battery-reporter/can_handle.lua | 14 -------- .../fingerprints.lua | 13 ------- .../yale/yale-bad-battery-reporter/init.lua | 34 ++++++++++++++++--- 27 files changed, 268 insertions(+), 177 deletions(-) delete mode 100644 drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/sub_drivers.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua diff --git a/drivers/SmartThings/zigbee-lock/src/configurations.lua b/drivers/SmartThings/zigbee-lock/src/configurations.lua index 88e4e59f80..a2429252b0 100644 --- a/drivers/SmartThings/zigbee-lock/src/configurations.lua +++ b/drivers/SmartThings/zigbee-lock/src/configurations.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-lock/src/init.lua b/drivers/SmartThings/zigbee-lock/src/init.lua index 94f5adc0c4..ce6894b868 100644 --- a/drivers/SmartThings/zigbee-lock/src/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Zigbee Driver utilities local defaults = require "st.zigbee.defaults" @@ -435,7 +445,12 @@ local zigbee_lock_driver = { [capabilities.refresh.commands.refresh.NAME] = refresh } }, - sub_drivers = require("sub_drivers"), + sub_drivers = { + require("samsungsds"), + require("yale"), + require("yale-fingerprint-lock"), + require("lock-without-codes") + }, lifecycle_handlers = { doConfigure = do_configure, added = device_added, diff --git a/drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua deleted file mode 100644 index 0bee6d2a75..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua +++ /dev/null @@ -1,15 +0,0 @@ --- 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-lock/src/lock-without-codes/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua deleted file mode 100644 index 543e43a8b1..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_lock_without_codes(opts, driver, device) - local FINGERPRINTS = require("lock-without-codes.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true, require("lock-without-codes") - end - end - return false -end - -return can_handle_lock_without_codes diff --git a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua deleted file mode 100644 index 63ae82b46c..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua +++ /dev/null @@ -1,9 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local LOCK_WITHOUT_CODES_FINGERPRINTS = { - { model = "E261-KR0B0Z0-HA" }, - { mfr = "Danalock", model = "V3-BTZB" } -} - -return LOCK_WITHOUT_CODES_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua index e5c6de3408..7272991459 100644 --- a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 configurationMap = require "configurations" local clusters = require "st.zigbee.zcl.clusters" @@ -9,7 +19,19 @@ local capabilities = require "st.capabilities" local DoorLock = clusters.DoorLock local PowerConfiguration = clusters.PowerConfiguration +local LOCK_WITHOUT_CODES_FINGERPRINTS = { + { model = "E261-KR0B0Z0-HA" }, + { mfr = "Danalock", model = "V3-BTZB" } +} +local function can_handle_lock_without_codes(opts, driver, device) + for _, fingerprint in ipairs(LOCK_WITHOUT_CODES_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 configuration = configurationMap.get_device_configuration(device) @@ -73,7 +95,7 @@ local lock_without_codes = { } } }, - can_handle = require("lock-without-codes.can_handle"), + can_handle = can_handle_lock_without_codes } return lock_without_codes diff --git a/drivers/SmartThings/zigbee-lock/src/lock_utils.lua b/drivers/SmartThings/zigbee-lock/src/lock_utils.lua index a02a59963c..0a36a9685e 100644 --- a/drivers/SmartThings/zigbee-lock/src/lock_utils.lua +++ b/drivers/SmartThings/zigbee-lock/src/lock_utils.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 utils = require "st.utils" local capabilities = require "st.capabilities" local json = require "st.json" diff --git a/drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua deleted file mode 100644 index c483b2fe27..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function samsungsds_can_handle(opts, driver, device, ...) - if device:get_manufacturer() == "SAMSUNG SDS" then - return true, require("samsungsds") - end - return false -end - -return samsungsds_can_handle diff --git a/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua b/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua index fff290df5d..b529dd3fd1 100644 --- a/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 device_management = require "st.zigbee.device_management" local clusters = require "st.zigbee.zcl.clusters" @@ -102,7 +112,9 @@ local samsung_sds_driver = { added = device_added, init = device_init }, - can_handle = require("samsungsds.can_handle"), + can_handle = function(opts, driver, device, ...) + return device:get_manufacturer() == "SAMSUNG SDS" + end } return samsung_sds_driver diff --git a/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua b/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua deleted file mode 100644 index ff4bf8980d..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua +++ /dev/null @@ -1,11 +0,0 @@ --- 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("samsungsds"), - lazy_load_if_possible("yale"), - lazy_load_if_possible("yale-fingerprint-lock"), - lazy_load_if_possible("lock-without-codes"), -} -return sub_drivers diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua index 146c628b8b..b6fa3d1323 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua index f287300f60..ed4ce6e3cc 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua @@ -1,5 +1,16 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 +-- 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. -- Mock out globals local test = require "integration_test" @@ -34,4 +45,4 @@ test.register_coroutine_test( end ) -test.run_registered_tests() +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua index 4f50c3c24a..d499d7ff66 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua index 3ed037cd54..80d10d092e 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua index 7950e3f62d..1aa9432933 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua index b8f4c386d9..ee1745e3b7 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua index 7cda71cdb3..2255c063a3 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua index 75ad49a1f5..34b6881028 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua deleted file mode 100644 index a80632bf80..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local yale_fingerprint_lock_models = function(opts, driver, device) - local FINGERPRINTS = require("yale-fingerprint-lock.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true, require("yale-fingerprint-lock") - end - end - return false -end - -return yale_fingerprint_lock_models diff --git a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua deleted file mode 100644 index b3db27d719..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local YALE_FINGERPRINT_LOCK = { - { mfr = "ASSA ABLOY iRevo", model = "iZBModule01" }, - { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, - { mfr = "ASSA ABLOY iRevo", model = "0700000001" }, - { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } -} - -return YALE_FINGERPRINT_LOCK diff --git a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua index b78d043784..9d0a0b4148 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" @@ -9,7 +19,21 @@ local LockCodes = capabilities.lockCodes local YALE_FINGERPRINT_MAX_CODES = 0x1E +local YALE_FINGERPRINT_LOCK = { + { mfr = "ASSA ABLOY iRevo", model = "iZBModule01" }, + { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, + { mfr = "ASSA ABLOY iRevo", model = "0700000001" }, + { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } +} +local yale_fingerprint_lock_models = function(opts, driver, device) + for _, fingerprint in ipairs(YALE_FINGERPRINT_LOCK) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end local handle_max_codes = function(driver, device, value) device:emit_event(LockCodes.maxCodes(YALE_FINGERPRINT_MAX_CODES), { visibility = { displayed = false } }) @@ -24,7 +48,7 @@ local yale_fingerprint_lock_driver = { } } }, - can_handle = require("yale-fingerprint-lock.can_handle"), + can_handle = yale_fingerprint_lock_models } return yale_fingerprint_lock_driver diff --git a/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua deleted file mode 100644 index 54340c7811..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function yale_can_handle(opts, driver, device, ...) - if device:get_manufacturer() == "ASSA ABLOY iRevo" or device:get_manufacturer() == "Yale" then - return true, require("yale") - end - return false -end - -return yale_can_handle diff --git a/drivers/SmartThings/zigbee-lock/src/yale/init.lua b/drivers/SmartThings/zigbee-lock/src/yale/init.lua index 8ba98b2aa8..73e984036e 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Zigbee Spec Utils local clusters = require "st.zigbee.zcl.clusters" @@ -142,7 +151,11 @@ local yale_door_lock_driver = { [LockCodes.commands.setCode.NAME] = set_code } }, - sub_drivers = require("yale.sub_drivers"), + + sub_drivers = { require("yale.yale-bad-battery-reporter") }, + can_handle = function(opts, driver, device, ...) + return device:get_manufacturer() == "ASSA ABLOY iRevo" or device:get_manufacturer() == "Yale" + end } return yale_door_lock_driver diff --git a/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua b/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua deleted file mode 100644 index 4b546979d3..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua +++ /dev/null @@ -1,8 +0,0 @@ --- 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("yale.yale-bad-battery-reporter"), -} -return sub_drivers diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua deleted file mode 100644 index 67169e9268..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local is_bad_yale_lock_models = function(opts, driver, device) - local FINGERPRINTS = require("yale.yale-bad-battery-reporter.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true, require("yale.yale-bad-battery-reporter") - end - end - return false -end - -return is_bad_yale_lock_models diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua deleted file mode 100644 index cbb7c3404f..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua +++ /dev/null @@ -1,13 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local BAD_YALE_LOCK_FINGERPRINTS = { - { mfr = "Yale", model = "YRD220/240 TSDB" }, - { mfr = "Yale", model = "YRL220 TS LL" }, - { mfr = "Yale", model = "YRD210 PB DB" }, - { mfr = "Yale", model = "YRL210 PB LL" }, - { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, - { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } -} - -return BAD_YALE_LOCK_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua index 3b77f32563..59fdbf228b 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua @@ -1,11 +1,37 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" +local BAD_YALE_LOCK_FINGERPRINTS = { + { mfr = "Yale", model = "YRD220/240 TSDB" }, + { mfr = "Yale", model = "YRL220 TS LL" }, + { mfr = "Yale", model = "YRD210 PB DB" }, + { mfr = "Yale", model = "YRL210 PB LL" }, + { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, + { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } +} +local is_bad_yale_lock_models = function(opts, driver, device) + for _, fingerprint in ipairs(BAD_YALE_LOCK_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end local battery_report_handler = function(driver, device, value) device:emit_event(capabilities.battery.battery(value.value)) @@ -20,7 +46,7 @@ local bad_yale_driver = { } } }, - can_handle = require("yale.yale-bad-battery-reporter.can_handle"), + can_handle = is_bad_yale_lock_models } return bad_yale_driver From d13db83d9ec9a3ff4d0b2ff3f4cbdd16efffe768 Mon Sep 17 00:00:00 2001 From: Zach Varberg Date: Wed, 18 Feb 2026 08:35:01 -0600 Subject: [PATCH 115/538] Revert "CHAD-17070: zigbee-lock lazy loading of subdrivers" This reverts commit aa71a9a9cd3b17cf00699b3dec8d24db6052ad83. --- .../zigbee-lock/src/configurations.lua | 15 ++++++-- drivers/SmartThings/zigbee-lock/src/init.lua | 23 ++++++++++--- .../zigbee-lock/src/lazy_load_subdriver.lua | 15 -------- .../src/lock-without-codes/can_handle.lua | 14 -------- .../src/lock-without-codes/fingerprints.lua | 9 ----- .../src/lock-without-codes/init.lua | 30 +++++++++++++--- .../zigbee-lock/src/lock_utils.lua | 15 ++++++-- .../zigbee-lock/src/samsungsds/can_handle.lua | 11 ------ .../zigbee-lock/src/samsungsds/init.lua | 20 ++++++++--- .../zigbee-lock/src/sub_drivers.lua | 11 ------ .../zigbee-lock/src/test/test_c2o_lock.lua | 15 ++++++-- .../src/test/test_generic_lock_migration.lua | 17 ++++++++-- ..._yale_fingerprint_bad_battery_reporter.lua | 15 ++++++-- .../zigbee-lock/src/test/test_zigbee_lock.lua | 15 ++++++-- .../test/test_zigbee_lock_code_migration.lua | 15 ++++++-- .../test_zigbee_yale-bad-battery-reporter.lua | 15 ++++++-- .../test_zigbee_yale-fingerprint-lock.lua | 15 ++++++-- .../zigbee-lock/src/test/test_zigbee_yale.lua | 15 ++++++-- .../src/yale-fingerprint-lock/can_handle.lua | 14 -------- .../yale-fingerprint-lock/fingerprints.lua | 11 ------ .../src/yale-fingerprint-lock/init.lua | 32 ++++++++++++++--- .../zigbee-lock/src/yale/can_handle.lua | 11 ------ .../SmartThings/zigbee-lock/src/yale/init.lua | 23 ++++++++++--- .../zigbee-lock/src/yale/sub_drivers.lua | 8 ----- .../yale-bad-battery-reporter/can_handle.lua | 14 -------- .../fingerprints.lua | 13 ------- .../yale/yale-bad-battery-reporter/init.lua | 34 ++++++++++++++++--- 27 files changed, 268 insertions(+), 177 deletions(-) delete mode 100644 drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/sub_drivers.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua diff --git a/drivers/SmartThings/zigbee-lock/src/configurations.lua b/drivers/SmartThings/zigbee-lock/src/configurations.lua index 88e4e59f80..a2429252b0 100644 --- a/drivers/SmartThings/zigbee-lock/src/configurations.lua +++ b/drivers/SmartThings/zigbee-lock/src/configurations.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-lock/src/init.lua b/drivers/SmartThings/zigbee-lock/src/init.lua index 94f5adc0c4..ce6894b868 100644 --- a/drivers/SmartThings/zigbee-lock/src/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Zigbee Driver utilities local defaults = require "st.zigbee.defaults" @@ -435,7 +445,12 @@ local zigbee_lock_driver = { [capabilities.refresh.commands.refresh.NAME] = refresh } }, - sub_drivers = require("sub_drivers"), + sub_drivers = { + require("samsungsds"), + require("yale"), + require("yale-fingerprint-lock"), + require("lock-without-codes") + }, lifecycle_handlers = { doConfigure = do_configure, added = device_added, diff --git a/drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua deleted file mode 100644 index 0bee6d2a75..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua +++ /dev/null @@ -1,15 +0,0 @@ --- 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-lock/src/lock-without-codes/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua deleted file mode 100644 index 543e43a8b1..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_lock_without_codes(opts, driver, device) - local FINGERPRINTS = require("lock-without-codes.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true, require("lock-without-codes") - end - end - return false -end - -return can_handle_lock_without_codes diff --git a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua deleted file mode 100644 index 63ae82b46c..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua +++ /dev/null @@ -1,9 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local LOCK_WITHOUT_CODES_FINGERPRINTS = { - { model = "E261-KR0B0Z0-HA" }, - { mfr = "Danalock", model = "V3-BTZB" } -} - -return LOCK_WITHOUT_CODES_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua index e5c6de3408..7272991459 100644 --- a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 configurationMap = require "configurations" local clusters = require "st.zigbee.zcl.clusters" @@ -9,7 +19,19 @@ local capabilities = require "st.capabilities" local DoorLock = clusters.DoorLock local PowerConfiguration = clusters.PowerConfiguration +local LOCK_WITHOUT_CODES_FINGERPRINTS = { + { model = "E261-KR0B0Z0-HA" }, + { mfr = "Danalock", model = "V3-BTZB" } +} +local function can_handle_lock_without_codes(opts, driver, device) + for _, fingerprint in ipairs(LOCK_WITHOUT_CODES_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 configuration = configurationMap.get_device_configuration(device) @@ -73,7 +95,7 @@ local lock_without_codes = { } } }, - can_handle = require("lock-without-codes.can_handle"), + can_handle = can_handle_lock_without_codes } return lock_without_codes diff --git a/drivers/SmartThings/zigbee-lock/src/lock_utils.lua b/drivers/SmartThings/zigbee-lock/src/lock_utils.lua index a02a59963c..0a36a9685e 100644 --- a/drivers/SmartThings/zigbee-lock/src/lock_utils.lua +++ b/drivers/SmartThings/zigbee-lock/src/lock_utils.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 utils = require "st.utils" local capabilities = require "st.capabilities" local json = require "st.json" diff --git a/drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua deleted file mode 100644 index c483b2fe27..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function samsungsds_can_handle(opts, driver, device, ...) - if device:get_manufacturer() == "SAMSUNG SDS" then - return true, require("samsungsds") - end - return false -end - -return samsungsds_can_handle diff --git a/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua b/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua index fff290df5d..b529dd3fd1 100644 --- a/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 device_management = require "st.zigbee.device_management" local clusters = require "st.zigbee.zcl.clusters" @@ -102,7 +112,9 @@ local samsung_sds_driver = { added = device_added, init = device_init }, - can_handle = require("samsungsds.can_handle"), + can_handle = function(opts, driver, device, ...) + return device:get_manufacturer() == "SAMSUNG SDS" + end } return samsung_sds_driver diff --git a/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua b/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua deleted file mode 100644 index ff4bf8980d..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua +++ /dev/null @@ -1,11 +0,0 @@ --- 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("samsungsds"), - lazy_load_if_possible("yale"), - lazy_load_if_possible("yale-fingerprint-lock"), - lazy_load_if_possible("lock-without-codes"), -} -return sub_drivers diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua index 146c628b8b..b6fa3d1323 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua index f287300f60..ed4ce6e3cc 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua @@ -1,5 +1,16 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 +-- 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. -- Mock out globals local test = require "integration_test" @@ -34,4 +45,4 @@ test.register_coroutine_test( end ) -test.run_registered_tests() +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua index 4f50c3c24a..d499d7ff66 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua index 3ed037cd54..80d10d092e 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua index 7950e3f62d..1aa9432933 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua index b8f4c386d9..ee1745e3b7 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua index 7cda71cdb3..2255c063a3 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua index 75ad49a1f5..34b6881028 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua deleted file mode 100644 index a80632bf80..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local yale_fingerprint_lock_models = function(opts, driver, device) - local FINGERPRINTS = require("yale-fingerprint-lock.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true, require("yale-fingerprint-lock") - end - end - return false -end - -return yale_fingerprint_lock_models diff --git a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua deleted file mode 100644 index b3db27d719..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local YALE_FINGERPRINT_LOCK = { - { mfr = "ASSA ABLOY iRevo", model = "iZBModule01" }, - { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, - { mfr = "ASSA ABLOY iRevo", model = "0700000001" }, - { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } -} - -return YALE_FINGERPRINT_LOCK diff --git a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua index b78d043784..9d0a0b4148 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" @@ -9,7 +19,21 @@ local LockCodes = capabilities.lockCodes local YALE_FINGERPRINT_MAX_CODES = 0x1E +local YALE_FINGERPRINT_LOCK = { + { mfr = "ASSA ABLOY iRevo", model = "iZBModule01" }, + { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, + { mfr = "ASSA ABLOY iRevo", model = "0700000001" }, + { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } +} +local yale_fingerprint_lock_models = function(opts, driver, device) + for _, fingerprint in ipairs(YALE_FINGERPRINT_LOCK) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end local handle_max_codes = function(driver, device, value) device:emit_event(LockCodes.maxCodes(YALE_FINGERPRINT_MAX_CODES), { visibility = { displayed = false } }) @@ -24,7 +48,7 @@ local yale_fingerprint_lock_driver = { } } }, - can_handle = require("yale-fingerprint-lock.can_handle"), + can_handle = yale_fingerprint_lock_models } return yale_fingerprint_lock_driver diff --git a/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua deleted file mode 100644 index 54340c7811..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function yale_can_handle(opts, driver, device, ...) - if device:get_manufacturer() == "ASSA ABLOY iRevo" or device:get_manufacturer() == "Yale" then - return true, require("yale") - end - return false -end - -return yale_can_handle diff --git a/drivers/SmartThings/zigbee-lock/src/yale/init.lua b/drivers/SmartThings/zigbee-lock/src/yale/init.lua index 8ba98b2aa8..73e984036e 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. -- Zigbee Spec Utils local clusters = require "st.zigbee.zcl.clusters" @@ -142,7 +151,11 @@ local yale_door_lock_driver = { [LockCodes.commands.setCode.NAME] = set_code } }, - sub_drivers = require("yale.sub_drivers"), + + sub_drivers = { require("yale.yale-bad-battery-reporter") }, + can_handle = function(opts, driver, device, ...) + return device:get_manufacturer() == "ASSA ABLOY iRevo" or device:get_manufacturer() == "Yale" + end } return yale_door_lock_driver diff --git a/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua b/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua deleted file mode 100644 index 4b546979d3..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua +++ /dev/null @@ -1,8 +0,0 @@ --- 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("yale.yale-bad-battery-reporter"), -} -return sub_drivers diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua deleted file mode 100644 index 67169e9268..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local is_bad_yale_lock_models = function(opts, driver, device) - local FINGERPRINTS = require("yale.yale-bad-battery-reporter.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true, require("yale.yale-bad-battery-reporter") - end - end - return false -end - -return is_bad_yale_lock_models diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua deleted file mode 100644 index cbb7c3404f..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua +++ /dev/null @@ -1,13 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local BAD_YALE_LOCK_FINGERPRINTS = { - { mfr = "Yale", model = "YRD220/240 TSDB" }, - { mfr = "Yale", model = "YRL220 TS LL" }, - { mfr = "Yale", model = "YRD210 PB DB" }, - { mfr = "Yale", model = "YRL210 PB LL" }, - { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, - { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } -} - -return BAD_YALE_LOCK_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua index 3b77f32563..59fdbf228b 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua @@ -1,11 +1,37 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" +local BAD_YALE_LOCK_FINGERPRINTS = { + { mfr = "Yale", model = "YRD220/240 TSDB" }, + { mfr = "Yale", model = "YRL220 TS LL" }, + { mfr = "Yale", model = "YRD210 PB DB" }, + { mfr = "Yale", model = "YRL210 PB LL" }, + { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, + { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } +} +local is_bad_yale_lock_models = function(opts, driver, device) + for _, fingerprint in ipairs(BAD_YALE_LOCK_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end local battery_report_handler = function(driver, device, value) device:emit_event(capabilities.battery.battery(value.value)) @@ -20,7 +46,7 @@ local bad_yale_driver = { } } }, - can_handle = require("yale.yale-bad-battery-reporter.can_handle"), + can_handle = is_bad_yale_lock_models } return bad_yale_driver From ff4ea0d58ac1b76a8a637b858d7b013c161081d9 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:19 -0600 Subject: [PATCH 116/538] CHAD-17070: zigbee-lock lazy loading of subdrivers --- .../zigbee-lock/src/configurations.lua | 15 ++------ drivers/SmartThings/zigbee-lock/src/init.lua | 23 +++---------- .../zigbee-lock/src/lazy_load_subdriver.lua | 15 ++++++++ .../src/lock-without-codes/can_handle.lua | 14 ++++++++ .../src/lock-without-codes/fingerprints.lua | 9 +++++ .../src/lock-without-codes/init.lua | 30 +++------------- .../zigbee-lock/src/lock_utils.lua | 15 ++------ .../zigbee-lock/src/samsungsds/can_handle.lua | 11 ++++++ .../zigbee-lock/src/samsungsds/init.lua | 20 +++-------- .../zigbee-lock/src/sub_drivers.lua | 11 ++++++ .../zigbee-lock/src/test/test_c2o_lock.lua | 15 ++------ .../src/test/test_generic_lock_migration.lua | 17 ++-------- ..._yale_fingerprint_bad_battery_reporter.lua | 15 ++------ .../zigbee-lock/src/test/test_zigbee_lock.lua | 15 ++------ .../test/test_zigbee_lock_code_migration.lua | 15 ++------ .../test_zigbee_yale-bad-battery-reporter.lua | 15 ++------ .../test_zigbee_yale-fingerprint-lock.lua | 15 ++------ .../zigbee-lock/src/test/test_zigbee_yale.lua | 15 ++------ .../src/yale-fingerprint-lock/can_handle.lua | 14 ++++++++ .../yale-fingerprint-lock/fingerprints.lua | 11 ++++++ .../src/yale-fingerprint-lock/init.lua | 32 +++-------------- .../zigbee-lock/src/yale/can_handle.lua | 11 ++++++ .../SmartThings/zigbee-lock/src/yale/init.lua | 23 +++---------- .../zigbee-lock/src/yale/sub_drivers.lua | 8 +++++ .../yale-bad-battery-reporter/can_handle.lua | 14 ++++++++ .../fingerprints.lua | 13 +++++++ .../yale/yale-bad-battery-reporter/init.lua | 34 +++---------------- 27 files changed, 177 insertions(+), 268 deletions(-) create mode 100644 drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua diff --git a/drivers/SmartThings/zigbee-lock/src/configurations.lua b/drivers/SmartThings/zigbee-lock/src/configurations.lua index a2429252b0..88e4e59f80 100644 --- a/drivers/SmartThings/zigbee-lock/src/configurations.lua +++ b/drivers/SmartThings/zigbee-lock/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 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-lock/src/init.lua b/drivers/SmartThings/zigbee-lock/src/init.lua index ce6894b868..94f5adc0c4 100644 --- a/drivers/SmartThings/zigbee-lock/src/init.lua +++ b/drivers/SmartThings/zigbee-lock/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 + -- Zigbee Driver utilities local defaults = require "st.zigbee.defaults" @@ -445,12 +435,7 @@ local zigbee_lock_driver = { [capabilities.refresh.commands.refresh.NAME] = refresh } }, - sub_drivers = { - require("samsungsds"), - require("yale"), - require("yale-fingerprint-lock"), - require("lock-without-codes") - }, + sub_drivers = require("sub_drivers"), lifecycle_handlers = { doConfigure = do_configure, added = device_added, diff --git a/drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/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-lock/src/lock-without-codes/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua new file mode 100644 index 0000000000..543e43a8b1 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_lock_without_codes(opts, driver, device) + local FINGERPRINTS = require("lock-without-codes.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("lock-without-codes") + end + end + return false +end + +return can_handle_lock_without_codes diff --git a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua new file mode 100644 index 0000000000..63ae82b46c --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local LOCK_WITHOUT_CODES_FINGERPRINTS = { + { model = "E261-KR0B0Z0-HA" }, + { mfr = "Danalock", model = "V3-BTZB" } +} + +return LOCK_WITHOUT_CODES_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua index 7272991459..e5c6de3408 100644 --- a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/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 configurationMap = require "configurations" local clusters = require "st.zigbee.zcl.clusters" @@ -19,19 +9,7 @@ local capabilities = require "st.capabilities" local DoorLock = clusters.DoorLock local PowerConfiguration = clusters.PowerConfiguration -local LOCK_WITHOUT_CODES_FINGERPRINTS = { - { model = "E261-KR0B0Z0-HA" }, - { mfr = "Danalock", model = "V3-BTZB" } -} -local function can_handle_lock_without_codes(opts, driver, device) - for _, fingerprint in ipairs(LOCK_WITHOUT_CODES_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 configuration = configurationMap.get_device_configuration(device) @@ -95,7 +73,7 @@ local lock_without_codes = { } } }, - can_handle = can_handle_lock_without_codes + can_handle = require("lock-without-codes.can_handle"), } return lock_without_codes diff --git a/drivers/SmartThings/zigbee-lock/src/lock_utils.lua b/drivers/SmartThings/zigbee-lock/src/lock_utils.lua index 0a36a9685e..a02a59963c 100644 --- a/drivers/SmartThings/zigbee-lock/src/lock_utils.lua +++ b/drivers/SmartThings/zigbee-lock/src/lock_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 utils = require "st.utils" local capabilities = require "st.capabilities" local json = require "st.json" diff --git a/drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua new file mode 100644 index 0000000000..c483b2fe27 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/samsungsds/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function samsungsds_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "SAMSUNG SDS" then + return true, require("samsungsds") + end + return false +end + +return samsungsds_can_handle diff --git a/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua b/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua index b529dd3fd1..fff290df5d 100644 --- a/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/samsungsds/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 clusters = require "st.zigbee.zcl.clusters" @@ -112,9 +102,7 @@ local samsung_sds_driver = { added = device_added, init = device_init }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "SAMSUNG SDS" - end + can_handle = require("samsungsds.can_handle"), } return samsung_sds_driver diff --git a/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua b/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua new file mode 100644 index 0000000000..ff4bf8980d --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/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("samsungsds"), + lazy_load_if_possible("yale"), + lazy_load_if_possible("yale-fingerprint-lock"), + lazy_load_if_possible("lock-without-codes"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua index b6fa3d1323..146c628b8b 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.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 clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua index ed4ce6e3cc..f287300f60 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_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 2023 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" @@ -45,4 +34,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-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua index d499d7ff66..4f50c3c24a 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.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-lock/src/test/test_zigbee_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua index 80d10d092e..3ed037cd54 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.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-lock/src/test/test_zigbee_lock_code_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua index 1aa9432933..7950e3f62d 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.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-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua index ee1745e3b7..b8f4c386d9 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.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-lock/src/test/test_zigbee_yale-fingerprint-lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua index 2255c063a3..7cda71cdb3 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.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-lock/src/test/test_zigbee_yale.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua index 34b6881028..75ad49a1f5 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.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-lock/src/yale-fingerprint-lock/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua new file mode 100644 index 0000000000..a80632bf80 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local yale_fingerprint_lock_models = function(opts, driver, device) + local FINGERPRINTS = require("yale-fingerprint-lock.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("yale-fingerprint-lock") + end + end + return false +end + +return yale_fingerprint_lock_models diff --git a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua new file mode 100644 index 0000000000..b3db27d719 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local YALE_FINGERPRINT_LOCK = { + { mfr = "ASSA ABLOY iRevo", model = "iZBModule01" }, + { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, + { mfr = "ASSA ABLOY iRevo", model = "0700000001" }, + { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } +} + +return YALE_FINGERPRINT_LOCK diff --git a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua index 9d0a0b4148..b78d043784 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/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" @@ -19,21 +9,7 @@ local LockCodes = capabilities.lockCodes local YALE_FINGERPRINT_MAX_CODES = 0x1E -local YALE_FINGERPRINT_LOCK = { - { mfr = "ASSA ABLOY iRevo", model = "iZBModule01" }, - { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, - { mfr = "ASSA ABLOY iRevo", model = "0700000001" }, - { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } -} -local yale_fingerprint_lock_models = function(opts, driver, device) - for _, fingerprint in ipairs(YALE_FINGERPRINT_LOCK) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local handle_max_codes = function(driver, device, value) device:emit_event(LockCodes.maxCodes(YALE_FINGERPRINT_MAX_CODES), { visibility = { displayed = false } }) @@ -48,7 +24,7 @@ local yale_fingerprint_lock_driver = { } } }, - can_handle = yale_fingerprint_lock_models + can_handle = require("yale-fingerprint-lock.can_handle"), } return yale_fingerprint_lock_driver diff --git a/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua new file mode 100644 index 0000000000..54340c7811 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function yale_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "ASSA ABLOY iRevo" or device:get_manufacturer() == "Yale" then + return true, require("yale") + end + return false +end + +return yale_can_handle diff --git a/drivers/SmartThings/zigbee-lock/src/yale/init.lua b/drivers/SmartThings/zigbee-lock/src/yale/init.lua index 73e984036e..8ba98b2aa8 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale/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 + + -- Zigbee Spec Utils local clusters = require "st.zigbee.zcl.clusters" @@ -151,11 +142,7 @@ local yale_door_lock_driver = { [LockCodes.commands.setCode.NAME] = set_code } }, - - sub_drivers = { require("yale.yale-bad-battery-reporter") }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "ASSA ABLOY iRevo" or device:get_manufacturer() == "Yale" - end + sub_drivers = require("yale.sub_drivers"), } return yale_door_lock_driver diff --git a/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua b/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua new file mode 100644 index 0000000000..4b546979d3 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/yale/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("yale.yale-bad-battery-reporter"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua new file mode 100644 index 0000000000..67169e9268 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_bad_yale_lock_models = function(opts, driver, device) + local FINGERPRINTS = require("yale.yale-bad-battery-reporter.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("yale.yale-bad-battery-reporter") + end + end + return false +end + +return is_bad_yale_lock_models diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua new file mode 100644 index 0000000000..cbb7c3404f --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local BAD_YALE_LOCK_FINGERPRINTS = { + { mfr = "Yale", model = "YRD220/240 TSDB" }, + { mfr = "Yale", model = "YRL220 TS LL" }, + { mfr = "Yale", model = "YRD210 PB DB" }, + { mfr = "Yale", model = "YRL210 PB LL" }, + { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, + { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } +} + +return BAD_YALE_LOCK_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua index 59fdbf228b..3b77f32563 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua @@ -1,37 +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 clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" -local BAD_YALE_LOCK_FINGERPRINTS = { - { mfr = "Yale", model = "YRD220/240 TSDB" }, - { mfr = "Yale", model = "YRL220 TS LL" }, - { mfr = "Yale", model = "YRD210 PB DB" }, - { mfr = "Yale", model = "YRL210 PB LL" }, - { mfr = "ASSA ABLOY iRevo", model = "c700000202" }, - { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } -} -local is_bad_yale_lock_models = function(opts, driver, device) - for _, fingerprint in ipairs(BAD_YALE_LOCK_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local battery_report_handler = function(driver, device, value) device:emit_event(capabilities.battery.battery(value.value)) @@ -46,7 +20,7 @@ local bad_yale_driver = { } } }, - can_handle = is_bad_yale_lock_models + can_handle = require("yale.yale-bad-battery-reporter.can_handle"), } return bad_yale_driver From ef1fcda6853817bd73db139dee4dc0a2fd73cb72 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 18 Feb 2026 15:18:51 -0600 Subject: [PATCH 117/538] hotfix/CHAD-17070: Added test and reverting of dropped `can_handle` in yale lock --- .../src/test/test_zigbee_lock_v10.lua | 778 ++++++++++++++++++ .../SmartThings/zigbee-lock/src/yale/init.lua | 1 + 2 files changed, 779 insertions(+) create mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_v10.lua diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_v10.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_v10.lua new file mode 100644 index 0000000000..c4e28dcd0d --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_v10.lua @@ -0,0 +1,778 @@ +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Mock out globals +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 PowerConfiguration = clusters.PowerConfiguration +local DoorLock = clusters.DoorLock +local Alarm = clusters.Alarms +local capabilities = require "st.capabilities" +-- Note: This is not the proper way to test against previous versions. +-- Instead, testing should be run against different lua lib artifacts +local version = require "version" +version.api = 10 + +local DoorLockState = DoorLock.attributes.LockState +local OperationEventCode = DoorLock.types.OperationEventCode +local DoorLockUserStatus = DoorLock.types.DrlkUserStatus +local DoorLockUserType = DoorLock.types.DrlkUserType +local ProgrammingEventCode = DoorLock.types.ProgramEventCode + +local json = require "dkjson" + +local mock_device = test.mock_device.build_test_zigbee_device( + { profile = t_utils.get_profile_definition("base-lock.yml") } +) +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) + +local expect_reload_all_codes_messages = function() + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.SendPINOverTheAir:write(mock_device, + true) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.MaxPINCodeLength:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.MinPINCodeLength:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.scanCodes("Scanning", { visibility = { displayed = false } }))) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 0) }) +end + +test.register_coroutine_test( + "Configure should configure all necessary attributes and begin reading codes", + function() + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.wait_for_events() + + 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, + PowerConfiguration.ID) }) + test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:configure_reporting(mock_device, + 600, + 21600, + 1) }) + test.socket.zigbee:__expect_send({ mock_device.id, zigbee_test_utils.build_bind_request(mock_device, + zigbee_test_utils.mock_hub_eui, + DoorLock.ID) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:configure_reporting(mock_device, + 0, + 3600, + 0) }) + test.socket.zigbee:__expect_send({ mock_device.id, zigbee_test_utils.build_bind_request(mock_device, + zigbee_test_utils.mock_hub_eui, + Alarm.ID) }) + test.socket.zigbee:__expect_send({ mock_device.id, Alarm.attributes.AlarmCount:configure_reporting(mock_device, + 0, + 21600, + 0) }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + + test.mock_time.advance_time(2) + expect_reload_all_codes_messages() + + end +) + +test.register_coroutine_test( + "Refresh should read expected attributes", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} }}) + + test.socket.zigbee:__expect_send({mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device)}) + test.socket.zigbee:__expect_send({mock_device.id, DoorLock.attributes.LockState:read(mock_device)}) + test.socket.zigbee:__expect_send({mock_device.id, Alarm.attributes.AlarmCount:read(mock_device)}) + end +) + +test.register_message_test( + "Lock status reporting should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, DoorLock.attributes.LockState:build_test_attr_report(mock_device, + DoorLockState.LOCKED) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lock.lock.locked()) + } + } +) + +test.register_message_test( + "Battery percentage report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device, + 55) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) + } + } +) + +test.register_message_test( + "Lock operation event reporting should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, + DoorLock.client.commands.OperatingEventNotification.build_test_rx( + mock_device, + 0x02, + OperationEventCode.LOCK, + 0x0000, + "", + 0x0000, + "") } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "manual" } })) + } + } +) + +test.register_message_test( + "Pin response reporting should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 0x02, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("2 set", + { data = { codeName = "Code 2" }, state_change = true })) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["2"] = "Code 2"} ), { visibility = { displayed = false } })) + } + } +) + +test.register_message_test( + "Sending the lock command should be handled", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "lock", component = "main", command = "lock", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, DoorLock.server.commands.LockDoor(mock_device) } + } + } +) + +test.register_message_test( + "Min lock code length report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, DoorLock.attributes.MinPINCodeLength:build_test_attr_report(mock_device, 4) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lockCodes.minCodeLength(4, { visibility = { displayed = false }})) + } + } +) + +test.register_message_test( + "Max lock code length report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, DoorLock.attributes.MaxPINCodeLength:build_test_attr_report(mock_device, 4) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lockCodes.maxCodeLength(4, { visibility = { displayed = false }})) + } + } +) + +test.register_message_test( + "Max user code number report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device, + 16) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lockCodes.maxCodes(16, { visibility = { displayed = false }})) + } + } +) + +test.register_coroutine_test( + "Reloading all codes of an unconfigured lock should generate correct attribute checks", + function() + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "reloadAllCodes", args = {} } }) + expect_reload_all_codes_messages() + end +) + +test.register_message_test( + "Requesting a user code should be handled", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = capabilities.lockCodes.ID, command = "requestCode", args = { 1 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 1) } + } + } +) + +test.register_coroutine_test( + "Deleting a user code should be handled", + function() + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ mock_device.id, DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 0x01, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 set", + { data = { codeName = "Code 1" }, state_change = true }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode( {["1"] = "Code 1"} ), { visibility = { displayed = false }}) + )) + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "deleteCode", args = { 1 } } }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.SendPINOverTheAir:write(mock_device, + true) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearPINCode(mock_device, 1) }) + test.wait_for_events() + + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 0x01, + DoorLockUserType.UNRESTRICTED, + DoorLockUserStatus.AVAILABLE, + "")}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 deleted", + { data = { codeName = "Code 1"}, state_change = true }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({} ), { visibility = { displayed = false } }) + )) + end +) + +test.register_coroutine_test( + "Setting a user code should result in the named code changed event firing", + function() + test.timer.__create_and_queue_test_time_advance_timer(4, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "setCode", args = { 1, "1234", "test" } } }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) + } + ) + test.wait_for_events() + + test.mock_time.advance_time(4) + test.socket.zigbee:__expect_send( + { + mock_device.id, + DoorLock.server.commands.GetPINCode(mock_device, 1) + } + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 0x01, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 set", { data = { codeName = "test" }, state_change = true }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "test"}), { visibility = { displayed = false } }))) + end +) + +local function init_code_slot(slot_number, name, device) + test.timer.__create_and_queue_test_time_advance_timer(4, "oneshot") + test.socket.capability:__queue_receive({ device.id, { capability = capabilities.lockCodes.ID, command = "setCode", args = { slot_number, "1234", name } } }) + test.socket.zigbee:__expect_send( + { + device.id, + DoorLock.server.commands.SetPINCode(device, + slot_number, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) + } + ) + test.wait_for_events() + test.mock_time.advance_time(4) + test.socket.zigbee:__expect_send( + { + device.id, + DoorLock.server.commands.GetPINCode(device, slot_number) + } + ) + test.wait_for_events() + test.socket.zigbee:__queue_receive( + { + device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + device, + slot_number, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) + } + ) + test.socket.capability:__expect_send(device:generate_test_message("main", + capabilities.lockCodes.codeChanged(slot_number .. " set", { data = { codeName = name }, state_change = true })) + ) +end + +test.register_coroutine_test( + "Setting a user code name should be handled", + function() + init_code_slot(1, "initialName", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "initialName"}), { visibility = { displayed = false } }))) + test.wait_for_events() + + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "nameSlot", args = { 1, "foo" } } }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 renamed", {state_change = true}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false } }))) + end +) + +test.register_coroutine_test( + "Setting a user code name via setCode should be handled", + function() + init_code_slot(1, "initialName", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "initialName"}), { visibility = { displayed = false } }))) + test.wait_for_events() + + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "setCode", args = { 1, "", "foo"} } }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 renamed", {state_change = true}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false } }))) + end +) + +test.register_coroutine_test( + "Calling updateCodes should send properly spaced commands", + function () + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "updateCodes", args = {{code1 = "1234", code2 = "2345", code3 = "3456", code4 = ""}}}}) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) + }) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 2, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "2345" + ) + }) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 3, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "3456" + ) + }) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ + mock_device.id, DoorLock.server.commands.ClearPINCode(mock_device, 4) + }) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.GetPINCode(mock_device, 1) + }) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.GetPINCode(mock_device, 2) + }) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.GetPINCode(mock_device, 3) + }) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.GetPINCode(mock_device, 4) + }) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Setting all user codes should result in a code set event for each", + function () + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "updateCodes", args = {{code1 = "1234", code2 = "2345", code3 = "3456", code4 = ""}}}}) + test.socket.zigbee:__expect_send({mock_device.id, DoorLock.server.commands.SetPINCode(mock_device, 1, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "1234")}) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 1) }) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({mock_device.id, DoorLock.server.commands.SetPINCode(mock_device, 2, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "2345")}) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 2) }) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({mock_device.id, DoorLock.server.commands.SetPINCode(mock_device, 3, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "3456")}) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 3) }) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearPINCode(mock_device, 4) }) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 4) }) + test.wait_for_events() + end +) + +test.register_message_test( + "Master code programming event should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_device.id, + DoorLock.client.commands.ProgrammingEventNotification.build_test_rx( + mock_device, + 0x00, + ProgrammingEventCode.MASTER_CODE_CHANGED, + 0, + "1234", + DoorLockUserType.MASTER_USER, + DoorLockUserStatus.OCCUPIED_ENABLED, + 0x0000, + "data" + ) + } + }, + + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("0 set", { data = { codeName = "Master Code"}, state_change = true }) + ) + } + } +) + +test.register_message_test( + "The lock reporting a single code has been set should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_device.id, + DoorLock.client.commands.ProgrammingEventNotification.build_test_rx( + mock_device, + 0x0, + ProgrammingEventCode.PIN_CODE_ADDED, + 1, + "1234", + DoorLockUserType.UNRESTRICTED, + DoorLockUserStatus.OCCUPIED_ENABLED, + 0x0000, + "data" + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 set", { data = { codeName = "Code 1"}, state_change = true })) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } })) + } + } +) + +test.register_coroutine_test( + "The lock reporting a code has been deleted should be handled", + function() + init_code_slot(1, "Code 1", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } }))) + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.ProgrammingEventNotification.build_test_rx( + mock_device, + 0x0, + ProgrammingEventCode.PIN_CODE_DELETED, + 1, + "1234", + DoorLockUserType.UNRESTRICTED, + DoorLockUserStatus.AVAILABLE, + 0x0000, + "data" + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 deleted", { data = { codeName = "Code 1"}, state_change = true }) + ) + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({}), { visibility = { displayed = false } }))) + end +) + +test.register_coroutine_test( + "The lock reporting that all codes have been deleted should be handled", + function() + init_code_slot(1, "Code 1", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } }))) + init_code_slot(2, "Code 2", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1", ["2"] = "Code 2"}), { visibility = { displayed = false } }))) + init_code_slot(3, "Code 3", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1", ["2"] = "Code 2", ["3"] = "Code 3"}), { visibility = { displayed = false } }))) + + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.ProgrammingEventNotification.build_test_rx( + mock_device, + 0x0, + ProgrammingEventCode.PIN_CODE_DELETED, + 0xFF, + "1234", + DoorLockUserType.UNRESTRICTED, + DoorLockUserStatus.AVAILABLE, + 0x0000, + "data" + ) + } + ) + + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 deleted", { data = { codeName = "Code 1"}, state_change = true }) + ) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("2 deleted", { data = { codeName = "Code 2"}, state_change = true }) + ) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("3 deleted", { data = { codeName = "Code 3"}, state_change = true }) + ) + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({}), { visibility = { displayed = false } }))) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "The lock reporting unlock via code should include the code info in the report", + function() + init_code_slot(1, "Code 1", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } }))) + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.OperatingEventNotification.build_test_rx( + mock_device, + 0x00, -- 0 = keypad + OperationEventCode.UNLOCK, + 0x0001, + "1234", + 0x0000, + "" + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.unlocked({ data = { method = "keypad", codeId = "1", codeName = "Code 1" } }) + ) + ) + end +) + +test.register_coroutine_test( + "Lock state attribute reports (after the first) should be delayed if they come before event notifications ", + function() + init_code_slot(1, "Code 1", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } }))) + test.socket.zigbee:__queue_receive({mock_device.id, DoorLock.attributes.LockState:build_test_attr_report(mock_device, DoorLockState.UNLOCKED)}) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.unlocked() + ) + ) + test.mock_time.advance_time(2) + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.OperatingEventNotification.build_test_rx( + mock_device, + 0x00, -- 0 = keypad + OperationEventCode.UNLOCK, + 0x0001, + "1234", + 0x0000, + "" + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.unlocked({ data = { method = "keypad", codeId = "1", codeName = "Code 1" } }) + ) + ) + test.mock_time.advance_time(2) + test.timer.__create_and_queue_test_time_advance_timer(2.5, "oneshot") + test.socket.zigbee:__queue_receive({mock_device.id, DoorLock.attributes.LockState:build_test_attr_report(mock_device, DoorLockState.LOCKED)}) + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.OperatingEventNotification.build_test_rx( + mock_device, + 0x00, -- 0 = keypad + OperationEventCode.LOCK, + 0x0001, + "1234", + 0x0000, + "" + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.locked({ data = { method = "keypad", codeId = "1", codeName = "Code 1" } }) + ) + ) + test.mock_time.advance_time(2.5) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.locked() + ) + ) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/yale/init.lua b/drivers/SmartThings/zigbee-lock/src/yale/init.lua index 8ba98b2aa8..c315fbfa06 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale/init.lua @@ -143,6 +143,7 @@ local yale_door_lock_driver = { } }, sub_drivers = require("yale.sub_drivers"), + can_handle = require("yale.can_handle"), } return yale_door_lock_driver From 2c1799f31c66028286bf6e192a6308154b3acbc5 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Fri, 13 Feb 2026 16:40:38 -0600 Subject: [PATCH 118/538] update ikea scroll profile category to Button --- drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml index cce4b68d2d..e07006ce9d 100644 --- a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml +++ b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml @@ -14,7 +14,7 @@ components: - id: refresh version: 1 categories: - - name: RemoteController + - name: Button - id: group2 label: Group 2 capabilities: @@ -23,7 +23,7 @@ components: - id: knob version: 1 categories: - - name: RemoteController + - name: Button - id: group3 label: Group 3 capabilities: @@ -32,4 +32,4 @@ components: - id: knob version: 1 categories: - - name: RemoteController + - name: Button From 024c47140f6a857886597a509efdc47dab4650ef Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 19 Feb 2026 13:59:55 -0800 Subject: [PATCH 119/538] WWSTCERT-10417 ubisys dimmer D1-R --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 1c26d5d6c8..6bb32c5978 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -615,6 +615,11 @@ zigbeeManufacturer: manufacturer: ubisys model: "1151" deviceProfileName: switch-power + - id: "ubisys/D1-R (5603)" + deviceLabel: Dimmer D1-R + manufacturer: ubisys + model: "D1-R (5603)" + deviceProfileName: switch-level-power - id: "Megaman/AD-DimmableLight3001" deviceLabel: INGENIUM Light manufacturer: Megaman From 58de43e1941183e03db917df39c6dbf78107d208 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:09 -0600 Subject: [PATCH 120/538] CHAD-17079: zigbee-valve lazy load sub-drivers --- .../zigbee-valve/src/ezex/can_handle.lua | 12 +++++++++++ .../zigbee-valve/src/ezex/init.lua | 20 ++++-------------- drivers/SmartThings/zigbee-valve/src/init.lua | 21 ++++--------------- .../zigbee-valve/src/lazy_load_subdriver.lua | 15 +++++++++++++ .../zigbee-valve/src/sinope/can_handle.lua | 11 ++++++++++ .../zigbee-valve/src/sinope/init.lua | 20 ++++-------------- .../zigbee-valve/src/sub_drivers.lua | 9 ++++++++ .../zigbee-valve/src/test/test_ezex_valve.lua | 16 +++----------- .../src/test/test_sinope_valve.lua | 16 +++----------- .../src/test/test_zigbee_valve.lua | 16 +++----------- 10 files changed, 68 insertions(+), 88 deletions(-) create mode 100644 drivers/SmartThings/zigbee-valve/src/ezex/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-valve/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-valve/src/sinope/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-valve/src/sub_drivers.lua diff --git a/drivers/SmartThings/zigbee-valve/src/ezex/can_handle.lua b/drivers/SmartThings/zigbee-valve/src/ezex/can_handle.lua new file mode 100644 index 0000000000..f16f04858c --- /dev/null +++ b/drivers/SmartThings/zigbee-valve/src/ezex/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function ezex_can_handle(opts, driver, device, ...) + local clusters = require "st.zigbee.zcl.clusters" + if device:get_model() == "E253-KR0B0ZX-HA" and not device:supports_server_cluster(clusters.PowerConfiguration.ID) then + return true, require("ezex") + end + return false +end + +return ezex_can_handle diff --git a/drivers/SmartThings/zigbee-valve/src/ezex/init.lua b/drivers/SmartThings/zigbee-valve/src/ezex/init.lua index 47ced66806..c32fe60bac 100644 --- a/drivers/SmartThings/zigbee-valve/src/ezex/init.lua +++ b/drivers/SmartThings/zigbee-valve/src/ezex/init.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 + local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" @@ -71,9 +61,7 @@ local ezex_valve = { lifecycle_handlers = { init = device_init }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "E253-KR0B0ZX-HA" and not device:supports_server_cluster(clusters.PowerConfiguration.ID) - end + can_handle = require("ezex.can_handle"), } return ezex_valve diff --git a/drivers/SmartThings/zigbee-valve/src/init.lua b/drivers/SmartThings/zigbee-valve/src/init.lua index 6d355de8ec..1840b55be0 100644 --- a/drivers/SmartThings/zigbee-valve/src/init.lua +++ b/drivers/SmartThings/zigbee-valve/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" @@ -51,10 +41,7 @@ local zigbee_valve_driver_template = { lifecycle_handlers = { added = device_added }, - sub_drivers = { - require("sinope"), - require("ezex") - }, + sub_drivers = require("sub_drivers"), health_check = false, } diff --git a/drivers/SmartThings/zigbee-valve/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-valve/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-valve/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-valve/src/sinope/can_handle.lua b/drivers/SmartThings/zigbee-valve/src/sinope/can_handle.lua new file mode 100644 index 0000000000..f533d120e0 --- /dev/null +++ b/drivers/SmartThings/zigbee-valve/src/sinope/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function sinope_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Sinope Technologies" then + return true, require("sinope") + end + return false +end + +return sinope_can_handle diff --git a/drivers/SmartThings/zigbee-valve/src/sinope/init.lua b/drivers/SmartThings/zigbee-valve/src/sinope/init.lua index 6a0075cd33..ab3786511c 100644 --- a/drivers/SmartThings/zigbee-valve/src/sinope/init.lua +++ b/drivers/SmartThings/zigbee-valve/src/sinope/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 battery_defaults = require "st.zigbee.defaults.battery_defaults" @@ -59,9 +49,7 @@ local sinope_valve = { lifecycle_handlers = { init = device_init }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Sinope Technologies" - end + can_handle = require("sinope.can_handle"), } return sinope_valve diff --git a/drivers/SmartThings/zigbee-valve/src/sub_drivers.lua b/drivers/SmartThings/zigbee-valve/src/sub_drivers.lua new file mode 100644 index 0000000000..258579c7fb --- /dev/null +++ b/drivers/SmartThings/zigbee-valve/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("sinope"), + lazy_load_if_possible("ezex"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua index eb96363d99..e925491c87 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.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 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua index 344c6b0814..8710cef0c9 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.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/zigbee-valve/src/test/test_zigbee_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_zigbee_valve.lua index baa4252bd2..5b4756f207 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_zigbee_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_zigbee_valve.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" From fafa569efc940832ddd63540c8f6d0e48f9e62a5 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:16 -0600 Subject: [PATCH 121/538] CHAD-17089: zwave-lock lazy loading of subdrivers --- .../src/apiv6_bugfix/can_handle.lua | 16 +++++++++++++ .../zwave-lock/src/apiv6_bugfix/init.lua | 13 ++++------- drivers/SmartThings/zwave-lock/src/init.lua | 23 +++---------------- .../zwave-lock/src/keywe-lock/can_handle.lua | 12 ++++++++++ .../zwave-lock/src/keywe-lock/init.lua | 23 ++++--------------- .../zwave-lock/src/lazy_load_subdriver.lua | 18 +++++++++++++++ .../src/samsung-lock/can_handle.lua | 12 ++++++++++ .../zwave-lock/src/samsung-lock/init.lua | 23 ++++--------------- .../src/schlage-lock/can_handle.lua | 12 ++++++++++ .../zwave-lock/src/schlage-lock/init.lua | 23 ++++--------------- .../zwave-lock/src/sub_drivers.lua | 12 ++++++++++ .../zwave-lock/src/test/test_keywe_lock.lua | 16 +++---------- .../zwave-lock/src/test/test_lock_battery.lua | 16 +++---------- .../zwave-lock/src/test/test_samsung_lock.lua | 16 +++---------- .../zwave-lock/src/test/test_schlage_lock.lua | 16 +++---------- .../zwave-lock/src/test/test_zwave_lock.lua | 16 +++---------- .../test/test_zwave_lock_code_migration.lua | 16 +++---------- .../src/zwave-alarm-v1-lock/can_handle.lua | 11 +++++++++ .../src/zwave-alarm-v1-lock/init.lua | 21 ++++------------- 19 files changed, 134 insertions(+), 181 deletions(-) create mode 100644 drivers/SmartThings/zwave-lock/src/apiv6_bugfix/can_handle.lua create mode 100644 drivers/SmartThings/zwave-lock/src/keywe-lock/can_handle.lua create mode 100644 drivers/SmartThings/zwave-lock/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-lock/src/samsung-lock/can_handle.lua create mode 100644 drivers/SmartThings/zwave-lock/src/schlage-lock/can_handle.lua create mode 100644 drivers/SmartThings/zwave-lock/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/can_handle.lua diff --git a/drivers/SmartThings/zwave-lock/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-lock/src/apiv6_bugfix/can_handle.lua new file mode 100644 index 0000000000..8a9b8cc6cc --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/apiv6_bugfix/can_handle.lua @@ -0,0 +1,16 @@ +-- 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-lock/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-lock/src/apiv6_bugfix/init.lua index 0204b7b2d5..94dc5975ab 100644 --- a/drivers/SmartThings/zwave-lock/src/apiv6_bugfix/init.lua +++ b/drivers/SmartThings/zwave-lock/src/apiv6_bugfix/init.lua @@ -1,14 +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 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() end @@ -20,7 +15,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-lock/src/init.lua b/drivers/SmartThings/zwave-lock/src/init.lua index b83b196256..925452c431 100644 --- a/drivers/SmartThings/zwave-lock/src/init.lua +++ b/drivers/SmartThings/zwave-lock/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 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -182,13 +171,7 @@ local driver_template = { [Time.GET] = time_get_handler -- used by DanaLock } }, - sub_drivers = { - require("zwave-alarm-v1-lock"), - require("schlage-lock"), - require("samsung-lock"), - require("keywe-lock"), - require("apiv6_bugfix"), - } + sub_drivers = require("sub_drivers"), } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zwave-lock/src/keywe-lock/can_handle.lua b/drivers/SmartThings/zwave-lock/src/keywe-lock/can_handle.lua new file mode 100644 index 0000000000..d8bcd5756e --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/keywe-lock/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_keywe_lock(opts, self, device, cmd, ...) + local KEYWE_MFR = 0x037B + if device.zwave_manufacturer_id == KEYWE_MFR then + return true, require("keywe-lock") + end + return false +end + +return can_handle_keywe_lock diff --git a/drivers/SmartThings/zwave-lock/src/keywe-lock/init.lua b/drivers/SmartThings/zwave-lock/src/keywe-lock/init.lua index d39aa45d1c..a51af26e00 100644 --- a/drivers/SmartThings/zwave-lock/src/keywe-lock/init.lua +++ b/drivers/SmartThings/zwave-lock/src/keywe-lock/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" @@ -23,13 +13,8 @@ local LockDefaults = require "st.zwave.defaults.lock" local LockCodesDefaults = require "st.zwave.defaults.lockCodes" local TamperDefaults = require "st.zwave.defaults.tamperAlert" -local KEYWE_MFR = 0x037B local TAMPER_CLEAR_DELAY = 10 -local function can_handle_keywe_lock(opts, self, device, cmd, ...) - return device.zwave_manufacturer_id == KEYWE_MFR -end - local function clear_tamper_if_needed(device) local current_tamper_state = device:get_latest_state("main", capabilities.tamperAlert.ID, capabilities.tamperAlert.tamper.NAME) if current_tamper_state == "detected" then @@ -80,7 +65,7 @@ local keywe_lock = { doConfigure = do_configure }, NAME = "Keywe Lock", - can_handle = can_handle_keywe_lock, + can_handle = require("keywe-lock.can_handle"), } return keywe_lock diff --git a/drivers/SmartThings/zwave-lock/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-lock/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/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-lock/src/samsung-lock/can_handle.lua b/drivers/SmartThings/zwave-lock/src/samsung-lock/can_handle.lua new file mode 100644 index 0000000000..e9222cb8fb --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/samsung-lock/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_samsung_lock(opts, self, device, cmd, ...) + local SAMSUNG_MFR = 0x022E + if device.zwave_manufacturer_id == SAMSUNG_MFR then + return true, require("samsung-lock") + end + return false +end + +return can_handle_samsung_lock diff --git a/drivers/SmartThings/zwave-lock/src/samsung-lock/init.lua b/drivers/SmartThings/zwave-lock/src/samsung-lock/init.lua index 813c6217b4..b2f4f60975 100644 --- a/drivers/SmartThings/zwave-lock/src/samsung-lock/init.lua +++ b/drivers/SmartThings/zwave-lock/src/samsung-lock/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" @@ -28,11 +18,6 @@ local get_lock_codes = LockCodesDefaults.get_lock_codes local clear_code_state = LockCodesDefaults.clear_code_state local code_deleted = LockCodesDefaults.code_deleted -local SAMSUNG_MFR = 0x022E - -local function can_handle_samsung_lock(opts, self, device, cmd, ...) - return device.zwave_manufacturer_id == SAMSUNG_MFR -end local function get_ongoing_code_set(device) local code_id @@ -105,7 +90,7 @@ local samsung_lock = { doConfigure = do_configure }, NAME = "Samsung Lock", - can_handle = can_handle_samsung_lock, + can_handle = require("samsung-lock.can_handle"), } return samsung_lock diff --git a/drivers/SmartThings/zwave-lock/src/schlage-lock/can_handle.lua b/drivers/SmartThings/zwave-lock/src/schlage-lock/can_handle.lua new file mode 100644 index 0000000000..e9f3cfb84c --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/schlage-lock/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_schlage_lock(opts, self, device, cmd, ...) + local SCHLAGE_MFR = 0x003B + if device.zwave_manufacturer_id == SCHLAGE_MFR then + return true, require("schlage-lock") + end + return false +end + +return can_handle_schlage_lock diff --git a/drivers/SmartThings/zwave-lock/src/schlage-lock/init.lua b/drivers/SmartThings/zwave-lock/src/schlage-lock/init.lua index 67e649d869..6b22049beb 100644 --- a/drivers/SmartThings/zwave-lock/src/schlage-lock/init.lua +++ b/drivers/SmartThings/zwave-lock/src/schlage-lock/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" @@ -27,15 +17,10 @@ local Association = (require "st.zwave.CommandClass.Association")({version=1}) local LockCodesDefaults = require "st.zwave.defaults.lockCodes" -local SCHLAGE_MFR = 0x003B local SCHLAGE_LOCK_CODE_LENGTH_PARAM = {number = 16, size = 1} local DEFAULT_COMMANDS_DELAY = 4.2 -- seconds -local function can_handle_schlage_lock(opts, self, device, cmd, ...) - return device.zwave_manufacturer_id == SCHLAGE_MFR -end - local function set_code_length(self, device, cmd) local length = cmd.args.length if length >= 4 and length <= 8 then @@ -187,7 +172,7 @@ local schlage_lock = { doConfigure = do_configure, }, NAME = "Schlage Lock", - can_handle = can_handle_schlage_lock, + can_handle = require("schlage-lock.can_handle"), } return schlage_lock diff --git a/drivers/SmartThings/zwave-lock/src/sub_drivers.lua b/drivers/SmartThings/zwave-lock/src/sub_drivers.lua new file mode 100644 index 0000000000..46700ce154 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/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("zwave-alarm-v1-lock"), + lazy_load_if_possible("schlage-lock"), + lazy_load_if_possible("samsung-lock"), + lazy_load_if_possible("keywe-lock"), + lazy_load_if_possible("apiv6_bugfix"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua index d8e8ecc1bc..0266391d85 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.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-lock/src/test/test_lock_battery.lua b/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua index 9aac02c6b2..7667842e61 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.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-lock/src/test/test_samsung_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua index 7707b8a850..9dc1e38bcf 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.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-lock/src/test/test_schlage_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua index b1a5964502..189184f19e 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.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-lock/src/test/test_zwave_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock.lua index 52144295b3..b105707c7d 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock.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-lock/src/test/test_zwave_lock_code_migration.lua b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_code_migration.lua index e4a9b50758..a6b0b5a2a3 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_code_migration.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_code_migration.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/zwave-lock/src/zwave-alarm-v1-lock/can_handle.lua b/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/can_handle.lua new file mode 100644 index 0000000000..7bb54f23f2 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_v1_alarm(opts, driver, device, cmd, ...) + if opts.dispatcher_class == "ZwaveDispatcher" and cmd ~= nil and cmd.version ~= nil and cmd.version == 1 then + return true, require("zwave-alarm-v1-lock") + end + return false +end + +return can_handle_v1_alarm diff --git a/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/init.lua b/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/init.lua index 44d978999b..d7c862f22a 100644 --- a/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/init.lua +++ b/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/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 @@ -35,9 +25,6 @@ local METHOD = { --- @param driver st.zwave.Driver --- @param device st.zwave.Device --- @return boolean true if the device is smoke co alarm -local function can_handle_v1_alarm(opts, driver, device, cmd, ...) - return opts.dispatcher_class == "ZwaveDispatcher" and cmd ~= nil and cmd.version ~= nil and cmd.version == 1 -end --- Default handler for alarm command class reports, these were largely OEM-defined --- @@ -159,7 +146,7 @@ local zwave_lock = { } }, NAME = "Z-Wave lock alarm V1", - can_handle = can_handle_v1_alarm, + can_handle = require("zwave-alarm-v1-lock.can_handle"), } return zwave_lock From 91b1c7139465424525288612f50a720c4aaf98a6 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:37 -0600 Subject: [PATCH 122/538] CHAD-17094: zwave-smoke-alarm lazy loading of sub-drivers --- .../src/apiv6_bugfix/can_handle.lua | 16 +++++++++ .../src/apiv6_bugfix/init.lua | 13 +++---- .../src/fibaro-smoke-sensor/can_handle.lua | 14 ++++++++ .../src/fibaro-smoke-sensor/fingerprints.lua | 11 ++++++ .../src/fibaro-smoke-sensor/init.lua | 32 +++-------------- .../zwave-smoke-alarm/src/init.lua | 23 +++--------- .../src/lazy_load_subdriver.lua | 18 ++++++++++ .../zwave-smoke-alarm/src/preferences.lua | 16 ++------- .../zwave-smoke-alarm/src/sub_drivers.lua | 11 ++++++ .../src/test/test_fibaro_co_sensor_zw5.lua | 16 ++------- .../src/test/test_fibaro_smoke_sensor.lua | 16 ++------- .../src/test/test_zwave_alarm_v1.lua | 16 ++------- .../src/test/test_zwave_co_detector.lua | 16 ++------- .../src/test/test_zwave_smoke_detector.lua | 16 ++------- .../zwave-smoke-co-alarm-v1/can_handle.lua | 13 +++++++ .../src/zwave-smoke-co-alarm-v1/init.lua | 28 ++------------- .../zwave-smoke-co-alarm-v2/can_handle.lua | 14 ++++++++ .../fibaro-co-sensor-zw5/can_handle.lua | 14 ++++++++ .../fibaro-co-sensor-zw5/fingerprints.lua | 9 +++++ .../fibaro-co-sensor-zw5/init.lua | 30 +++------------- .../zwave-smoke-co-alarm-v2/fingerprints.lua | 9 +++++ .../src/zwave-smoke-co-alarm-v2/init.lua | 35 ++++--------------- .../zwave-smoke-co-alarm-v2/sub_drivers.lua | 8 +++++ 23 files changed, 180 insertions(+), 214 deletions(-) create mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/can_handle.lua create mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/fibaro-smoke-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/fibaro-smoke-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v1/can_handle.lua create mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/can_handle.lua create mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5/can_handle.lua create mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/sub_drivers.lua diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/can_handle.lua new file mode 100644 index 0000000000..8a9b8cc6cc --- /dev/null +++ b/drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/can_handle.lua @@ -0,0 +1,16 @@ +-- 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-smoke-alarm/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/init.lua index 0204b7b2d5..94dc5975ab 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/init.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/init.lua @@ -1,14 +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 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() end @@ -20,7 +15,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-smoke-alarm/src/fibaro-smoke-sensor/can_handle.lua b/drivers/SmartThings/zwave-smoke-alarm/src/fibaro-smoke-sensor/can_handle.lua new file mode 100644 index 0000000000..4a3f51183b --- /dev/null +++ b/drivers/SmartThings/zwave-smoke-alarm/src/fibaro-smoke-sensor/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_smoke_sensor(opts, driver, device, cmd, ...) + local FINGERPRINTS = require("fibaro-smoke-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("fibaro-smoke-sensor") + end + end + return false +end + +return can_handle_fibaro_smoke_sensor diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/fibaro-smoke-sensor/fingerprints.lua b/drivers/SmartThings/zwave-smoke-alarm/src/fibaro-smoke-sensor/fingerprints.lua new file mode 100644 index 0000000000..81c04eccfc --- /dev/null +++ b/drivers/SmartThings/zwave-smoke-alarm/src/fibaro-smoke-sensor/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FIBARO_SMOKE_SENSOR_FINGERPRINTS = { + { manufacturerId = 0x010F, productType = 0x0C02, productId = 0x1002 }, -- Fibaro Smoke Sensor + { manufacturerId = 0x010F, productType = 0x0C02, productId = 0x1003 }, -- Fibaro Smoke Sensor + { manufacturerId = 0x010F, productType = 0x0C02, productId = 0x3002 }, -- Fibaro Smoke Sensor + { manufacturerId = 0x010F, productType = 0x0C02, productId = 0x4002 } -- Fibaro Smoke Sensor +} + +return FIBARO_SMOKE_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/fibaro-smoke-sensor/init.lua b/drivers/SmartThings/zwave-smoke-alarm/src/fibaro-smoke-sensor/init.lua index a4d62fa1a4..bf7d554613 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/fibaro-smoke-sensor/init.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/fibaro-smoke-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 @@ -24,26 +14,12 @@ local WakeUp = (require "st.zwave.CommandClass.WakeUp")({version=1}) local FIBARO_SMOKE_SENSOR_WAKEUP_INTERVAL = 21600 --seconds -local FIBARO_SMOKE_SENSOR_FINGERPRINTS = { - { manufacturerId = 0x010F, productType = 0x0C02, productId = 0x1002 }, -- Fibaro Smoke Sensor - { manufacturerId = 0x010F, productType = 0x0C02, productId = 0x1003 }, -- Fibaro Smoke Sensor - { manufacturerId = 0x010F, productType = 0x0C02, productId = 0x3002 }, -- Fibaro Smoke Sensor - { manufacturerId = 0x010F, productType = 0x0C02, productId = 0x4002 } -- Fibaro Smoke Sensor -} --- Determine whether the passed device is fibaro smoke sensro --- --- @param driver st.zwave.Driver --- @param device st.zwave.Device --- @return boolean true if the device is fibaro smoke sensor -local function can_handle_fibaro_smoke_sensor(opts, driver, device, cmd, ...) - for _, fingerprint in ipairs(FIBARO_SMOKE_SENSOR_FINGERPRINTS) 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:send(WakeUp:IntervalSet({node_id = self.environment_info.hub_zwave_id, seconds = FIBARO_SMOKE_SENSOR_WAKEUP_INTERVAL})) @@ -76,7 +52,7 @@ local fibaro_smoke_sensor = { added = device_added }, NAME = "fibaro smoke sensor", - can_handle = can_handle_fibaro_smoke_sensor, + can_handle = require("fibaro-smoke-sensor.can_handle"), health_check = false, } diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/init.lua b/drivers/SmartThings/zwave-smoke-alarm/src/init.lua index 0d0a5d1bfd..7968983f63 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/init.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/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.CommandClass @@ -83,12 +73,7 @@ local driver_template = { capabilities.temperatureAlarm, capabilities.temperatureMeasurement }, - sub_drivers = { - require("zwave-smoke-co-alarm-v1"), - require("zwave-smoke-co-alarm-v2"), - require("fibaro-smoke-sensor"), - require("apiv6_bugfix"), - }, + sub_drivers = require("sub_drivers"), lifecycle_handlers = { init = device_init, infoChanged = info_changed, diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-smoke-alarm/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-smoke-alarm/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-smoke-alarm/src/preferences.lua b/drivers/SmartThings/zwave-smoke-alarm/src/preferences.lua index 25606a5255..55fa70d48b 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/preferences.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/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 = { FIBARO = { diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/sub_drivers.lua b/drivers/SmartThings/zwave-smoke-alarm/src/sub_drivers.lua new file mode 100644 index 0000000000..f0a2d96412 --- /dev/null +++ b/drivers/SmartThings/zwave-smoke-alarm/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("zwave-smoke-co-alarm-v1"), + lazy_load_if_possible("zwave-smoke-co-alarm-v2"), + lazy_load_if_possible("fibaro-smoke-sensor"), + lazy_load_if_possible("apiv6_bugfix"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_co_sensor_zw5.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_co_sensor_zw5.lua index 4f3b08d3f3..dc14f1c51b 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_co_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_co_sensor_zw5.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-smoke-alarm/src/test/test_fibaro_smoke_sensor.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_smoke_sensor.lua index ebb50eaa6e..0ec85f00f2 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_smoke_sensor.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_smoke_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-smoke-alarm/src/test/test_zwave_alarm_v1.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_alarm_v1.lua index fd2996b9d1..aa1ebb27f0 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_alarm_v1.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_alarm_v1.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-smoke-alarm/src/test/test_zwave_co_detector.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_co_detector.lua index e42edd4828..1523bf2420 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_co_detector.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_co_detector.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-smoke-alarm/src/test/test_zwave_smoke_detector.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_smoke_detector.lua index 95121b1673..4464a10fda 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_smoke_detector.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_smoke_detector.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-smoke-alarm/src/zwave-smoke-co-alarm-v1/can_handle.lua b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v1/can_handle.lua new file mode 100644 index 0000000000..dc78d0e4ac --- /dev/null +++ b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v1/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_v1_alarm(opts, driver, device, cmd, ...) + -- The default handlers for the Alarm/Notification command class(es) for the + -- Smoke Detector and Carbon Monoxide Detector only handles V3 and up. + if opts.dispatcher_class == "ZwaveDispatcher" and cmd ~= nil and cmd.version ~= nil and cmd.version < 3 then + return true, require("zwave-smoke-co-alarm-v1") + end + return false +end + +return can_handle_v1_alarm diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v1/init.lua b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v1/init.lua index 923c516167..8e3cc4e267 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v1/init.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v1/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -23,17 +12,6 @@ local Alarm = (require "st.zwave.CommandClass.Alarm")({ version = 1 }) -- manufacturerId = 0x0138, productType = 0x0001, productId = 0x0002 -- First Alert Smoke & CO Detector -- manufacturerId = 0x0138, productType = 0x0001, productId = 0x0003 -- First Alert Smoke & CO Detector ---- Determine whether the passed device only supports V1 or V2 of the Alarm command class ---- ---- @param driver st.zwave.Driver ---- @param device st.zwave.Device ---- @return boolean true if the device is smoke co alarm -local function can_handle_v1_alarm(opts, driver, device, cmd, ...) - -- The default handlers for the Alarm/Notification command class(es) for the - -- Smoke Detector and Carbon Monoxide Detector only handles V3 and up. - return opts.dispatcher_class == "ZwaveDispatcher" and cmd ~= nil and cmd.version ~= nil and cmd.version < 3 -end - --- Default handler for alarm command class reports --- --- This converts alarm V1 reports to correct smoke events @@ -75,7 +53,7 @@ local zwave_alarm = { } }, NAME = "Z-Wave smoke and CO alarm V1", - can_handle = can_handle_v1_alarm, + can_handle = require("zwave-smoke-co-alarm-v1.can_handle"), } return zwave_alarm diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/can_handle.lua b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/can_handle.lua new file mode 100644 index 0000000000..6666e7abc2 --- /dev/null +++ b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_v2_alarm(opts, driver, device, cmd, ...) + local FINGERPRINTS = require("zwave-smoke-co-alarm-v2.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("zwave-smoke-co-alarm-v2") + end + end + return false +end + +return can_handle_v2_alarm diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5/can_handle.lua b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5/can_handle.lua new file mode 100644 index 0000000000..baa0d7bbf0 --- /dev/null +++ b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_co_sensor(opts, driver, device, cmd, ...) + local FINGERPRINTS = require("zwave-smoke-co-alarm-v2.fibaro-co-sensor-zw5.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("zwave-smoke-co-alarm-v2.fibaro-co-sensor-zw5") + end + end + return false +end + +return can_handle_fibaro_co_sensor diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5/fingerprints.lua b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5/fingerprints.lua new file mode 100644 index 0000000000..37fb1f508c --- /dev/null +++ b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FIBARO_CO_SENSORS_FINGERPRINTS = { + { manufacturerId = 0x010F, productType = 0x1201, productId = 0x1000 }, -- Fibaro CO Sensor + { manufacturerId = 0x010F, productType = 0x1201, productId = 0x1001 } -- Fibaro CO Sensor +} + +return FIBARO_CO_SENSORS_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5/init.lua b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5/init.lua index bde1cbc877..0559b83983 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5/init.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5/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 Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) @@ -21,10 +11,6 @@ local TAMPERING_AND_EXCEEDING_THE_TEMPERATURE = 3 local ACOUSTIC_SIGNALS = 4 local EXCEEDING_THE_TEMPERATURE = 2 -local FIBARO_CO_SENSORS_FINGERPRINTS = { - { manufacturerId = 0x010F, productType = 0x1201, productId = 0x1000 }, -- Fibaro CO Sensor - { manufacturerId = 0x010F, productType = 0x1201, productId = 0x1001 } -- Fibaro CO Sensor -} local function parameterNumberToParameterName(preferences,parameterNumber) for id, parameter in pairs(preferences) do @@ -40,14 +26,6 @@ end --- @param driver st.zwave.Driver --- @param device st.zwave.Device --- @return boolean true if the device is smoke co alarm -local function can_handle_fibaro_co_sensor(opts, driver, device, cmd, ...) - for _, fingerprint in ipairs(FIBARO_CO_SENSORS_FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -end local function update_preferences(self, device, args) local preferences = preferencesMap.get_device_parameters(device) @@ -108,7 +86,7 @@ local fibaro_co_sensor = { init = device_init, infoChanged = info_changed }, - can_handle = can_handle_fibaro_co_sensor + can_handle = require("zwave-smoke-co-alarm-v2.fibaro-co-sensor-zw5.can_handle"), } return fibaro_co_sensor diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fingerprints.lua b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fingerprints.lua new file mode 100644 index 0000000000..8dc021cc28 --- /dev/null +++ b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local SMOKE_CO_ALARM_V2_FINGERPRINTS = { + { manufacturerId = 0x010F, productType = 0x1201, productId = 0x1000 }, -- Fibaro CO Sensor + { manufacturerId = 0x010F, productType = 0x1201, productId = 0x1001 } -- Fibaro CO Sensor +} + +return SMOKE_CO_ALARM_V2_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/init.lua b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/init.lua index b49b010cdb..5e69f7108d 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/init.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -20,24 +11,12 @@ local Alarm = (require "st.zwave.CommandClass.Alarm")({ version = 2 }) --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) -local SMOKE_CO_ALARM_V2_FINGERPRINTS = { - { manufacturerId = 0x010F, productType = 0x1201, productId = 0x1000 }, -- Fibaro CO Sensor - { manufacturerId = 0x010F, productType = 0x1201, productId = 0x1001 } -- Fibaro CO Sensor -} --- Determine whether the passed device is Smoke Alarm --- --- @param driver st.zwave.Driver --- @param device st.zwave.Device --- @return boolean true if the device is smoke co alarm -local function can_handle_v2_alarm(opts, driver, device, cmd, ...) - for _, fingerprint in ipairs(SMOKE_CO_ALARM_V2_FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -end local device_added = function(self, device) device:emit_event(capabilities.carbonMonoxideDetector.carbonMonoxide.clear()) @@ -94,13 +73,11 @@ local zwave_alarm = { } }, NAME = "Z-Wave smoke and CO alarm V2", - can_handle = can_handle_v2_alarm, + can_handle = require("zwave-smoke-co-alarm-v2.can_handle"), lifecycle_handlers = { added = device_added }, - sub_drivers = { - require("zwave-smoke-co-alarm-v2/fibaro-co-sensor-zw5") - } + sub_drivers = require("zwave-smoke-co-alarm-v2.sub_drivers"), } return zwave_alarm diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/sub_drivers.lua b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/sub_drivers.lua new file mode 100644 index 0000000000..0699743371 --- /dev/null +++ b/drivers/SmartThings/zwave-smoke-alarm/src/zwave-smoke-co-alarm-v2/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("zwave-smoke-co-alarm-v2.fibaro-co-sensor-zw5"), +} +return sub_drivers From 8ebd6e44e3dc738948cc8a3815504172216ea14b Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Thu, 19 Feb 2026 15:28:08 -0600 Subject: [PATCH 123/538] add initialPress handling for BILRESA scrolling --- .../src/sub_drivers/ikea_scroll/init.lua | 1 + .../scroll_handlers/event_handlers.lua | 9 +- .../ikea_scroll/scroll_utils/fields.lua | 1 + .../src/test/test_ikea_scroll.lua | 145 +++++++++++++++++- 4 files changed, 145 insertions(+), 11 deletions(-) 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 index 5c2a009de1..32b23ccaae 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua @@ -49,6 +49,7 @@ local ikea_scroll_handler = { matter_handlers = { event = { [clusters.Switch.ID] = { + [clusters.Switch.events.InitialPress.ID] = event_handlers.initial_press_handler, [clusters.Switch.events.MultiPressOngoing.ID] = event_handlers.multi_press_ongoing_handler, [clusters.Switch.events.MultiPressComplete.ID] = event_handlers.multi_press_complete_handler, } diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua index 7415da8f9b..da9b1c0392 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua @@ -49,9 +49,12 @@ function IkeaScrollEventHandlers.initial_press_handler(driver, device, ib, respo if switch_utils.tbl_contains(scroll_fields.ENDPOINTS_PUSH, ib.endpoint_id) then generic_event_handlers.initial_press_handler(driver, device, ib, response) else - -- Ignore InitialPress events from non-push endpoints. Presently, we want to solely - -- rely on MultiPressOngoing events to handle rotation for those endpoints. - device.log.debug("Received InitialPress event from scroll endpoint, ignoring.") + -- the magic number "1" occurs in this handler since the InitialPress event represents the first press. + local latest_presses_counted = device:get_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED) or 0 + if latest_presses_counted == 0 then + device:set_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED, 1) + rotate_amount_event_helper(device, ib.endpoint_id, 1) + end end end 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 index 58580328b1..5e98a829c0 100644 --- 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 @@ -37,6 +37,7 @@ IkeaScrollFields.switch_press_subscribed_events = { -- Required Events for the ENDPOINTS_UP_SCROLL and ENDPOINTS_DOWN_SCROLL. Adds a -- MultiPressOngoing subscription to handle step functionality in real-time IkeaScrollFields.switch_scroll_subscribed_events = { + clusters.Switch.events.InitialPress.ID, clusters.Switch.events.MultiPressOngoing.ID, clusters.Switch.events.MultiPressComplete.ID, } diff --git a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua index 78f99c37fe..4629d69a5f 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua @@ -133,6 +133,7 @@ local function ikea_scroll_subscribe() clusters.Switch.server.events.MultiPressComplete, } local CLUSTER_SUBSCRIBE_LIST_SCROLL = { + clusters.Switch.events.InitialPress, clusters.Switch.server.events.MultiPressOngoing, clusters.Switch.server.events.MultiPressComplete, } @@ -236,6 +237,22 @@ test.register_message_test( test.register_message_test( "Ikea Scroll Positive rotateAmount events on main are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(6, {state_change = true})) + }, { channel = "matter", direction = "receive", @@ -250,7 +267,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("main", - capabilities.knob.rotateAmount(12, {state_change = true})) + capabilities.knob.rotateAmount(6, {state_change = true})) }, { channel = "matter", @@ -279,6 +296,22 @@ test.register_message_test( }, }, { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(6, {state_change = true})) + }, + { channel = "matter", direction = "receive", message = { @@ -292,7 +325,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("main", - capabilities.knob.rotateAmount(12, {state_change = true})) + capabilities.knob.rotateAmount(6, {state_change = true})) }, { channel = "matter", @@ -325,6 +358,22 @@ test.register_message_test( test.register_message_test( "Ikea Scroll Negative rotateAmount events on main are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[2], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(-6, {state_change = true})) + }, { channel = "matter", direction = "receive", @@ -339,7 +388,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("main", - capabilities.knob.rotateAmount(-12, {state_change = true})) + capabilities.knob.rotateAmount(-6, {state_change = true})) }, { channel = "matter", @@ -367,6 +416,22 @@ test.register_message_test( ) }, }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[2], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(-6, {state_change = true})) + }, { channel = "matter", direction = "receive", @@ -381,7 +446,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("main", - capabilities.knob.rotateAmount(-12, {state_change = true})) + capabilities.knob.rotateAmount(-6, {state_change = true})) }, { channel = "matter", @@ -414,6 +479,22 @@ test.register_message_test( test.register_message_test( "Ikea Scroll Positive rotateAmount events on group2 are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[3], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(6, {state_change = true})) + }, { channel = "matter", direction = "receive", @@ -428,7 +509,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("group2", - capabilities.knob.rotateAmount(12, {state_change = true})) + capabilities.knob.rotateAmount(6, {state_change = true})) }, { channel = "matter", @@ -451,6 +532,22 @@ test.register_message_test( test.register_message_test( "Ikea Scroll Negative rotateAmount events on group2 are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[4], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(-6, {state_change = true})) + }, { channel = "matter", direction = "receive", @@ -465,7 +562,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("group2", - capabilities.knob.rotateAmount(-12, {state_change = true})) + capabilities.knob.rotateAmount(-6, {state_change = true})) }, { channel = "matter", @@ -488,6 +585,22 @@ test.register_message_test( test.register_message_test( "Ikea Scroll Positive rotateAmount events on group3 are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[5], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(6, {state_change = true})) + }, { channel = "matter", direction = "receive", @@ -502,7 +615,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("group3", - capabilities.knob.rotateAmount(12, {state_change = true})) + capabilities.knob.rotateAmount(6, {state_change = true})) }, { channel = "matter", @@ -525,6 +638,22 @@ test.register_message_test( test.register_message_test( "Ikea Scroll Negative rotateAmount events on group3 are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[6], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(-6, {state_change = true})) + }, { channel = "matter", direction = "receive", @@ -539,7 +668,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("group3", - capabilities.knob.rotateAmount(-12, {state_change = true})) + capabilities.knob.rotateAmount(-6, {state_change = true})) }, { channel = "matter", From 71e5828e7d37d8cdd87239eff51599f9e8610fe1 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:04 -0600 Subject: [PATCH 124/538] CHAD-17083: zigbee-window-treatment lazy loading of sub-drivers --- .../src/HOPOsmart/can_handle.lua | 14 + .../src/HOPOsmart/custom_clusters.lua | 16 +- .../src/HOPOsmart/fingerprints.lua | 8 + .../src/HOPOsmart/init.lua | 29 +- .../src/VIVIDSTORM/can_handle.lua | 14 + .../src/VIVIDSTORM/custom_clusters.lua | 16 +- .../src/VIVIDSTORM/fingerprints.lua | 8 + .../src/VIVIDSTORM/init.lua | 29 +- .../src/aqara/can_handle.lua | 14 + .../aqara/curtain-driver-e1/can_handle.lua | 11 + .../src/aqara/curtain-driver-e1/init.lua | 20 +- .../src/aqara/fingerprints.lua | 11 + .../src/aqara/init.lua | 26 +- .../src/aqara/roller-shade/can_handle.lua | 11 + .../src/aqara/roller-shade/init.lua | 283 +++++++++--------- .../src/aqara/sub_drivers.lua | 10 + .../src/aqara/version/can_handle.lua | 13 + .../src/aqara/version/init.lua | 10 +- .../src/axis/axis_version/can_handle.lua | 15 + .../src/axis/axis_version/init.lua | 28 +- .../src/axis/can_handle.lua | 11 + .../zigbee-window-treatment/src/axis/init.lua | 27 +- .../src/axis/sub_drivers.lua | 8 + .../src/feibit/can_handle.lua | 15 + .../src/feibit/fingerprints.lua | 9 + .../src/feibit/init.lua | 31 +- .../src/hanssem/can_handle.lua | 11 + .../src/hanssem/init.lua | 9 +- .../zigbee-window-treatment/src/init.lua | 38 +-- .../src/invert-lift-percentage/can_handle.lua | 14 + .../src/invert-lift-percentage/init.lua | 22 +- .../src/lazy_load_subdriver.lua | 15 + .../src/rooms-beautiful/can_handle.lua | 14 + .../src/rooms-beautiful/fingerprints.lua | 8 + .../src/rooms-beautiful/init.lua | 29 +- .../src/screen-innovations/can_handle.lua | 11 + .../src/screen-innovations/init.lua | 20 +- .../src/somfy/can_handle.lua | 14 + .../src/somfy/fingerprints.lua | 10 + .../src/somfy/init.lua | 31 +- .../src/sub_drivers.lua | 19 ++ .../test_zigbee_window_shade_battery_ikea.lua | 16 +- ...est_zigbee_window_shade_battery_yoolax.lua | 16 +- ...est_zigbee_window_shade_only_HOPOsmart.lua | 16 +- .../src/test/test_zigbee_window_treatment.lua | 16 +- ..._zigbee_window_treatment_VWSDSTUST120H.lua | 16 +- .../test_zigbee_window_treatment_aqara.lua | 16 +- ...ndow_treatment_aqara_curtain_driver_e1.lua | 16 +- ...ow_treatment_aqara_roller_shade_rotate.lua | 16 +- .../test_zigbee_window_treatment_axis.lua | 16 +- .../test_zigbee_window_treatment_feibit.lua | 16 +- .../test_zigbee_window_treatment_hanssem.lua | 18 +- .../test_zigbee_window_treatment_rooms.lua | 16 +- ...ee_window_treatment_screen_innovations.lua | 18 +- .../test_zigbee_window_treatment_somfy.lua | 16 +- .../test_zigbee_window_treatment_vimar.lua | 16 +- .../src/vimar/can_handle.lua | 14 + .../src/vimar/fingerprints.lua | 9 + .../src/vimar/init.lua | 30 +- .../src/window_shade_utils.lua | 18 +- .../src/window_treatment_utils.lua | 16 +- .../src/yoolax/can_handle.lua | 14 + .../src/yoolax/fingerprints.lua | 9 + .../src/yoolax/init.lua | 30 +- 64 files changed, 609 insertions(+), 727 deletions(-) create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/aqara/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/aqara/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/aqara/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/aqara/version/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/axis/axis_version/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/axis/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/axis/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/feibit/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/feibit/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/hanssem/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/rooms-beautiful/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/rooms-beautiful/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/screen-innovations/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/somfy/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/somfy/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/vimar/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/vimar/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/yoolax/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/yoolax/fingerprints.lua diff --git a/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/can_handle.lua new file mode 100644 index 0000000000..85db6c0447 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_window_shade = function(opts, driver, device) + local FINGERPRINTS = require("HOPOsmart.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("HOPOsmart") + end + end + return false +end + +return is_zigbee_window_shade diff --git a/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/custom_clusters.lua b/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/custom_clusters.lua index 1f2950b2f1..b0394aa3fe 100755 --- a/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/custom_clusters.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/custom_clusters.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 data_types = require "st.zigbee.data_types" diff --git a/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/fingerprints.lua b/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/fingerprints.lua new file mode 100644 index 0000000000..abf5e54ae5 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { + { mfr = "HOPOsmart", model = "A2230011" } +} + +return ZIGBEE_WINDOW_SHADE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/init.lua index 5e3f002ec0..3267eefc33 100755 --- a/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/init.lua @@ -1,34 +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 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( @@ -83,7 +62,7 @@ local HOPOsmart_handler = { } } }, - can_handle = is_zigbee_window_shade, + can_handle = require("HOPOsmart.can_handle"), } return HOPOsmart_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/can_handle.lua new file mode 100644 index 0000000000..d6907690c3 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_window_shade = function(opts, driver, device) + local FINGERPRINTS = require("VIVIDSTORM.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("VIVIDSTORM") + end + end + return false +end + +return is_zigbee_window_shade diff --git a/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/custom_clusters.lua b/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/custom_clusters.lua index 6f266a474e..8efbc0e654 100755 --- a/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/custom_clusters.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/custom_clusters.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 data_types = require "st.zigbee.data_types" diff --git a/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/fingerprints.lua b/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/fingerprints.lua new file mode 100644 index 0000000000..ff1bbcb00d --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { + { mfr = "VIVIDSTORM", model = "VWSDSTUST120H" } +} + +return ZIGBEE_WINDOW_SHADE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/init.lua index ef36757490..106115edee 100755 --- a/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/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 zcl_clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" @@ -22,18 +12,7 @@ local MOST_RECENT_SETLEVEL = "windowShade_recent_setlevel" local TIMER = "liftPercentage_timer" -local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { - { mfr = "VIVIDSTORM", model = "VWSDSTUST120H" } -} -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( @@ -156,7 +135,7 @@ local screen_handler = { } } }, - can_handle = is_zigbee_window_shade, + can_handle = require("VIVIDSTORM.can_handle"), } return screen_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/can_handle.lua new file mode 100644 index 0000000000..e4453597ed --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/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-window-treatment/src/aqara/curtain-driver-e1/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/can_handle.lua new file mode 100644 index 0000000000..7eb40a7c10 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function curtain_driver_e1_can_handle(opts, driver, device, ...) + if device:get_model() == "lumi.curtain.agl001" then + return true, require("aqara.curtain-driver-e1") + end + return false +end + +return curtain_driver_e1_can_handle diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.lua index 6fe895ca7d..03e651b679 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.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 + local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" @@ -224,9 +214,7 @@ local aqara_curtain_driver_e1_handler = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "lumi.curtain.agl001" - end + can_handle = require("aqara.curtain-driver-e1.can_handle"), } return aqara_curtain_driver_e1_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/fingerprints.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/fingerprints.lua new file mode 100644 index 0000000000..8ad05530a5 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FINGERPRINTS = { + { mfr = "LUMI", model = "lumi.curtain" }, + { mfr = "LUMI", model = "lumi.curtain.v1" }, + { mfr = "LUMI", model = "lumi.curtain.aq2" }, + { mfr = "LUMI", model = "lumi.curtain.agl001" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/init.lua index 87505d2e40..1b66236038 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/init.lua @@ -1,3 +1,7 @@ +-- 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" @@ -27,21 +31,7 @@ local PREF_SOFT_TOUCH_ON = "\x00\x08\x00\x00\x00\x00\x00" local APPLICATION_VERSION = "application_version" -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.curtain" }, - { mfr = "LUMI", model = "lumi.curtain.v1" }, - { mfr = "LUMI", model = "lumi.curtain.aq2" }, - { mfr = "LUMI", model = "lumi.curtain.agl001" } -} -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 window_shade_level_cmd(driver, device, command) aqara_utils.shade_level_cmd(driver, device, command) @@ -217,12 +207,8 @@ local aqara_window_treatment_handler = { } } }, - sub_drivers = { - require("aqara.roller-shade"), - require("aqara.curtain-driver-e1"), - require("aqara.version") - }, - can_handle = is_aqara_products + sub_drivers = require("aqara.sub_drivers"), + can_handle = require("aqara.can_handle"), } return aqara_window_treatment_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/can_handle.lua new file mode 100644 index 0000000000..2e91fa090d --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function roller_shade_can_handle(opts, driver, device, ...) + if device:get_model() == "lumi.curtain.aq2" then + return true, require("aqara.roller-shade") + end + return false +end + +return roller_shade_can_handle diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua index 12b84eb0c8..909b65dd3e 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua @@ -1,141 +1,142 @@ -local capabilities = require "st.capabilities" -local clusters = require "st.zigbee.zcl.clusters" -local cluster_base = require "st.zigbee.cluster_base" -local FrameCtrl = require "st.zigbee.zcl.frame_ctrl" -local data_types = require "st.zigbee.data_types" -local aqara_utils = require "aqara/aqara_utils" -local window_treatment_utils = require "window_treatment_utils" - -local Basic = clusters.Basic -local WindowCovering = clusters.WindowCovering - -local initializedStateWithGuide = capabilities["stse.initializedStateWithGuide"] -local reverseRollerShadeDir = "stse.reverseRollerShadeDir" -local shadeRotateState = capabilities["stse.shadeRotateState"] -local setRotateStateCommandName = "setRotateState" - -local MULTISTATE_CLUSTER_ID = 0x0013 -local MULTISTATE_ATTRIBUTE_ID = 0x0055 -local ROTATE_UP_VALUE = 0x0004 -local ROTATE_DOWN_VALUE = 0x0005 - - -local function window_shade_level_cmd(driver, device, command) - -- Cannot be controlled if not initialized - local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, - initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 - if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then - aqara_utils.shade_level_cmd(driver, device, command) - end -end - -local function window_shade_open_cmd(driver, device, command) - -- Cannot be controlled if not initialized - local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, - initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 - if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then - device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, 100)) - end -end - -local function window_shade_close_cmd(driver, device, command) - -- Cannot be controlled if not initialized - local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, - initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 - if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then - device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, 0)) - end -end - -local function set_rotate_command_handler(driver, device, command) - device:emit_event(shadeRotateState.rotateState.idle({state_change = true, visibility = { displayed = false }})) -- update UI - - -- Cannot be controlled if not initialized - local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, - initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 - if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then - local state = command.args.state - if state == "rotateUp" then - local message = cluster_base.write_manufacturer_specific_attribute(device, MULTISTATE_CLUSTER_ID, - MULTISTATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint16, ROTATE_UP_VALUE) - message.body.zcl_header.frame_ctrl = FrameCtrl(0x10) - device:send(message) - elseif state == "rotateDown" then - local message = cluster_base.write_manufacturer_specific_attribute(device, MULTISTATE_CLUSTER_ID, - MULTISTATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint16, ROTATE_DOWN_VALUE) - message.body.zcl_header.frame_ctrl = FrameCtrl(0x10) - device:send(message) - end - end -end - -local function shade_state_report_handler(driver, device, value, zb_rx) - aqara_utils.emit_shade_event_by_state(device, value) -end - -local function pref_report_handler(driver, device, value, zb_rx) - -- initializedState - local initialized = string.byte(value.value, 3) & 0xFF - device:emit_event(initialized == 1 and initializedStateWithGuide.initializedStateWithGuide.initialized() or - initializedStateWithGuide.initializedStateWithGuide.notInitialized()) -end - -local function device_info_changed(driver, device, event, args) - if device.preferences ~= nil then - local reverseRollerShadeDirPrefValue = device.preferences[reverseRollerShadeDir] - if reverseRollerShadeDirPrefValue ~= nil and - reverseRollerShadeDirPrefValue ~= args.old_st_store.preferences[reverseRollerShadeDir] then - local raw_value = reverseRollerShadeDirPrefValue and aqara_utils.PREF_REVERSE_ON or aqara_utils.PREF_REVERSE_OFF - device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, - aqara_utils.MFG_CODE, data_types.CharString, raw_value)) - end - end -end - -local function device_added(driver, device) - device:emit_event(capabilities.windowShade.supportedWindowShadeCommands({ "open", "close", "pause" }, {visibility = {displayed = false}})) - window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShadeLevel, capabilities.windowShadeLevel.shadeLevel.NAME, capabilities.windowShadeLevel.shadeLevel(0)) - window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShade, capabilities.windowShade.windowShade.NAME, capabilities.windowShade.windowShade.closed()) - device:emit_event(initializedStateWithGuide.initializedStateWithGuide.notInitialized()) - device:emit_event(shadeRotateState.rotateState.idle({ visibility = { displayed = false }})) - - device:send(cluster_base.write_manufacturer_specific_attribute(device, aqara_utils.PRIVATE_CLUSTER_ID, - aqara_utils.PRIVATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint8, 1)) - - -- Initial default settings - device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, - aqara_utils.MFG_CODE, data_types.CharString, aqara_utils.PREF_REVERSE_OFF)) -end - -local aqara_roller_shade_handler = { - NAME = "Aqara Roller Shade Handler", - lifecycle_handlers = { - added = device_added, - infoChanged = device_info_changed - }, - capability_handlers = { - [capabilities.windowShadeLevel.ID] = { - [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = window_shade_level_cmd - }, - [capabilities.windowShade.ID] = { - [capabilities.windowShade.commands.open.NAME] = window_shade_open_cmd, - [capabilities.windowShade.commands.close.NAME] = window_shade_close_cmd, - }, - [shadeRotateState.ID] = { - [setRotateStateCommandName] = set_rotate_command_handler - } - }, - zigbee_handlers = { - attr = { - [Basic.ID] = { - [aqara_utils.SHADE_STATE_ATTRIBUTE_ID] = shade_state_report_handler, - [aqara_utils.PREF_ATTRIBUTE_ID] = pref_report_handler - } - } - }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "lumi.curtain.aq2" - end -} - -return aqara_roller_shade_handler +-- 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" +local FrameCtrl = require "st.zigbee.zcl.frame_ctrl" +local data_types = require "st.zigbee.data_types" +local aqara_utils = require "aqara/aqara_utils" +local window_treatment_utils = require "window_treatment_utils" + +local Basic = clusters.Basic +local WindowCovering = clusters.WindowCovering + +local initializedStateWithGuide = capabilities["stse.initializedStateWithGuide"] +local reverseRollerShadeDir = capabilities["stse.reverseRollerShadeDir"] +local shadeRotateState = capabilities["stse.shadeRotateState"] +local setRotateStateCommandName = "setRotateState" + +local MULTISTATE_CLUSTER_ID = 0x0013 +local MULTISTATE_ATTRIBUTE_ID = 0x0055 +local ROTATE_UP_VALUE = 0x0004 +local ROTATE_DOWN_VALUE = 0x0005 + + +local function window_shade_level_cmd(driver, device, command) + -- Cannot be controlled if not initialized + local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, + initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 + if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then + aqara_utils.shade_level_cmd(driver, device, command) + end +end + +local function window_shade_open_cmd(driver, device, command) + -- Cannot be controlled if not initialized + local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, + initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 + if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then + device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, 100)) + end +end + +local function window_shade_close_cmd(driver, device, command) + -- Cannot be controlled if not initialized + local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, + initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 + if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then + device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, 0)) + end +end + +local function set_rotate_command_handler(driver, device, command) + device:emit_event(shadeRotateState.rotateState.idle({state_change = true, visibility = { displayed = false }})) -- update UI + + -- Cannot be controlled if not initialized + local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, + initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 + if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then + local state = command.args.state + if state == "rotateUp" then + local message = cluster_base.write_manufacturer_specific_attribute(device, MULTISTATE_CLUSTER_ID, + MULTISTATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint16, ROTATE_UP_VALUE) + message.body.zcl_header.frame_ctrl = FrameCtrl(0x10) + device:send(message) + elseif state == "rotateDown" then + local message = cluster_base.write_manufacturer_specific_attribute(device, MULTISTATE_CLUSTER_ID, + MULTISTATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint16, ROTATE_DOWN_VALUE) + message.body.zcl_header.frame_ctrl = FrameCtrl(0x10) + device:send(message) + end + end +end + +local function shade_state_report_handler(driver, device, value, zb_rx) + aqara_utils.emit_shade_event_by_state(device, value) +end + +local function pref_report_handler(driver, device, value, zb_rx) + -- initializedState + local initialized = string.byte(value.value, 3) & 0xFF + device:emit_event(initialized == 1 and initializedStateWithGuide.initializedStateWithGuide.initialized() or + initializedStateWithGuide.initializedStateWithGuide.notInitialized()) +end + +local function device_info_changed(driver, device, event, args) + if device.preferences ~= nil then + local reverseRollerShadeDirPrefValue = device.preferences[reverseRollerShadeDir.ID] + if reverseRollerShadeDirPrefValue ~= nil and + reverseRollerShadeDirPrefValue ~= args.old_st_store.preferences[reverseRollerShadeDir.ID] then + local raw_value = reverseRollerShadeDirPrefValue and aqara_utils.PREF_REVERSE_ON or aqara_utils.PREF_REVERSE_OFF + device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, + aqara_utils.MFG_CODE, data_types.CharString, raw_value)) + end + end +end + +local function device_added(driver, device) + device:emit_event(capabilities.windowShade.supportedWindowShadeCommands({ "open", "close", "pause" }, {visibility = {displayed = false}})) + window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShadeLevel, capabilities.windowShadeLevel.shadeLevel.NAME, capabilities.windowShadeLevel.shadeLevel(0)) + window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShade, capabilities.windowShade.windowShade.NAME, capabilities.windowShade.windowShade.closed()) + device:emit_event(initializedStateWithGuide.initializedStateWithGuide.notInitialized()) + device:emit_event(shadeRotateState.rotateState.idle({ visibility = { displayed = false }})) + + device:send(cluster_base.write_manufacturer_specific_attribute(device, aqara_utils.PRIVATE_CLUSTER_ID, + aqara_utils.PRIVATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint8, 1)) + + -- Initial default settings + device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, + aqara_utils.MFG_CODE, data_types.CharString, aqara_utils.PREF_REVERSE_OFF)) +end + +local aqara_roller_shade_handler = { + NAME = "Aqara Roller Shade Handler", + lifecycle_handlers = { + added = device_added, + infoChanged = device_info_changed + }, + capability_handlers = { + [capabilities.windowShadeLevel.ID] = { + [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = window_shade_level_cmd + }, + [capabilities.windowShade.ID] = { + [capabilities.windowShade.commands.open.NAME] = window_shade_open_cmd, + [capabilities.windowShade.commands.close.NAME] = window_shade_close_cmd, + }, + [shadeRotateState.ID] = { + [setRotateStateCommandName] = set_rotate_command_handler + } + }, + zigbee_handlers = { + attr = { + [Basic.ID] = { + [aqara_utils.SHADE_STATE_ATTRIBUTE_ID] = shade_state_report_handler, + [aqara_utils.PREF_ATTRIBUTE_ID] = pref_report_handler + } + } + }, + can_handle = require("aqara.roller-shade.can_handle"), +} + +return aqara_roller_shade_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/sub_drivers.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/sub_drivers.lua new file mode 100644 index 0000000000..297f29d970 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/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("aqara.roller-shade"), + lazy_load_if_possible("aqara.curtain-driver-e1"), + lazy_load_if_possible("aqara.version"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/version/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/version/can_handle.lua new file mode 100644 index 0000000000..5d9f7f0135 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/version/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function version_can_handle(opts, driver, device) + local APPLICATION_VERSION = "application_version" + local softwareVersion = device:get_field(APPLICATION_VERSION) + if softwareVersion and softwareVersion ~= 34 then + return true, require("aqara.version") + end + return false +end + +return version_can_handle diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/version/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/version/init.lua index 10182ee928..ae1887d79a 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/aqara/version/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/version/init.lua @@ -1,9 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local clusters = require "st.zigbee.zcl.clusters" local WindowCovering = clusters.WindowCovering -local APPLICATION_VERSION = "application_version" - local function shade_level_report_legacy_handler(driver, device, value, zb_rx) -- not implemented end @@ -17,10 +18,7 @@ local aqara_window_treatment_version_handler = { } } }, - can_handle = function(opts, driver, device) - local softwareVersion = device:get_field(APPLICATION_VERSION) - return softwareVersion and softwareVersion ~= 34 - end + can_handle = require("aqara.version.can_handle"), } return aqara_window_treatment_version_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/axis/axis_version/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/axis/axis_version/can_handle.lua new file mode 100644 index 0000000000..828eb170fa --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/axis/axis_version/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_axis_gear_version = function(opts, driver, device) + local SOFTWARE_VERSION = "software_version" + local MIN_WINDOW_COVERING_VERSION = 1093 + local version = device:get_field(SOFTWARE_VERSION) or 0 + + if version >= MIN_WINDOW_COVERING_VERSION then + return true, require("axis.axis_version") + end + return false +end + +return is_axis_gear_version diff --git a/drivers/SmartThings/zigbee-window-treatment/src/axis/axis_version/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/axis/axis_version/init.lua index c0682d73c0..cbb2930bc3 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/axis/axis_version/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/axis/axis_version/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 window_shade_utils = require "window_shade_utils" @@ -22,18 +12,8 @@ local Level = zcl_clusters.Level local PowerConfiguration = zcl_clusters.PowerConfiguration local WindowCovering = zcl_clusters.WindowCovering -local SOFTWARE_VERSION = "software_version" -local MIN_WINDOW_COVERING_VERSION = 1093 local DEFAULT_LEVEL = 0 -local is_axis_gear_version = function(opts, driver, device) - local version = device:get_field(SOFTWARE_VERSION) or 0 - - if version >= MIN_WINDOW_COVERING_VERSION then - return true - end - return false -end -- Commands local function window_shade_set_level(device, command, level) @@ -141,7 +121,7 @@ local axis_handler_version = { } } }, - can_handle = is_axis_gear_version, + can_handle = require("axis.axis_version.can_handle"), } return axis_handler_version diff --git a/drivers/SmartThings/zigbee-window-treatment/src/axis/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/axis/can_handle.lua new file mode 100644 index 0000000000..049e47acb5 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/axis/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_window_shade = function(opts, driver, device) + if device:get_manufacturer() == "AXIS" then + return true, require("axis") + end + return false +end + +return is_zigbee_window_shade diff --git a/drivers/SmartThings/zigbee-window-treatment/src/axis/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/axis/init.lua index 612dd450a5..ec7c96b975 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/axis/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/axis/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 capabilities = require "st.capabilities" local device_management = require "st.zigbee.device_management" @@ -26,12 +17,6 @@ local WindowCovering = zcl_clusters.WindowCovering local SOFTWARE_VERSION = "software_version" local DEFAULT_LEVEL = 0 -local is_zigbee_window_shade = function(opts, driver, device) - if device:get_manufacturer() == "AXIS" then - return true - end - return false -end -- Commands local function window_shade_set_level(device, command, level) @@ -151,8 +136,8 @@ local axis_handler = { added = device_added, doConfigure = do_configure, }, - sub_drivers = { require("axis.axis_version") }, - can_handle = is_zigbee_window_shade, + sub_drivers = require("axis.sub_drivers"), + can_handle = require("axis.can_handle"), } return axis_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/axis/sub_drivers.lua b/drivers/SmartThings/zigbee-window-treatment/src/axis/sub_drivers.lua new file mode 100644 index 0000000000..e3ea740478 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/axis/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("axis.axis_version") +} diff --git a/drivers/SmartThings/zigbee-window-treatment/src/feibit/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/feibit/can_handle.lua new file mode 100644 index 0000000000..32457dde8a --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/feibit/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_window_shade = function(opts, driver, device) + local FINGERPRINTS = require("feibit.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("feibit") + end + end + + return false +end + +return is_zigbee_window_shade diff --git a/drivers/SmartThings/zigbee-window-treatment/src/feibit/fingerprints.lua b/drivers/SmartThings/zigbee-window-treatment/src/feibit/fingerprints.lua new file mode 100644 index 0000000000..95781ff992 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/feibit/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { + { mfr = "Feibit Co.Ltd", model = "FTB56-ZT218AK1.6" }, + { mfr = "Feibit Co.Ltd", model = "FTB56-ZT218AK1.8" }, +} + +return ZIGBEE_WINDOW_SHADE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-window-treatment/src/feibit/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/feibit/init.lua index e0fd17219e..1e97fbc4ce 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/feibit/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/feibit/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" @@ -19,20 +9,7 @@ local window_shade_defaults = require "st.zigbee.defaults.windowShade_defaults" local device_management = require "st.zigbee.device_management" local Level = zcl_clusters.Level -local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { - { mfr = "Feibit Co.Ltd", model = "FTB56-ZT218AK1.6" }, - { mfr = "Feibit Co.Ltd", model = "FTB56-ZT218AK1.8" }, -} - -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 set_shade_level(device, value, component) local level = math.floor(value / 100.0 * 254) @@ -87,7 +64,7 @@ local feibit_handler = { lifecycle_handlers = { doConfigure = do_configure, }, - can_handle = is_zigbee_window_shade, + can_handle = require("feibit.can_handle"), } return feibit_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/hanssem/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/hanssem/can_handle.lua new file mode 100644 index 0000000000..65f1a6dc75 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/hanssem/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function hanssem_can_handle(opts, driver, device, ...) + if device:get_model() == "TS0601" then + return true, require("hanssem") + end + return false +end + +return hanssem_can_handle diff --git a/drivers/SmartThings/zigbee-window-treatment/src/hanssem/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/hanssem/init.lua index be956d79b2..41b4eb1cac 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/hanssem/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/hanssem/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2021-2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- -- Based on https://github.com/iquix/ST-Edge-Driver/blob/master/tuya-window-shade/src/init.lua -- Copyright 2021-2022 Jaewon Park (iquix) @@ -241,9 +244,7 @@ local hanssem_window_treatment = { added = device_added, infoChanged = device_info_changed }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "TS0601" - end + can_handle = require("hanssem.can_handle"), } -return hanssem_window_treatment \ No newline at end of file +return hanssem_window_treatment diff --git a/drivers/SmartThings/zigbee-window-treatment/src/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/init.lua index 3403b3d528..16783a8726 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/init.lua +++ b/drivers/SmartThings/zigbee-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" local ZigbeeDriver = require "st.zigbee" @@ -46,29 +36,17 @@ local zigbee_window_treatment_driver_template = { capabilities.powerSource, capabilities.battery }, - sub_drivers = { - require("vimar"), - require("aqara"), - require("feibit"), - require("somfy"), - require("invert-lift-percentage"), - require("rooms-beautiful"), - require("axis"), - require("yoolax"), - require("hanssem"), - require("screen-innovations"), - require("VIVIDSTORM"), - require("HOPOsmart")}, - lifecycle_handlers = { - init = init_handler, - added = added_handler - }, capability_handlers = { [capabilities.windowShadePreset.ID] = { [capabilities.windowShadePreset.commands.setPresetPosition.NAME] = window_shade_utils.set_preset_position_cmd, [capabilities.windowShadePreset.commands.presetPosition.NAME] = window_shade_utils.window_shade_preset_cmd, } }, + lifecycle_handlers = { + init = init_handler, + added = added_handler + }, + sub_drivers = require("sub_drivers"), health_check = false, } diff --git a/drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/can_handle.lua new file mode 100644 index 0000000000..69cfd4393a --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function invert_lift_percentage_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "IKEA of Sweden" or + device:get_manufacturer() == "Smartwings" or + device:get_manufacturer() == "Insta GmbH" + then + return true, require("invert-lift-percentage") + end + return false +end + +return invert_lift_percentage_can_handle diff --git a/drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/init.lua index 19532bc847..b586459b9a 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/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" @@ -97,11 +87,7 @@ local ikea_window_treatment = { [capabilities.windowShadePreset.commands.presetPosition.NAME] = window_shade_preset_cmd } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "IKEA of Sweden" or - device:get_manufacturer() == "Smartwings" or - device:get_manufacturer() == "Insta GmbH" - end + can_handle = require("invert-lift-percentage.can_handle"), } return ikea_window_treatment diff --git a/drivers/SmartThings/zigbee-window-treatment/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-window-treatment/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/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-window-treatment/src/rooms-beautiful/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/rooms-beautiful/can_handle.lua new file mode 100644 index 0000000000..6bc25d2f91 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/rooms-beautiful/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_window_shade = function(opts, driver, device) + local FINGERPRINTS = require("rooms-beautiful.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("rooms-beautiful") + end + end + return false +end + +return is_zigbee_window_shade diff --git a/drivers/SmartThings/zigbee-window-treatment/src/rooms-beautiful/fingerprints.lua b/drivers/SmartThings/zigbee-window-treatment/src/rooms-beautiful/fingerprints.lua new file mode 100644 index 0000000000..71ece32b8e --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/rooms-beautiful/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { + { mfr = "Rooms Beautiful", model = "C001" } +} + +return ZIGBEE_WINDOW_SHADE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-window-treatment/src/rooms-beautiful/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/rooms-beautiful/init.lua index fc4883aa7f..bb868a8716 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/rooms-beautiful/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/rooms-beautiful/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" @@ -21,22 +11,11 @@ local PowerConfiguration = zcl_clusters.PowerConfiguration local OnOff = zcl_clusters.OnOff local WindowCovering = zcl_clusters.WindowCovering -local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { - { mfr = "Rooms Beautiful", model = "C001" } -} local INVERT_CLUSTER = 0xFC00 local INVERT_CLUSTER_ATTRIBUTE = 0x0000 local PREV_TIME = "shadeLevelCmdTime" -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 invert_preference_handler(device) local window_level = device:get_latest_state("main", capabilities.windowShadeLevel.ID, capabilities.windowShadeLevel.shadeLevel.NAME) or 0 @@ -129,7 +108,7 @@ local rooms_beautiful_handler = { init = battery_defaults.build_linear_voltage_init(2.5, 3.0), infoChanged = info_changed }, - can_handle = is_zigbee_window_shade, + can_handle = require("rooms-beautiful.can_handle"), } return rooms_beautiful_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/screen-innovations/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/screen-innovations/can_handle.lua new file mode 100644 index 0000000000..df291c2612 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/screen-innovations/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function screen_innovations_can_handle(opts, driver, device, ...) + if device:get_model() == "WM25/L-Z" then + return true, require("screen-innovations") + end + return false +end + +return screen_innovations_can_handle diff --git a/drivers/SmartThings/zigbee-window-treatment/src/screen-innovations/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/screen-innovations/init.lua index 868e92af56..49397a5369 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/screen-innovations/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/screen-innovations/init.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 + -- require st provided libraries local capabilities = require "st.capabilities" @@ -173,9 +163,7 @@ local screeninnovations_roller_shade_handler = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "WM25/L-Z" - end + can_handle = require("screen-innovations.can_handle"), } -- return the handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/somfy/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/somfy/can_handle.lua new file mode 100644 index 0000000000..27a6c83ca3 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/somfy/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_window_shade = function(opts, driver, device) + local FINGERPRINTS = require("somfy.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("somfy") + end + end + return false +end + +return is_zigbee_window_shade diff --git a/drivers/SmartThings/zigbee-window-treatment/src/somfy/fingerprints.lua b/drivers/SmartThings/zigbee-window-treatment/src/somfy/fingerprints.lua new file mode 100644 index 0000000000..ce6094564c --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/somfy/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { + { mfr = "SOMFY", model = "Glydea Ultra Curtain" }, + { mfr = "SOMFY", model = "Sonesse 30 WF Roller" }, + { mfr = "SOMFY", model = "Sonesse 40 Roller" } +} + +return ZIGBEE_WINDOW_SHADE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-window-treatment/src/somfy/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/somfy/init.lua index ffc9541b64..da416ba9ea 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/somfy/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/somfy/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" @@ -20,23 +10,10 @@ local WindowCovering = zcl_clusters.WindowCovering local GLYDEA_MOVE_THRESHOLD = 3 -local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { - { mfr = "SOMFY", model = "Glydea Ultra Curtain" }, - { mfr = "SOMFY", model = "Sonesse 30 WF Roller" }, - { mfr = "SOMFY", model = "Sonesse 40 Roller" } -} local MOVE_LESS_THAN_THRESHOLD = "_sameLevelEvent" local FINAL_STATE_POLL_TIMER = "_finalStatePollTimer" -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 overwrite_existing_timer_if_needed(device, new_timer) local old_timer = device:get_field(FINAL_STATE_POLL_TIMER) @@ -132,7 +109,7 @@ local somfy_handler = { } } }, - can_handle = is_zigbee_window_shade, + can_handle = require("somfy.can_handle"), } return somfy_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/sub_drivers.lua b/drivers/SmartThings/zigbee-window-treatment/src/sub_drivers.lua new file mode 100644 index 0000000000..959c8d8c22 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/sub_drivers.lua @@ -0,0 +1,19 @@ +-- 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("vimar"), + lazy_load_if_possible("aqara"), + lazy_load_if_possible("feibit"), + lazy_load_if_possible("somfy"), + lazy_load_if_possible("invert-lift-percentage"), + lazy_load_if_possible("rooms-beautiful"), + lazy_load_if_possible("axis"), + lazy_load_if_possible("yoolax"), + lazy_load_if_possible("hanssem"), + lazy_load_if_possible("screen-innovations"), + lazy_load_if_possible("VIVIDSTORM"), + lazy_load_if_possible("HOPOsmart"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua index c0e83e5ab8..a73bb6c5a3 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.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-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua index d51f303378..c13164df09 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.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-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 index 1e4a8b3a2a..4c3028fd46 100755 --- 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 @@ -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 test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua index 6380e5ce51..16d7ebf366 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_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 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.lua index 394498b3c8..69da00efb8 100755 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.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 test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara.lua index d669681c0b..050d0b34f0 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara.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 base64 = require "st.base64" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_curtain_driver_e1.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_curtain_driver_e1.lua index 2b095c6c16..ea389680f2 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_curtain_driver_e1.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_curtain_driver_e1.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 + local zigbee_test_utils = require "integration_test.zigbee_test_utils" local cluster_base = require "st.zigbee.cluster_base" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_roller_shade_rotate.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_roller_shade_rotate.lua index a27f85f528..bd9d5684e6 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_roller_shade_rotate.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_roller_shade_rotate.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 base64 = require "st.base64" local capabilities = require "st.capabilities" 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 f3564ff79f..e8faf2a33d 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 @@ -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 base64 = require "st.base64" 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 aa0480eb2a..7cfb443256 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 @@ -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-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 2e511c99bb..db50f28d32 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 @@ -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 zigbee_test_utils = require "integration_test.zigbee_test_utils" @@ -371,4 +361,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-window-treatment/src/test/test_zigbee_window_treatment_rooms.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_rooms.lua index b3bc0b6c29..303397191d 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_rooms.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_rooms.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 base64 = require "st.base64" diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua index b7f630cf71..c0a004ff6e 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.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 + -- Mock out globals local test = require "integration_test" @@ -429,4 +419,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-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua index 4c66d29257..6c8ea27d01 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.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-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua index 80a6552d8b..ae87438c99 100755 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.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-window-treatment/src/vimar/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/vimar/can_handle.lua new file mode 100644 index 0000000000..1d72817eae --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/vimar/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_window_shade = function(opts, driver, device) + local FINGERPRINTS = require("vimar.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("vimar") + end + end + return false +end + +return is_zigbee_window_shade diff --git a/drivers/SmartThings/zigbee-window-treatment/src/vimar/fingerprints.lua b/drivers/SmartThings/zigbee-window-treatment/src/vimar/fingerprints.lua new file mode 100644 index 0000000000..ea7f4cd3bf --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/vimar/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { + { mfr = "Vimar", model = "Window_Cov_v1.0" }, + { mfr = "Vimar", model = "Window_Cov_Module_v1.0" } +} + +return ZIGBEE_WINDOW_SHADE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-window-treatment/src/vimar/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/vimar/init.lua index 9fa928645a..dd5ea15aed 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/vimar/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/vimar/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" @@ -27,20 +17,8 @@ local windowShade = capabilities.windowShade.windowShade local VIMAR_SHADES_OPENING = "_vimarShadesOpening" local VIMAR_SHADES_CLOSING = "_vimarShadesClosing" -local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { - { mfr = "Vimar", model = "Window_Cov_v1.0" }, - { mfr = "Vimar", model = "Window_Cov_Module_v1.0" } -} -- UTILS to check manufacturer details -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 -- ATTRIBUTE HANDLER FOR CurrentPositionLiftPercentage local function current_position_attr_handler(driver, device, value, zb_rx) @@ -176,7 +154,7 @@ local vimar_handler = { lifecycle_handlers = { init = device_init }, - can_handle = is_zigbee_window_shade, + can_handle = require("vimar.can_handle"), } return vimar_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/window_shade_utils.lua b/drivers/SmartThings/zigbee-window-treatment/src/window_shade_utils.lua index 262e549c2d..f3e09c20a6 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/window_shade_utils.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/window_shade_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 zcl_clusters = require "st.zigbee.zcl.clusters" @@ -41,4 +31,4 @@ utils.set_preset_position_cmd = function(driver, device, command) device:set_field(utils.PRESET_LEVEL_KEY, command.args.position, {persist = true}) end -return utils \ No newline at end of file +return utils diff --git a/drivers/SmartThings/zigbee-window-treatment/src/window_treatment_utils.lua b/drivers/SmartThings/zigbee-window-treatment/src/window_treatment_utils.lua index 2f20ff2b4f..5b2ec304e6 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/window_treatment_utils.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/window_treatment_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 window_treatment_utils = {} diff --git a/drivers/SmartThings/zigbee-window-treatment/src/yoolax/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/yoolax/can_handle.lua new file mode 100644 index 0000000000..006fa3e1bb --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/yoolax/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_yoolax_window_shade(opts, driver, device) + local FINGERPRINTS = require("yoolax.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("yoolax") + end + end + return false +end + +return is_yoolax_window_shade diff --git a/drivers/SmartThings/zigbee-window-treatment/src/yoolax/fingerprints.lua b/drivers/SmartThings/zigbee-window-treatment/src/yoolax/fingerprints.lua new file mode 100644 index 0000000000..30e0dd4c62 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/yoolax/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local YOOLAX_WINDOW_SHADE_FINGERPRINTS = { + { mfr = "Yookee", model = "D10110" }, -- Yookee Window Treatment + { mfr = "yooksmart", model = "D10110" } -- yooksmart Window Treatment +} + +return YOOLAX_WINDOW_SHADE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-window-treatment/src/yoolax/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/yoolax/init.lua index 46cb33fed2..5a593cdf2c 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/yoolax/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/yoolax/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" @@ -24,19 +14,7 @@ local device_management = require "st.zigbee.device_management" local LEVEL_UPDATE_TIMEOUT = "__level_update_timeout" local MOST_RECENT_SETLEVEL = "__most_recent_setlevel" -local YOOLAX_WINDOW_SHADE_FINGERPRINTS = { - { mfr = "Yookee", model = "D10110" }, -- Yookee Window Treatment - { mfr = "yooksmart", model = "D10110" } -- yooksmart Window Treatment -} -local function is_yoolax_window_shade(opts, driver, device) - for _, fingerprint in ipairs(YOOLAX_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 default_response_handler(driver, device, zb_message) local is_success = zb_message.body.zcl_body.status.value @@ -160,7 +138,7 @@ local yoolax_window_shade = { } }, }, - can_handle = is_yoolax_window_shade + can_handle = require("yoolax.can_handle"), } return yoolax_window_shade From 6fece83ff2871142704204e6ee92f86569d816bd Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 19 Feb 2026 13:59:55 -0800 Subject: [PATCH 125/538] WWSTCERT-10417 ubisys dimmer D1-R --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 1c26d5d6c8..6bb32c5978 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -615,6 +615,11 @@ zigbeeManufacturer: manufacturer: ubisys model: "1151" deviceProfileName: switch-power + - id: "ubisys/D1-R (5603)" + deviceLabel: Dimmer D1-R + manufacturer: ubisys + model: "D1-R (5603)" + deviceProfileName: switch-level-power - id: "Megaman/AD-DimmableLight3001" deviceLabel: INGENIUM Light manufacturer: Megaman From 344f87fb9b3916b22795d50591dd480e317d8da7 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 23 Feb 2026 11:56:04 -0600 Subject: [PATCH 126/538] Revert "CHAD-17092: zwave-sensor lazy loading of sub-drivers" This reverts commit 9a714175757dea23539df997566506121325e578. --- .../src/aeotec-multisensor/can_handle.lua | 15 ------ .../src/aeotec-multisensor/fingerprints.lua | 9 ---- .../src/aeotec-multisensor/init.lua | 37 ++++++++++++-- .../multisensor-6/can_handle.lua | 11 ---- .../aeotec-multisensor/multisensor-6/init.lua | 23 +++++++-- .../multisensor-7/can_handle.lua | 12 ----- .../aeotec-multisensor/multisensor-7/init.lua | 24 +++++++-- .../src/aeotec-multisensor/sub_drivers.lua | 9 ---- .../src/aeotec-water-sensor/can_handle.lua | 15 ------ .../src/aeotec-water-sensor/fingerprints.lua | 11 ---- .../src/aeotec-water-sensor/init.lua | 34 +++++++++++-- .../src/apiv6_bugfix/can_handle.lua | 35 ------------- .../zwave-sensor/src/apiv6_bugfix/init.lua | 33 ++++++++++-- .../zwave-sensor/src/configurations.lua | 16 ++++-- .../src/enerwave-motion-sensor/can_handle.lua | 12 ----- .../src/enerwave-motion-sensor/init.lua | 27 ++++++++-- .../can_handle.lua | 17 ------- .../everspring-motion-light-sensor/init.lua | 32 ++++++++++-- .../can_handle.lua | 15 ------ .../ezmultipli-multipurpose-sensor/init.lua | 32 +++++++++--- .../fibaro-door-window-sensor/can_handle.lua | 15 ------ .../can_handle.lua | 14 ------ .../fingerprints.lua | 8 --- .../fibaro-door-window-sensor-1/init.lua | 30 +++++++++-- .../can_handle.lua | 14 ------ .../fingerprints.lua | 10 ---- .../fibaro-door-window-sensor-2/init.lua | 32 ++++++++++-- .../fingerprints.lua | 15 ------ .../src/fibaro-door-window-sensor/init.lua | 43 ++++++++++++++-- .../fibaro-door-window-sensor/sub_drivers.lua | 9 ---- .../src/fibaro-flood-sensor/can_handle.lua | 14 ------ .../src/fibaro-flood-sensor/init.lua | 30 +++++++++-- .../src/fibaro-motion-sensor/can_handle.lua | 15 ------ .../src/fibaro-motion-sensor/init.lua | 29 +++++++++-- .../src/firmware-version/can_handle.lua | 21 -------- .../src/firmware-version/init.lua | 34 +++++++++++-- .../can_handle.lua | 20 -------- .../glentronics-water-leak-sensor/init.lua | 36 +++++++++++-- .../src/homeseer-multi-sensor/can_handle.lua | 20 -------- .../src/homeseer-multi-sensor/init.lua | 36 +++++++++++-- drivers/SmartThings/zwave-sensor/src/init.lua | 50 +++++++++++++++++-- .../zwave-sensor/src/lazy_load_subdriver.lua | 18 ------- .../zwave-sensor/src/preferences.lua | 16 ++++-- .../src/sensative-strip/can_handle.lua | 14 ------ .../zwave-sensor/src/sensative-strip/init.lua | 27 ++++++++-- .../zwave-sensor/src/sub_drivers.lua | 26 ---------- .../src/test/test_aeon_multisensor.lua | 16 ++++-- .../src/test/test_aeotec_multisensor_6.lua | 16 ++++-- .../src/test/test_aeotec_multisensor_7.lua | 16 ++++-- .../src/test/test_aeotec_multisensor_gen5.lua | 16 ++++-- .../src/test/test_aeotec_water_sensor.lua | 16 ++++-- .../src/test/test_aeotec_water_sensor_7.lua | 16 ++++-- .../src/test/test_enerwave_motion_sensor.lua | 16 ++++-- .../src/test/test_everpsring_sp817.lua | 16 ++++-- .../src/test/test_everspring_PIR_sensor.lua | 16 ++++-- .../src/test/test_everspring_ST814.lua | 16 ++++-- .../test_everspring_illuminance_sensor.lua | 16 ++++-- .../test_everspring_motion_light_sensor.lua | 16 ++++-- .../test_ezmultipli_multipurpose_sensor.lua | 16 ++++-- .../test/test_fibaro_door_window_sensor.lua | 16 ++++-- .../test/test_fibaro_door_window_sensor_1.lua | 16 ++++-- .../test/test_fibaro_door_window_sensor_2.lua | 16 ++++-- ...ro_door_window_sensor_with_temperature.lua | 16 ++++-- .../src/test/test_fibaro_flood_sensor.lua | 16 ++++-- .../src/test/test_fibaro_flood_sensor_zw5.lua | 16 ++++-- .../src/test/test_fibaro_motion_sensor.lua | 16 ++++-- .../test/test_fibaro_motion_sensor_zw5.lua | 16 ++++-- .../src/test/test_generic_sensor.lua | 16 ++++-- .../test_glentronics_water_leak_sensor.lua | 16 ++++-- .../src/test/test_homeseer_multi_sensor.lua | 16 ++++-- .../src/test/test_no_wakeup_poll.lua | 18 +++++-- .../src/test/test_sensative_strip.lua | 16 ++++-- .../test_smartthings_water_leak_sensor.lua | 16 ++++-- .../src/test/test_v1_contact_event.lua | 16 ++++-- .../src/test/test_vision_motion_detector.lua | 16 ++++-- .../src/test/test_zooz_4_in_1_sensor.lua | 16 ++++-- .../test/test_zwave_motion_light_sensor.lua | 16 ++++-- .../test_zwave_motion_temp_light_sensor.lua | 16 ++++-- .../src/test/test_zwave_sensor.lua | 16 ++++-- .../src/test/test_zwave_water_sensor.lua | 16 ++++-- .../src/timed-tamper-clear/can_handle.lua | 23 --------- .../src/timed-tamper-clear/init.lua | 36 +++++++++++-- .../src/v1-contact-event/can_handle.lua | 22 -------- .../src/v1-contact-event/init.lua | 31 ++++++++++-- .../src/vision-motion-detector/can_handle.lua | 17 ------- .../src/vision-motion-detector/init.lua | 33 ++++++++++-- .../src/wakeup-no-poll/can_handle.lua | 12 ----- .../zwave-sensor/src/wakeup-no-poll/init.lua | 32 +++++++++--- .../src/zooz-4-in-1-sensor/can_handle.lua | 15 ------ .../src/zooz-4-in-1-sensor/fingerprints.lua | 10 ---- .../src/zooz-4-in-1-sensor/init.lua | 33 ++++++++++-- .../zwave-water-leak-sensor/can_handle.lua | 15 ------ .../zwave-water-leak-sensor/fingerprints.lua | 19 ------- .../src/zwave-water-leak-sensor/init.lua | 43 ++++++++++++++-- 94 files changed, 1160 insertions(+), 742 deletions(-) delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/sub_drivers.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua deleted file mode 100644 index 99cea3c0b3..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_aeotec_multisensor(opts, self, device, ...) - local FINGERPRINTS = require("aeotec-multisensor.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("aeotec-multisensor") - return true, subdriver, require("aeotec-multisensor") - end - end - return false -end - -return can_handle_aeotec_multisensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua deleted file mode 100644 index 9436a85979..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua +++ /dev/null @@ -1,9 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local AEOTEC_MULTISENSOR_FINGERPRINTS = { - { manufacturerId = 0x0086, productId = 0x0064 }, -- MultiSensor 6 - { manufacturerId = 0x0371, productId = 0x0018 }, -- MultiSensor 7 -} - -return AEOTEC_MULTISENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua index d1759a41e0..edd01c7553 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -7,6 +18,21 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +local AEOTEC_MULTISENSOR_FINGERPRINTS = { + { manufacturerId = 0x0086, productId = 0x0064 }, -- MultiSensor 6 + { manufacturerId = 0x0371, productId = 0x0018 }, -- MultiSensor 7 +} + +local function can_handle_aeotec_multisensor(opts, self, device, ...) + for _, fingerprint in ipairs(AEOTEC_MULTISENSOR_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("aeotec-multisensor") + return true, subdriver + end + end + return false +end + local function notification_report_handler(self, device, cmd) local event if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then @@ -35,9 +61,12 @@ local aeotec_multisensor = { [Notification.REPORT] = notification_report_handler } }, - sub_drivers = require("aeotec-multisensor.sub_drivers"), + sub_drivers = { + require("aeotec-multisensor/multisensor-6"), + require("aeotec-multisensor/multisensor-7") + }, NAME = "aeotec multisensor", - can_handle = require("aeotec-multisensor.can_handle"), + can_handle = can_handle_aeotec_multisensor } return aeotec_multisensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua deleted file mode 100644 index d86e9c8b3a..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_multisensor_6(opts, self, device, ...) -local MULTISENSOR_6_PRODUCT_ID = 0x0064 - if device.zwave_product_id == MULTISENSOR_6_PRODUCT_ID then - return true, require("aeotec-multisensor.multisensor-6") - end - return false -end -return can_handle_multisensor_6 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua index 4174b3b14e..1b9d4d6b97 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -10,8 +19,12 @@ local cc = require "st.zwave.CommandClass" local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) local WakeUp = (require "st.zwave.CommandClass.WakeUp")({version = 2}) +local MULTISENSOR_6_PRODUCT_ID = 0x0064 local PREFERENCE_NUM = 9 +local function can_handle_multisensor_6(opts, self, device, ...) + return device.zwave_product_id == MULTISENSOR_6_PRODUCT_ID +end local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher @@ -49,7 +62,7 @@ local multisensor_6 = { } }, NAME = "aeotec multisensor 6", - can_handle = require("aeotec-multisensor.multisensor-6.can_handle"), + can_handle = can_handle_multisensor_6 } return multisensor_6 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua deleted file mode 100644 index f109d0e31c..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua +++ /dev/null @@ -1,12 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_multisensor_7(opts, self, device, ...) - local MULTISENSOR_7_PRODUCT_ID = 0x0018 - if device.zwave_product_id == MULTISENSOR_7_PRODUCT_ID then - return true, require("aeotec-multisensor.multisensor-7") - end - return false -end - -return can_handle_multisensor_7 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua index c3dc69178f..2d2bf4e36e 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -10,8 +19,13 @@ local cc = require "st.zwave.CommandClass" local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 2 }) +local MULTISENSOR_7_PRODUCT_ID = 0x0018 local PREFERENCE_NUM = 10 +local function can_handle_multisensor_7(opts, self, device, ...) + return device.zwave_product_id == MULTISENSOR_7_PRODUCT_ID +end + local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher --This is done to help the hub correctly set the checkInterval for migrated devices. @@ -48,7 +62,7 @@ local multisensor_7 = { } }, NAME = "aeotec multisensor 7", - can_handle = require("aeotec-multisensor.multisensor-7.can_handle"), + can_handle = can_handle_multisensor_7 } return multisensor_7 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua deleted file mode 100644 index 396f53fe86..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua +++ /dev/null @@ -1,9 +0,0 @@ --- 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-multisensor/multisensor-6"), - lazy_load_if_possible("aeotec-multisensor/multisensor-7"), -} -return sub_drivers diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua deleted file mode 100644 index 1bdf9f12a8..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_zwave_water_temp_humidity_sensor(opts, driver, device, ...) - local FINGERPRINTS = require("aeotec-water-sensor.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("aeotec-water-sensor") - return true, subdriver, require("aeotec-water-sensor") - end - end - return false -end - -return can_handle_zwave_water_temp_humidity_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua deleted file mode 100644 index 423d87754e..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS = { - { manufacturerId = 0x0371, productType = 0x0002, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro EU - { manufacturerId = 0x0371, productType = 0x0102, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro US - { manufacturerId = 0x0371, productType = 0x0202, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro AU - { manufacturerId = 0x0371, productId = 0x0012 } -- Aeotec Water Sensor 7 -} - -return ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua index 4c7a86e708..9d883ea3c2 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -9,8 +18,23 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +local ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS = { + { manufacturerId = 0x0371, productType = 0x0002, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro EU + { manufacturerId = 0x0371, productType = 0x0102, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro US + { manufacturerId = 0x0371, productType = 0x0202, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro AU + { manufacturerId = 0x0371, productId = 0x0012 } -- Aeotec Water Sensor 7 +} --- Determine whether the passed device is zwave water temperature humidiry sensor +local function can_handle_zwave_water_temp_humidity_sensor(opts, driver, device, ...) + for _, fingerprint in ipairs(ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("aeotec-water-sensor") + return true, subdriver + end + end + return false +end --- Default handler for notification command class reports --- @@ -44,7 +68,7 @@ local zwave_water_temp_humidity_sensor = { }, }, NAME = "zwave water temp humidity sensor", - can_handle = require("aeotec-water-sensor.can_handle"), + can_handle = can_handle_zwave_water_temp_humidity_sensor } return zwave_water_temp_humidity_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua deleted file mode 100644 index 4913e9a25e..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua +++ /dev/null @@ -1,35 +0,0 @@ --- 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 }) - --- doing refresh would cause incorrect state for device, see comments in wakeup-no-poll -local NORTEK_FP = {mfr = 0x014F, prod = 0x2001, model = 0x0102} -- NorTek open/close sensor -local POPP_THERMOSTAT_FP = {mfr = 0x0002, prod = 0x0115, model = 0xA010} --Popp thermostat -local AEOTEC_MULTISENSOR_6_FP = {mfr = 0x0086, model = 0x0064} --Aeotec multisensor 6 -local AEOTEC_MULTISENSOR_7_FP = {mfr = 0x0371, model = 0x0018} --Aeotec multisensor 7 -local ENERWAVE_MOTION_FP = {mfr = 0x011A} --Enerwave motion sensor -local HOMESEER_MULTI_SENSOR_FP = {mfr = 0x001E, prod = 0x0002, model = 0x0001} -- Homeseer multi sensor HSM100 -local SENSATIVE_STRIP_FP = {mfr = 0x019A, model = 0x000A} -local FPS = {NORTEK_FP, POPP_THERMOSTAT_FP, - AEOTEC_MULTISENSOR_6_FP, AEOTEC_MULTISENSOR_7_FP, - ENERWAVE_MOTION_FP, HOMESEER_MULTI_SENSOR_FP, SENSATIVE_STRIP_FP} - -local function can_handle(opts, driver, device, cmd, ...) - local version = require "version" - if version.api == 6 and - cmd.cmd_class == cc.WAKE_UP and - cmd.cmd_id == WakeUp.NOTIFICATION then - - for _, fp in ipairs(FPS) do - if device:id_match(fp.mfr, fp.prod, fp.model) then return false end - end - local subdriver = require("apiv6_bugfix") - return true, subdriver - else - return false - end -end - -return can_handle diff --git a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua index 94dc5975ab..322333d565 100644 --- a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua @@ -1,9 +1,34 @@ --- 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 }) +-- doing refresh would cause incorrect state for device, see comments in wakeup-no-poll +local NORTEK_FP = {mfr = 0x014F, prod = 0x2001, model = 0x0102} -- NorTek open/close sensor +local POPP_THERMOSTAT_FP = {mfr = 0x0002, prod = 0x0115, model = 0xA010} --Popp thermostat +local AEOTEC_MULTISENSOR_6_FP = {mfr = 0x0086, model = 0x0064} --Aeotec multisensor 6 +local AEOTEC_MULTISENSOR_7_FP = {mfr = 0x0371, model = 0x0018} --Aeotec multisensor 7 +local ENERWAVE_MOTION_FP = {mfr = 0x011A} --Enerwave motion sensor +local HOMESEER_MULTI_SENSOR_FP = {mfr = 0x001E, prod = 0x0002, model = 0x0001} -- Homeseer multi sensor HSM100 +local SENSATIVE_STRIP_FP = {mfr = 0x019A, model = 0x000A} +local FPS = {NORTEK_FP, POPP_THERMOSTAT_FP, + AEOTEC_MULTISENSOR_6_FP, AEOTEC_MULTISENSOR_7_FP, + ENERWAVE_MOTION_FP, HOMESEER_MULTI_SENSOR_FP, SENSATIVE_STRIP_FP} + +local function can_handle(opts, driver, device, cmd, ...) + local version = require "version" + if version.api == 6 and + cmd.cmd_class == cc.WAKE_UP and + cmd.cmd_id == WakeUp.NOTIFICATION then + + for _, fp in ipairs(FPS) do + if device:id_match(fp.mfr, fp.prod, fp.model) then return false end + end + local subdriver = require("apiv6_bugfix") + return true, subdriver + else + return false + end +end + local function wakeup_notification(driver, device, cmd) device:refresh() end @@ -15,7 +40,7 @@ local apiv6_bugfix = { } }, NAME = "apiv6_bugfix", - can_handle = require("apiv6_bugfix.can_handle"), + can_handle = can_handle } return apiv6_bugfix diff --git a/drivers/SmartThings/zwave-sensor/src/configurations.lua b/drivers/SmartThings/zwave-sensor/src/configurations.lua index 0a3c62ead8..2883e70384 100644 --- a/drivers/SmartThings/zwave-sensor/src/configurations.lua +++ b/drivers/SmartThings/zwave-sensor/src/configurations.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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.Configuration diff --git a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua deleted file mode 100644 index a707d7493a..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua +++ /dev/null @@ -1,12 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_enerwave_motion_sensor(opts, driver, device, cmd, ...) - local ENERWAVE_MFR = 0x011A - if device.zwave_manufacturer_id == ENERWAVE_MFR then - local subdriver = require("enerwave-motion-sensor") - return true, subdriver, require("enerwave-motion-sensor") - else return false end -end - -return can_handle_enerwave_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua index 012a8884a3..6fb712e3b0 100644 --- a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -10,6 +20,15 @@ local Association = (require "st.zwave.CommandClass.Association")({version=2}) --- @type st.zwave.CommandClass.WakeUp local WakeUp = (require "st.zwave.CommandClass.WakeUp")({version=1}) +local ENERWAVE_MFR = 0x011A + +local function can_handle_enerwave_motion_sensor(opts, driver, device, cmd, ...) + if device.zwave_manufacturer_id == ENERWAVE_MFR then + local subdriver = require("enerwave-motion-sensor") + return true, subdriver + else return false end +end + local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher --This is done to help the hub correctly set the checkInterval for migrated devices. @@ -39,7 +58,7 @@ local enerwave_motion_sensor = { doConfigure = do_configure }, NAME = "enerwave_motion_sensor", - can_handle = require("enerwave-motion-sensor.can_handle") + can_handle = can_handle_enerwave_motion_sensor } return enerwave_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua deleted file mode 100644 index c9fd2eafd1..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua +++ /dev/null @@ -1,17 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_everspring_motion_light(opts, driver, device, ...) - local EVERSPRING_MOTION_LIGHT_FINGERPRINT = { mfr = 0x0060, prod = 0x0012, model = 0x0001 } - if device:id_match( - EVERSPRING_MOTION_LIGHT_FINGERPRINT.mfr, - EVERSPRING_MOTION_LIGHT_FINGERPRINT.prod, - EVERSPRING_MOTION_LIGHT_FINGERPRINT.model - ) then - local subdriver = require("everspring-motion-light-sensor") - return true, subdriver - end - return false -end - -return can_handle_everspring_motion_light diff --git a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua index 8baa3756d6..1b11aadabe 100644 --- a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua @@ -1,12 +1,34 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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" local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=2,strict=true}) local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({version=2}) +local EVERSPRING_MOTION_LIGHT_FINGERPRINT = { mfr = 0x0060, prod = 0x0012, model = 0x0001 } + +local function can_handle_everspring_motion_light(opts, driver, device, ...) + if device:id_match( + EVERSPRING_MOTION_LIGHT_FINGERPRINT.mfr, + EVERSPRING_MOTION_LIGHT_FINGERPRINT.prod, + EVERSPRING_MOTION_LIGHT_FINGERPRINT.model + ) then + local subdriver = require("everspring-motion-light-sensor") + return true, subdriver + else return false end +end + local function device_added(driver, device) device:emit_event(capabilities.motionSensor.motion.inactive()) device:send(SwitchBinary:Get({})) @@ -18,7 +40,7 @@ local everspring_motion_light = { lifecycle_handlers = { added = device_added }, - can_handle = require("everspring-motion-light-sensor.can_handle"), + can_handle = can_handle_everspring_motion_light } return everspring_motion_light diff --git a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua deleted file mode 100644 index 5596894fbb..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_ezmultipli_multipurpose_sensor(opts, driver, device, ...) - local EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0004, productId = 0x0001 } - if device:id_match(EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.manufacturerId, - EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productType, - EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productId) then - local subdriver = require("ezmultipli-multipurpose-sensor") - return true, subdriver - end - return false -end - -return can_handle_ezmultipli_multipurpose_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua index f4cac30faa..1e4b3bf0ce 100644 --- a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -19,6 +28,17 @@ local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=2}) local CAP_CACHE_KEY = "st.capabilities." .. capabilities.colorControl.ID +local EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0004, productId = 0x0001 } + +local function can_handle_ezmultipli_multipurpose_sensor(opts, driver, device, ...) + if device:id_match(EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.manufacturerId, + EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productType, + EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productId) then + local subdriver = require("ezmultipli-multipurpose-sensor") + return true, subdriver + else return false end +end + local function basic_report_handler(driver, device, cmd) local event local value = (cmd.args.target_value ~= nil) and cmd.args.target_value or cmd.args.value @@ -82,7 +102,7 @@ local ezmultipli_multipurpose_sensor = { [capabilities.colorControl.commands.setColor.NAME] = set_color } }, - can_handle = require("ezmultipli-multipurpose-sensor.can_handle"), + can_handle = can_handle_ezmultipli_multipurpose_sensor } -return ezmultipli_multipurpose_sensor +return ezmultipli_multipurpose_sensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua deleted file mode 100644 index c088fd2b2f..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_fibaro_door_window_sensor(opts, driver, device, ...) - local FINGERPRINTS = require("fibaro-door-window-sensor.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.prod, fingerprint.productId) then - local subdriver = require("fibaro-door-window-sensor") - return true, subdriver, require("fibaro-door-window-sensor") - end - end - return false -end - -return can_handle_fibaro_door_window_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua deleted file mode 100644 index 992ea8fd9d..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_fibaro_door_window_sensor_1(opts, driver, device, cmd, ...) - local FINGERPRINTS = require("fibaro-door-window-sensor.fibaro-door-window-sensor-1.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true, require("fibaro-door-window-sensor.fibaro-door-window-sensor-1") - end - end - return false -end - -return can_handle_fibaro_door_window_sensor_1 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua deleted file mode 100644 index 50727133bb..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua +++ /dev/null @@ -1,8 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS = { - { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } -} - -return FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua index 4dbca58919..698fffcceb 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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" local cc = require "st.zwave.CommandClass" @@ -10,6 +21,19 @@ local SensorAlarm = (require "st.zwave.CommandClass.SensorAlarm")({ version = 1 local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 1 }) local configurationsMap = require "configurations" +local FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS = { + { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } +} + +local function can_handle_fibaro_door_window_sensor_1(opts, driver, device, cmd, ...) + for _, fingerprint in ipairs(FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true + end + end + return false +end + local function sensor_alarm_report_handler(driver, device, cmd) if (cmd.args.sensor_state == SensorAlarm.sensor_state.ALARM) then device:emit_event(capabilities.tamperAlert.tamper.detected()) @@ -68,7 +92,7 @@ local fibaro_door_window_sensor_1 = { [capabilities.refresh.ID] = { [capabilities.refresh.commands.refresh.NAME] = do_refresh }, - can_handle = require("fibaro-door-window-sensor.fibaro-door-window-sensor-1.can_handle"), + can_handle = can_handle_fibaro_door_window_sensor_1 } return fibaro_door_window_sensor_1 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua deleted file mode 100644 index 4493496f94..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_fibaro_door_window_sensor_2(opts, driver, device, cmd, ...) - local FINGERPRINTS = require("fibaro-door-window-sensor.fibaro-door-window-sensor-2.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true, require("fibaro-door-window-sensor.fibaro-door-window-sensor-2") - end - end - return false -end - -return can_handle_fibaro_door_window_sensor_2 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua deleted file mode 100644 index 6103c107d1..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua +++ /dev/null @@ -1,10 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS = { - { manufacturerId = 0x010F, productType = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe - { manufacturerId = 0x010F, productType = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA - { manufacturerId = 0x010F, productType = 0x0702, productId = 0x3000 } -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ -} - -return FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua index 250203b0cd..16c5ec2017 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -7,6 +18,21 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Alarm local Alarm = (require "st.zwave.CommandClass.Alarm")({ version = 2 }) +local FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS = { + { manufacturerId = 0x010F, productType = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe + { manufacturerId = 0x010F, productType = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA + { manufacturerId = 0x010F, productType = 0x0702, productId = 0x3000 } -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ +} + +local function can_handle_fibaro_door_window_sensor_2(opts, driver, device, cmd, ...) + for _, fingerprint in ipairs(FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true + end + end + return false +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) @@ -57,7 +83,7 @@ local fibaro_door_window_sensor_2 = { lifecycle_handlers = { added = device_added }, - can_handle = require("fibaro-door-window-sensor.fibaro-door-window-sensor-2.can_handle"), + can_handle = can_handle_fibaro_door_window_sensor_2, } return fibaro_door_window_sensor_2 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua deleted file mode 100644 index 699df3f623..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS = { - { manufacturerId = 0x010F, prod = 0x0700, productId = 0x1000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / Europe - { manufacturerId = 0x010F, prod = 0x0700, productId = 0x2000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / NA - { manufacturerId = 0x010F, prod = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe - { manufacturerId = 0x010F, prod = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA - { manufacturerId = 0x010F, prod = 0x0702, productId = 0x3000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ - { manufacturerId = 0x010F, prod = 0x0701, productId = 0x2001 }, -- Fibaro Open/Closed Sensor with temperature (FGK-10X) / NA - { manufacturerId = 0x010F, prod = 0x0701, productId = 0x1001 }, -- Fibaro Open/Closed Sensor - { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } -- Fibaro Open/Closed Sensor -} - -return FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua index 6c30508fd1..86cf865348 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 cc = require "st.zwave.CommandClass" local capabilities = require "st.capabilities" @@ -13,6 +24,27 @@ local preferencesMap = require "preferences" local FIBARO_DOOR_WINDOW_SENSOR_WAKEUP_INTERVAL = 21600 --seconds +local FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS = { + { manufacturerId = 0x010F, prod = 0x0700, productId = 0x1000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / Europe + { manufacturerId = 0x010F, prod = 0x0700, productId = 0x2000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / NA + { manufacturerId = 0x010F, prod = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe + { manufacturerId = 0x010F, prod = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA + { manufacturerId = 0x010F, prod = 0x0702, productId = 0x3000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ + { manufacturerId = 0x010F, prod = 0x0701, productId = 0x2001 }, -- Fibaro Open/Closed Sensor with temperature (FGK-10X) / NA + { manufacturerId = 0x010F, prod = 0x0701, productId = 0x1001 }, -- Fibaro Open/Closed Sensor + { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } -- Fibaro Open/Closed Sensor +} + +local function can_handle_fibaro_door_window_sensor(opts, driver, device, ...) + for _, fingerprint in ipairs(FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.prod, fingerprint.productId) then + local subdriver = require("fibaro-door-window-sensor") + return true, subdriver + end + end + return false +end + local function parameterNumberToParameterName(preferences,parameterNumber) for id, parameter in pairs(preferences) do if parameter.parameter_number == parameterNumber then @@ -122,8 +154,11 @@ local fibaro_door_window_sensor = { [capabilities.refresh.commands.refresh.NAME] = do_refresh } }, - sub_drivers = require("fibaro-door-window-sensor.sub_drivers"), - can_handle = require("fibaro-door-window-sensor.can_handle"), + sub_drivers = { + require("fibaro-door-window-sensor/fibaro-door-window-sensor-1"), + require("fibaro-door-window-sensor/fibaro-door-window-sensor-2") + }, + can_handle = can_handle_fibaro_door_window_sensor } return fibaro_door_window_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua deleted file mode 100644 index 0c4ddd4e43..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua +++ /dev/null @@ -1,9 +0,0 @@ --- 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("fibaro-door-window-sensor/fibaro-door-window-sensor-1"), - lazy_load_if_possible("fibaro-door-window-sensor/fibaro-door-window-sensor-2"), -} -return sub_drivers diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua deleted file mode 100644 index 341fbcd6f9..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_fibaro_flood_sensor(opts, driver, device, ...) - local FIBARO_MFR_ID = 0x010F - local FIBARO_FLOOD_PROD_TYPES = { 0x0000, 0x0B00 } - if device:id_match(FIBARO_MFR_ID, FIBARO_FLOOD_PROD_TYPES, nil) then - local subdriver = require("fibaro-flood-sensor") - return true, subdriver - end - return false -end - -return can_handle_fibaro_flood_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua index 9a0a8e7eaa..144be985ae 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -17,6 +26,17 @@ local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = local preferences = require "preferences" local configurations = require "configurations" +local FIBARO_MFR_ID = 0x010F +local FIBARO_FLOOD_PROD_TYPES = { 0x0000, 0x0B00 } + +local function can_handle_fibaro_flood_sensor(opts, driver, device, ...) + if device:id_match(FIBARO_MFR_ID, FIBARO_FLOOD_PROD_TYPES, nil) then + local subdriver = require("fibaro-flood-sensor") + return true, subdriver + else return false end +end + + local function basic_set_handler(self, device, cmd) local value = cmd.args.target_value and cmd.args.target_value or cmd.args.value device:emit_event(value == 0xFF and capabilities.waterSensor.water.wet() or capabilities.waterSensor.water.dry()) @@ -76,7 +96,7 @@ local fibaro_flood_sensor = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = require("fibaro-flood-sensor.can_handle"), + can_handle = can_handle_fibaro_flood_sensor } return fibaro_flood_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua deleted file mode 100644 index cd3f647394..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_fibaro_motion_sensor(opts, driver, device, ...) - - local FIBARO_MOTION_MFR = 0x010F - local FIBARO_MOTION_PROD = 0x0800 - if device:id_match(FIBARO_MOTION_MFR, FIBARO_MOTION_PROD) then - local subdriver = require("fibaro-motion-sensor") - return true, subdriver, require("fibaro-motion-sensor") - end - return false -end - -return can_handle_fibaro_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua index ed035bde18..ae45f5a27b 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -8,6 +18,15 @@ local cc = require "st.zwave.CommandClass" local SensorAlarm = (require "st.zwave.CommandClass.SensorAlarm")({ version = 1 }) local capabilities = require "st.capabilities" +local FIBARO_MOTION_MFR = 0x010F +local FIBARO_MOTION_PROD = 0x0800 + +local function can_handle_fibaro_motion_sensor(opts, driver, device, ...) + if device:id_match(FIBARO_MOTION_MFR, FIBARO_MOTION_PROD) then + local subdriver = require("fibaro-motion-sensor") + return true, subdriver + else return false end +end local function sensor_alarm_report(driver, device, cmd) if (cmd.args.sensor_state ~= SensorAlarm.sensor_state.NO_ALARM) then @@ -24,7 +43,7 @@ local fibaro_motion_sensor = { [SensorAlarm.REPORT] = sensor_alarm_report } }, - can_handle = require("fibaro-motion-sensor.can_handle") + can_handle = can_handle_fibaro_motion_sensor } -return fibaro_motion_sensor +return fibaro_motion_sensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua deleted file mode 100644 index 3ecdc2baf0..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua +++ /dev/null @@ -1,21 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local capabilities = require "st.capabilities" - ---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 -} - -return function(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 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua b/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua index 528cf7da45..058a7f955c 100644 --- a/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua @@ -1,6 +1,16 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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" --- @type st.zwave.CommandClass @@ -10,6 +20,22 @@ 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) @@ -47,7 +73,7 @@ end local firmware_version = { NAME = "firmware_version", - can_handle = require("firmware-version.can_handle"), + can_handle = can_handle_fw, lifecycle_handlers = { added = added_handler, diff --git a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua deleted file mode 100644 index e24d7b9cf2..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua +++ /dev/null @@ -1,20 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - ---- Determine whether the passed device is glentronics water leak sensor ---- ---- @param driver Driver driver instance ---- @param device Device device isntance ---- @return boolean true if the device proper, else false -local function can_handle_glentronics_water_leak_sensor(opts, driver, device, ...) - local GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS = { manufacturerId = 0x0084, productType = 0x0093, productId = 0x0114 } -- glentronics water leak sensor - if device:id_match( - GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.manufacturerId, - GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productType, - GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productId) then - return true, require("glentronics-water-leak-sensor") - end - return false -end - -return can_handle_glentronics_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua index 7400d889b7..3dba7351d6 100644 --- a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -9,6 +18,23 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +local GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS = { manufacturerId = 0x0084, productType = 0x0093, productId = 0x0114 } -- glentronics water leak sensor + +--- Determine whether the passed device is glentronics water leak sensor +--- +--- @param driver Driver driver instance +--- @param device Device device isntance +--- @return boolean true if the device proper, else false +local function can_handle_glentronics_water_leak_sensor(opts, driver, device, ...) + if device:id_match( + GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.manufacturerId, + GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productType, + GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productId) then + local subdriver = require("glentronics-water-leak-sensor") + return true, subdriver + else return false end +end + local function notification_report_handler(self, device, cmd) local event if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then @@ -52,7 +78,7 @@ local glentronics_water_leak_sensor = { added = device_added }, NAME = "glentronics water leak sensor", - can_handle = require("glentronics-water-leak-sensor.can_handle"), + can_handle = can_handle_glentronics_water_leak_sensor } return glentronics_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua deleted file mode 100644 index 992c1f7c7f..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua +++ /dev/null @@ -1,20 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - ---- Determine whether the passed device is homeseer multi sensor ---- ---- @param driver Driver driver instance ---- @param device Device device instance ---- @return boolean true if the device proper, else false -local function can_handle_homeseer_multi_sensor(opts, driver, device, ...) - local HOMESEER_MULTI_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0002, productId = 0x0001 } -- Homeseer multi sensor HSM100 - if device:id_match( - HOMESEER_MULTI_SENSOR_FINGERPRINTS.manufacturerId, - HOMESEER_MULTI_SENSOR_FINGERPRINTS.productType, - HOMESEER_MULTI_SENSOR_FINGERPRINTS.productId) then - return true, require("homeseer-multi-sensor") - end - return false -end - -return can_handle_homeseer_multi_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua index f89e6a7870..2330f28106 100644 --- a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -13,6 +22,23 @@ local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({version = 5}) local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1}) +local HOMESEER_MULTI_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0002, productId = 0x0001 } -- Homeseer multi sensor HSM100 + +--- Determine whether the passed device is homeseer multi sensor +--- +--- @param driver Driver driver instance +--- @param device Device device instance +--- @return boolean true if the device proper, else false +local function can_handle_homeseer_multi_sensor(opts, driver, device, ...) + if device:id_match( + HOMESEER_MULTI_SENSOR_FINGERPRINTS.manufacturerId, + HOMESEER_MULTI_SENSOR_FINGERPRINTS.productType, + HOMESEER_MULTI_SENSOR_FINGERPRINTS.productId) then + local subdriver = require("homeseer-multi-sensor") + return true, subdriver + else return false end +end + local function basic_set_handler(self, device, cmd) if cmd.args.value ~= nil then device:emit_event(cmd.args.value == 0xFF and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) @@ -61,7 +87,7 @@ local homeseer_multi_sensor = { init = device_init, }, NAME = "homeseer multi sensor", - can_handle = require("homeseer-multi-sensor.can_handle"), + can_handle = can_handle_homeseer_multi_sensor } return homeseer_multi_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/init.lua b/drivers/SmartThings/zwave-sensor/src/init.lua index 2c18e4c2ed..213aa8c389 100644 --- a/drivers/SmartThings/zwave-sensor/src/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/init.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -16,6 +27,19 @@ local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) local preferences = require "preferences" local configurations = require "configurations" +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 + --- Handle preference changes --- --- @param driver st.zwave.Driver @@ -110,7 +134,27 @@ local driver_template = { capabilities.powerMeter, capabilities.smokeDetector }, - sub_drivers = require("sub_drivers"), + sub_drivers = { + lazy_load_if_possible("zooz-4-in-1-sensor"), + lazy_load_if_possible("vision-motion-detector"), + lazy_load_if_possible("fibaro-flood-sensor"), + lazy_load_if_possible("aeotec-water-sensor"), + lazy_load_if_possible("glentronics-water-leak-sensor"), + lazy_load_if_possible("homeseer-multi-sensor"), + lazy_load_if_possible("fibaro-door-window-sensor"), + lazy_load_if_possible("sensative-strip"), + lazy_load_if_possible("enerwave-motion-sensor"), + lazy_load_if_possible("aeotec-multisensor"), + lazy_load_if_possible("zwave-water-leak-sensor"), + lazy_load_if_possible("everspring-motion-light-sensor"), + lazy_load_if_possible("ezmultipli-multipurpose-sensor"), + lazy_load_if_possible("fibaro-motion-sensor"), + 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"), + }, lifecycle_handlers = { added = added_handler, init = device_init, diff --git a/drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua deleted file mode 100644 index 45115081e4..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua +++ /dev/null @@ -1,18 +0,0 @@ --- 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-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index 70293b10fa..9585b6ffe9 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/src/preferences.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) diff --git a/drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua deleted file mode 100644 index e639c32c94..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_sensative_strip(opts, driver, device, cmd, ...) - local SENSATIVE_MFR = 0x019A - local SENSATIVE_MODEL = 0x000A - if device:id_match(SENSATIVE_MFR, nil, SENSATIVE_MODEL) then - local subdriver = require("sensative-strip") - return true, subdriver, require("sensative-strip") - end - return false -end - -return can_handle_sensative_strip diff --git a/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua b/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua index 7fd9fb2258..73f1cb8459 100644 --- a/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -9,11 +19,20 @@ local Configuration = (require "st.zwave.CommandClass.Configuration")({ version --- @type st.zwave.CommandClass.WakeUp local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) +local SENSATIVE_MFR = 0x019A +local SENSATIVE_MODEL = 0x000A local LEAKAGE_ALARM_PARAM = 12 local LEAKAGE_ALARM_OFF = 0 local SENSATIVE_COMFORT_PROFILE = "illuminance-temperature" local CONFIG_REPORT_RECEIVED = "configReportReceived" +local function can_handle_sensative_strip(opts, driver, device, cmd, ...) + if device:id_match(SENSATIVE_MFR, nil, SENSATIVE_MODEL) then + local subdriver = require("sensative-strip") + return true, subdriver + else return false end +end + local function configuration_report(driver, device, cmd) local parameter_number = cmd.args.parameter_number local configuration_value = cmd.args.configuration_value @@ -56,7 +75,7 @@ local sensative_strip = { doConfigure = do_configure }, NAME = "sensative_strip", - can_handle = require("sensative-strip.can_handle") + can_handle = can_handle_sensative_strip } return sensative_strip diff --git a/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua deleted file mode 100644 index 9504479304..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua +++ /dev/null @@ -1,26 +0,0 @@ --- 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("zooz-4-in-1-sensor"), - lazy_load_if_possible("vision-motion-detector"), - lazy_load_if_possible("fibaro-flood-sensor"), - lazy_load_if_possible("aeotec-water-sensor"), - lazy_load_if_possible("glentronics-water-leak-sensor"), - lazy_load_if_possible("homeseer-multi-sensor"), - lazy_load_if_possible("fibaro-door-window-sensor"), - lazy_load_if_possible("sensative-strip"), - lazy_load_if_possible("enerwave-motion-sensor"), - lazy_load_if_possible("aeotec-multisensor"), - lazy_load_if_possible("zwave-water-leak-sensor"), - lazy_load_if_possible("everspring-motion-light-sensor"), - lazy_load_if_possible("ezmultipli-multipurpose-sensor"), - lazy_load_if_possible("fibaro-motion-sensor"), - 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"), -} diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua index cfdb08f89d..2d83fd6e25 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua index e78e63e214..02c2adf751 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua index 5bbd1b4f0f..7fd57e42b2 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua index 57584e779c..3b73ff17c3 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua index e58badc224..8af011f51f 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua index eb951cef9b..a5f44ff12e 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua index bf2d800817..a90655669d 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua index 87d87b75b5..45e0672c11 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua index c6192bcddf..1cb2735a31 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua index c576543902..097016fc81 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua index f6db164cfe..04103db0db 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua index 6e04140010..17a627a736 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua index 2e0ead247a..74b6d6196c 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua index d3f51b0bb5..248c22ab60 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua index d597b72bdc..6904f318a9 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua index 2bbe391a62..bc78bd02ca 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua index 0fd2d2c8df..2b27d66868 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua index 98edcbf9f6..7a9743a69e 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua index 0ad90d3479..45cde29b8f 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua index 6d1f459361..24a7f31eaf 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua index 4b322b315d..e514a2f2e5 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua index 950da6f412..e6fff57b0d 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua index 5ba9ee537e..747c4cbce1 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua index 52a273f0b7..f57bfe7950 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua b/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua index 65f30fb7a8..9818f1422c 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua @@ -1,6 +1,16 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - +-- Copyright 2023 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. local test = require "integration_test" local capabilities = require "st.capabilities" @@ -103,4 +113,4 @@ test.register_message_test( } ) -test.run_registered_tests() +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua b/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua index a5be01cb81..873909df23 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua index 1b89db9f9f..2321b5bd09 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua b/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua index b4de025737..866508e3d6 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua @@ -1,6 +1,16 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - +-- Copyright 2023 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua b/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua index 1cf3da742a..e1b82a6b38 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua index c0cc183c41..1bef24c5d6 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua index 4b149c9be7..4ff11090c9 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua index 2256522888..d918f47179 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua index 3719a31f1c..a390c35db9 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua index 2a8eea8eab..e6caf1f07c 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua deleted file mode 100644 index c05cbdcf7d..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua +++ /dev/null @@ -1,23 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_tamper_event(opts, driver, device, cmd, ...) - local cc = require "st.zwave.CommandClass" - local Notification = (require "st.zwave.CommandClass.Notification")({ version = 4 }) - local FIBARO_DOOR_WINDOW_MFR_ID = 0x010F - - if device.zwave_manufacturer_id ~= FIBARO_DOOR_WINDOW_MFR_ID and - 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 - return true, require("timed-tamper-clear") - end - return false -end - -return can_handle_tamper_event 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 c2420791b5..2007bedb0d 100644 --- a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua @@ -1,7 +1,16 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - - +-- 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. --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -11,6 +20,23 @@ 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 + 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 + end +end -- This behavior is from zwave-door-window-sensor.groovy. We've seen this behavior -- in Ecolink and several other z-wave sensors that do not send tamper clear events @@ -34,7 +60,7 @@ local timed_tamper_clear = { } }, NAME = "timed tamper clear", - can_handle = require("timed-tamper-clear.can_handle"), + can_handle = can_handle_tamper_event } return timed_tamper_clear diff --git a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua deleted file mode 100644 index 2fa45d8015..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua +++ /dev/null @@ -1,22 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_v1_contact_event(opts, driver, device, cmd, ...) - local cc = require "st.zwave.CommandClass" - local Notification = (require "st.zwave.CommandClass.Notification")({ version = 4 }) - - if 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.v1_alarm_type == 0x07 then - local subdriver = require("v1-contact-event") - return true, subdriver, require("v1-contact-event") - else - return false - end -end - -return can_handle_v1_contact_event diff --git a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua index 887ac32bf0..40efc7633e 100644 --- a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua @@ -1,5 +1,16 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 +-- 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. --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -7,6 +18,20 @@ local cc = require "st.zwave.CommandClass" local Notification = (require "st.zwave.CommandClass.Notification")({ version = 4 }) local capabilities = require "st.capabilities" +local function can_handle_v1_contact_event(opts, driver, device, cmd, ...) + if 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.v1_alarm_type == 0x07 then + local subdriver = require("v1-contact-event") + return true, subdriver + else + return false + end +end -- This behavior is from zwave-door-window-sensor.groovy, where it is -- indicated that certain monoprice sensors had this behavior. Also, @@ -28,7 +53,7 @@ local v1_contact_event = { } }, NAME = "v1 contact event", - can_handle = require("v1-contact-event.can_handle"), + can_handle = can_handle_v1_contact_event } return v1_contact_event diff --git a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua deleted file mode 100644 index d270a7954d..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua +++ /dev/null @@ -1,17 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - ---- Determine whether the passed device is zwave-plus-motion-temp-sensor -local function can_handle_vision_motion_detector(opts, driver, device, ...) - local VISION_MOTION_DETECTOR_FINGERPRINTS = { manufacturerId = 0x0109, productType = 0x2002, productId = 0x0205 } -- Vision Motion Detector ZP3102 - if device:id_match( - VISION_MOTION_DETECTOR_FINGERPRINTS.manufacturerId, - VISION_MOTION_DETECTOR_FINGERPRINTS.productType, - VISION_MOTION_DETECTOR_FINGERPRINTS.productId - ) then - return true, require("vision-motion-detector") - end - return false -end - -return can_handle_vision_motion_detector diff --git a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua index 72934362c5..320bf3824f 100644 --- a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -13,6 +22,20 @@ local Configuration = (require "st.zwave.CommandClass.Configuration")({ version --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +local VISION_MOTION_DETECTOR_FINGERPRINTS = { manufacturerId = 0x0109, productType = 0x2002, productId = 0x0205 } -- Vision Motion Detector ZP3102 + +--- Determine whether the passed device is zwave-plus-motion-temp-sensor +local function can_handle_vision_motion_detector(opts, driver, device, ...) + if device:id_match( + VISION_MOTION_DETECTOR_FINGERPRINTS.manufacturerId, + VISION_MOTION_DETECTOR_FINGERPRINTS.productType, + VISION_MOTION_DETECTOR_FINGERPRINTS.productId + ) then + local subdriver = require("vision-motion-detector") + return true, subdriver + else return false end +end + --- Handler for notification report command class from sensor --- --- @param self st.zwave.Driver @@ -60,7 +83,7 @@ local vision_motion_detector = { doConfigure = do_configure, }, NAME = "Vision motion detector", - can_handle = require("vision-motion-detector.can_handle"), + can_handle = can_handle_vision_motion_detector } return vision_motion_detector diff --git a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua deleted file mode 100644 index 15ac66d439..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua +++ /dev/null @@ -1,12 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle(opts, driver, device, ...) - local fingerprint = {manufacturerId = 0x014F, productType = 0x2001, productId = 0x0102} -- NorTek open/close sensor - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true, require("wakeup-no-poll") - end - return false -end - -return can_handle diff --git a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua index 1270cd4557..59d298a0e4 100644 --- a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua @@ -1,7 +1,16 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - - +-- Copyright 2023 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -12,6 +21,17 @@ local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({version = 2 --- @type st.zwave.CommandClass.Battery local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +local fingerprint = {manufacturerId = 0x014F, productType = 0x2001, productId = 0x0102} -- NorTek open/close sensor + +local function can_handle(opts, driver, device, ...) + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("wakeup-no-poll") + return true, subdriver + else + return false + end +end + -- Nortek open/closed sensors _always_ respond with "open" when polled, and they are polled after wakeup local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher @@ -33,7 +53,7 @@ local wakeup_no_poll = { [WakeUp.NOTIFICATION] = wakeup_notification } }, - can_handle = require("wakeup-no-poll.can_handle"), + can_handle = can_handle } -return wakeup_no_poll +return wakeup_no_poll \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua deleted file mode 100644 index 17c80b70fb..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_zooz_4_in_1_sensor(opts, driver, device, ...) - local FINGERPRINTS = require("zooz-4-in-1-sensor.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("zooz-4-in-1-sensor") - return true, subdriver, require("zooz-4-in-1-sensor") - end - end - return false -end - -return can_handle_zooz_4_in_1_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua deleted file mode 100644 index 12d853b147..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua +++ /dev/null @@ -1,10 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local ZOOZ_4_IN_1_FINGERPRINTS = { - { manufacturerId = 0x027A, productType = 0x2021, productId = 0x2101 }, -- Zooz 4-in-1 sensor - { manufacturerId = 0x0109, productType = 0x2021, productId = 0x2101 }, -- ZP3111US 4-in-1 Motion - { manufacturerId = 0x0060, productType = 0x0001, productId = 0x0004 } -- Everspring Immune Pet PIR Sensor SP815 -} - -return ZOOZ_4_IN_1_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua index 7321c3f9b4..5d4570e525 100644 --- a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -13,8 +22,22 @@ local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ ve --- @type st.utils local utils = require "st.utils" +local ZOOZ_4_IN_1_FINGERPRINTS = { + { manufacturerId = 0x027A, productType = 0x2021, productId = 0x2101 }, -- Zooz 4-in-1 sensor + { manufacturerId = 0x0109, productType = 0x2021, productId = 0x2101 }, -- ZP3111US 4-in-1 Motion + { manufacturerId = 0x0060, productType = 0x0001, productId = 0x0004 } -- Everspring Immune Pet PIR Sensor SP815 +} --- Determine whether the passed device is zooz_4_in_1_sensor +local function can_handle_zooz_4_in_1_sensor(opts, driver, device, ...) + for _, fingerprint in ipairs(ZOOZ_4_IN_1_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("zooz-4-in-1-sensor") + return true, subdriver + end + end + return false +end --- Handler for notification report command class --- @@ -86,7 +109,7 @@ local zooz_4_in_1_sensor = { } }, NAME = "zooz 4 in 1 sensor", - can_handle = require("zooz-4-in-1-sensor.can_handle"), + can_handle = can_handle_zooz_4_in_1_sensor } return zooz_4_in_1_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua deleted file mode 100644 index ad7e736a7c..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_water_leak_sensor(opts, driver, device, ...) - local FINGERPRINTS = require("zwave-water-leak-sensor.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("zwave-water-leak-sensor") - return true, subdriver, require("zwave-water-leak-sensor") - end - end - return false -end - -return can_handle_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua deleted file mode 100644 index c07bdb52cb..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua +++ /dev/null @@ -1,19 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local WATER_LEAK_SENSOR_FINGERPRINTS = { - {mfr = 0x0084, prod = 0x0063, model = 0x010C}, -- SmartThings Water Leak Sensor - {mfr = 0x0084, prod = 0x0053, model = 0x0216}, -- FortrezZ Water Leak Sensor - {mfr = 0x021F, prod = 0x0003, model = 0x0085}, -- Dome Leak Sensor - {mfr = 0x0258, prod = 0x0003, model = 0x0085}, -- NEO Coolcam Water Sensor - {mfr = 0x0258, prod = 0x0003, model = 0x1085}, -- NEO Coolcam Water Sensor - {mfr = 0x0258, prod = 0x0003, model = 0x2085}, -- NEO Coolcam Water Sensor - {mfr = 0x0086, prod = 0x0002, model = 0x007A}, -- Aeotec Water Sensor 6 (EU) - {mfr = 0x0086, prod = 0x0102, model = 0x007A}, -- Aeotec Water Sensor 6 (US) - {mfr = 0x0086, prod = 0x0202, model = 0x007A}, -- Aeotec Water Sensor 6 (AU) - {mfr = 0x000C, prod = 0x0201, model = 0x000A}, -- HomeSeer LS100+ Water Sensor - {mfr = 0x0173, prod = 0x4C47, model = 0x4C44}, -- Leak Gopher Z-Wave Leak Detector - {mfr = 0x027A, prod = 0x7000, model = 0xE002} -- Zooz ZSE42 XS Water Leak Sensor -} - -return WATER_LEAK_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua index 4575de352a..1eefab7479 100644 --- a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua @@ -1,10 +1,47 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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" local cc = require "st.zwave.CommandClass" local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) + +local WATER_LEAK_SENSOR_FINGERPRINTS = { + {mfr = 0x0084, prod = 0x0063, model = 0x010C}, -- SmartThings Water Leak Sensor + {mfr = 0x0084, prod = 0x0053, model = 0x0216}, -- FortrezZ Water Leak Sensor + {mfr = 0x021F, prod = 0x0003, model = 0x0085}, -- Dome Leak Sensor + {mfr = 0x0258, prod = 0x0003, model = 0x0085}, -- NEO Coolcam Water Sensor + {mfr = 0x0258, prod = 0x0003, model = 0x1085}, -- NEO Coolcam Water Sensor + {mfr = 0x0258, prod = 0x0003, model = 0x2085}, -- NEO Coolcam Water Sensor + {mfr = 0x0086, prod = 0x0002, model = 0x007A}, -- Aeotec Water Sensor 6 (EU) + {mfr = 0x0086, prod = 0x0102, model = 0x007A}, -- Aeotec Water Sensor 6 (US) + {mfr = 0x0086, prod = 0x0202, model = 0x007A}, -- Aeotec Water Sensor 6 (AU) + {mfr = 0x000C, prod = 0x0201, model = 0x000A}, -- HomeSeer LS100+ Water Sensor + {mfr = 0x0173, prod = 0x4C47, model = 0x4C44}, -- Leak Gopher Z-Wave Leak Detector + {mfr = 0x027A, prod = 0x7000, model = 0xE002} -- Zooz ZSE42 XS Water Leak Sensor +} + +local function can_handle_water_leak_sensor(opts, driver, device, ...) + for _, fingerprint in ipairs(WATER_LEAK_SENSOR_FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("zwave-water-leak-sensor") + return true, subdriver + 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 device:emit_event(value == 0xFF and capabilities.waterSensor.water.wet() or capabilities.waterSensor.water.dry()) @@ -17,7 +54,7 @@ local water_leak_sensor = { [Basic.SET] = basic_set_handler } }, - can_handle = require("zwave-water-leak-sensor.can_handle"), + can_handle = can_handle_water_leak_sensor } return water_leak_sensor From 9ef110a99d8ae276814d07a67120d0459e8de6c8 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 23 Feb 2026 11:56:04 -0600 Subject: [PATCH 127/538] Revert "CHAD-17092: zwave-sensor lazy loading of sub-drivers" This reverts commit 9a714175757dea23539df997566506121325e578. --- .../src/aeotec-multisensor/can_handle.lua | 15 ------ .../src/aeotec-multisensor/fingerprints.lua | 9 ---- .../src/aeotec-multisensor/init.lua | 37 ++++++++++++-- .../multisensor-6/can_handle.lua | 11 ---- .../aeotec-multisensor/multisensor-6/init.lua | 23 +++++++-- .../multisensor-7/can_handle.lua | 12 ----- .../aeotec-multisensor/multisensor-7/init.lua | 24 +++++++-- .../src/aeotec-multisensor/sub_drivers.lua | 9 ---- .../src/aeotec-water-sensor/can_handle.lua | 15 ------ .../src/aeotec-water-sensor/fingerprints.lua | 11 ---- .../src/aeotec-water-sensor/init.lua | 34 +++++++++++-- .../src/apiv6_bugfix/can_handle.lua | 35 ------------- .../zwave-sensor/src/apiv6_bugfix/init.lua | 33 ++++++++++-- .../zwave-sensor/src/configurations.lua | 16 ++++-- .../src/enerwave-motion-sensor/can_handle.lua | 12 ----- .../src/enerwave-motion-sensor/init.lua | 27 ++++++++-- .../can_handle.lua | 17 ------- .../everspring-motion-light-sensor/init.lua | 32 ++++++++++-- .../can_handle.lua | 15 ------ .../ezmultipli-multipurpose-sensor/init.lua | 32 +++++++++--- .../fibaro-door-window-sensor/can_handle.lua | 15 ------ .../can_handle.lua | 14 ------ .../fingerprints.lua | 8 --- .../fibaro-door-window-sensor-1/init.lua | 30 +++++++++-- .../can_handle.lua | 14 ------ .../fingerprints.lua | 10 ---- .../fibaro-door-window-sensor-2/init.lua | 32 ++++++++++-- .../fingerprints.lua | 15 ------ .../src/fibaro-door-window-sensor/init.lua | 43 ++++++++++++++-- .../fibaro-door-window-sensor/sub_drivers.lua | 9 ---- .../src/fibaro-flood-sensor/can_handle.lua | 14 ------ .../src/fibaro-flood-sensor/init.lua | 30 +++++++++-- .../src/fibaro-motion-sensor/can_handle.lua | 15 ------ .../src/fibaro-motion-sensor/init.lua | 29 +++++++++-- .../src/firmware-version/can_handle.lua | 21 -------- .../src/firmware-version/init.lua | 34 +++++++++++-- .../can_handle.lua | 20 -------- .../glentronics-water-leak-sensor/init.lua | 36 +++++++++++-- .../src/homeseer-multi-sensor/can_handle.lua | 20 -------- .../src/homeseer-multi-sensor/init.lua | 36 +++++++++++-- drivers/SmartThings/zwave-sensor/src/init.lua | 50 +++++++++++++++++-- .../zwave-sensor/src/lazy_load_subdriver.lua | 18 ------- .../zwave-sensor/src/preferences.lua | 16 ++++-- .../src/sensative-strip/can_handle.lua | 14 ------ .../zwave-sensor/src/sensative-strip/init.lua | 27 ++++++++-- .../zwave-sensor/src/sub_drivers.lua | 26 ---------- .../src/test/test_aeon_multisensor.lua | 16 ++++-- .../src/test/test_aeotec_multisensor_6.lua | 16 ++++-- .../src/test/test_aeotec_multisensor_7.lua | 16 ++++-- .../src/test/test_aeotec_multisensor_gen5.lua | 16 ++++-- .../src/test/test_aeotec_water_sensor.lua | 16 ++++-- .../src/test/test_aeotec_water_sensor_7.lua | 16 ++++-- .../src/test/test_enerwave_motion_sensor.lua | 16 ++++-- .../src/test/test_everpsring_sp817.lua | 16 ++++-- .../src/test/test_everspring_PIR_sensor.lua | 16 ++++-- .../src/test/test_everspring_ST814.lua | 16 ++++-- .../test_everspring_illuminance_sensor.lua | 16 ++++-- .../test_everspring_motion_light_sensor.lua | 16 ++++-- .../test_ezmultipli_multipurpose_sensor.lua | 16 ++++-- .../test/test_fibaro_door_window_sensor.lua | 16 ++++-- .../test/test_fibaro_door_window_sensor_1.lua | 16 ++++-- .../test/test_fibaro_door_window_sensor_2.lua | 16 ++++-- ...ro_door_window_sensor_with_temperature.lua | 16 ++++-- .../src/test/test_fibaro_flood_sensor.lua | 16 ++++-- .../src/test/test_fibaro_flood_sensor_zw5.lua | 16 ++++-- .../src/test/test_fibaro_motion_sensor.lua | 16 ++++-- .../test/test_fibaro_motion_sensor_zw5.lua | 16 ++++-- .../src/test/test_generic_sensor.lua | 16 ++++-- .../test_glentronics_water_leak_sensor.lua | 16 ++++-- .../src/test/test_homeseer_multi_sensor.lua | 16 ++++-- .../src/test/test_no_wakeup_poll.lua | 18 +++++-- .../src/test/test_sensative_strip.lua | 16 ++++-- .../test_smartthings_water_leak_sensor.lua | 16 ++++-- .../src/test/test_v1_contact_event.lua | 16 ++++-- .../src/test/test_vision_motion_detector.lua | 16 ++++-- .../src/test/test_zooz_4_in_1_sensor.lua | 16 ++++-- .../test/test_zwave_motion_light_sensor.lua | 16 ++++-- .../test_zwave_motion_temp_light_sensor.lua | 16 ++++-- .../src/test/test_zwave_sensor.lua | 16 ++++-- .../src/test/test_zwave_water_sensor.lua | 16 ++++-- .../src/timed-tamper-clear/can_handle.lua | 23 --------- .../src/timed-tamper-clear/init.lua | 36 +++++++++++-- .../src/v1-contact-event/can_handle.lua | 22 -------- .../src/v1-contact-event/init.lua | 31 ++++++++++-- .../src/vision-motion-detector/can_handle.lua | 17 ------- .../src/vision-motion-detector/init.lua | 33 ++++++++++-- .../src/wakeup-no-poll/can_handle.lua | 12 ----- .../zwave-sensor/src/wakeup-no-poll/init.lua | 32 +++++++++--- .../src/zooz-4-in-1-sensor/can_handle.lua | 15 ------ .../src/zooz-4-in-1-sensor/fingerprints.lua | 10 ---- .../src/zooz-4-in-1-sensor/init.lua | 33 ++++++++++-- .../zwave-water-leak-sensor/can_handle.lua | 15 ------ .../zwave-water-leak-sensor/fingerprints.lua | 19 ------- .../src/zwave-water-leak-sensor/init.lua | 43 ++++++++++++++-- 94 files changed, 1160 insertions(+), 742 deletions(-) delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/sub_drivers.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua deleted file mode 100644 index 99cea3c0b3..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_aeotec_multisensor(opts, self, device, ...) - local FINGERPRINTS = require("aeotec-multisensor.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("aeotec-multisensor") - return true, subdriver, require("aeotec-multisensor") - end - end - return false -end - -return can_handle_aeotec_multisensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua deleted file mode 100644 index 9436a85979..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua +++ /dev/null @@ -1,9 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local AEOTEC_MULTISENSOR_FINGERPRINTS = { - { manufacturerId = 0x0086, productId = 0x0064 }, -- MultiSensor 6 - { manufacturerId = 0x0371, productId = 0x0018 }, -- MultiSensor 7 -} - -return AEOTEC_MULTISENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua index d1759a41e0..edd01c7553 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -7,6 +18,21 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +local AEOTEC_MULTISENSOR_FINGERPRINTS = { + { manufacturerId = 0x0086, productId = 0x0064 }, -- MultiSensor 6 + { manufacturerId = 0x0371, productId = 0x0018 }, -- MultiSensor 7 +} + +local function can_handle_aeotec_multisensor(opts, self, device, ...) + for _, fingerprint in ipairs(AEOTEC_MULTISENSOR_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("aeotec-multisensor") + return true, subdriver + end + end + return false +end + local function notification_report_handler(self, device, cmd) local event if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then @@ -35,9 +61,12 @@ local aeotec_multisensor = { [Notification.REPORT] = notification_report_handler } }, - sub_drivers = require("aeotec-multisensor.sub_drivers"), + sub_drivers = { + require("aeotec-multisensor/multisensor-6"), + require("aeotec-multisensor/multisensor-7") + }, NAME = "aeotec multisensor", - can_handle = require("aeotec-multisensor.can_handle"), + can_handle = can_handle_aeotec_multisensor } return aeotec_multisensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua deleted file mode 100644 index d86e9c8b3a..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_multisensor_6(opts, self, device, ...) -local MULTISENSOR_6_PRODUCT_ID = 0x0064 - if device.zwave_product_id == MULTISENSOR_6_PRODUCT_ID then - return true, require("aeotec-multisensor.multisensor-6") - end - return false -end -return can_handle_multisensor_6 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua index 4174b3b14e..1b9d4d6b97 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -10,8 +19,12 @@ local cc = require "st.zwave.CommandClass" local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) local WakeUp = (require "st.zwave.CommandClass.WakeUp")({version = 2}) +local MULTISENSOR_6_PRODUCT_ID = 0x0064 local PREFERENCE_NUM = 9 +local function can_handle_multisensor_6(opts, self, device, ...) + return device.zwave_product_id == MULTISENSOR_6_PRODUCT_ID +end local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher @@ -49,7 +62,7 @@ local multisensor_6 = { } }, NAME = "aeotec multisensor 6", - can_handle = require("aeotec-multisensor.multisensor-6.can_handle"), + can_handle = can_handle_multisensor_6 } return multisensor_6 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua deleted file mode 100644 index f109d0e31c..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua +++ /dev/null @@ -1,12 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_multisensor_7(opts, self, device, ...) - local MULTISENSOR_7_PRODUCT_ID = 0x0018 - if device.zwave_product_id == MULTISENSOR_7_PRODUCT_ID then - return true, require("aeotec-multisensor.multisensor-7") - end - return false -end - -return can_handle_multisensor_7 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua index c3dc69178f..2d2bf4e36e 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -10,8 +19,13 @@ local cc = require "st.zwave.CommandClass" local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 2 }) +local MULTISENSOR_7_PRODUCT_ID = 0x0018 local PREFERENCE_NUM = 10 +local function can_handle_multisensor_7(opts, self, device, ...) + return device.zwave_product_id == MULTISENSOR_7_PRODUCT_ID +end + local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher --This is done to help the hub correctly set the checkInterval for migrated devices. @@ -48,7 +62,7 @@ local multisensor_7 = { } }, NAME = "aeotec multisensor 7", - can_handle = require("aeotec-multisensor.multisensor-7.can_handle"), + can_handle = can_handle_multisensor_7 } return multisensor_7 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua deleted file mode 100644 index 396f53fe86..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua +++ /dev/null @@ -1,9 +0,0 @@ --- 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-multisensor/multisensor-6"), - lazy_load_if_possible("aeotec-multisensor/multisensor-7"), -} -return sub_drivers diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua deleted file mode 100644 index 1bdf9f12a8..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_zwave_water_temp_humidity_sensor(opts, driver, device, ...) - local FINGERPRINTS = require("aeotec-water-sensor.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("aeotec-water-sensor") - return true, subdriver, require("aeotec-water-sensor") - end - end - return false -end - -return can_handle_zwave_water_temp_humidity_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua deleted file mode 100644 index 423d87754e..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS = { - { manufacturerId = 0x0371, productType = 0x0002, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro EU - { manufacturerId = 0x0371, productType = 0x0102, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro US - { manufacturerId = 0x0371, productType = 0x0202, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro AU - { manufacturerId = 0x0371, productId = 0x0012 } -- Aeotec Water Sensor 7 -} - -return ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua index 4c7a86e708..9d883ea3c2 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -9,8 +18,23 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +local ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS = { + { manufacturerId = 0x0371, productType = 0x0002, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro EU + { manufacturerId = 0x0371, productType = 0x0102, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro US + { manufacturerId = 0x0371, productType = 0x0202, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro AU + { manufacturerId = 0x0371, productId = 0x0012 } -- Aeotec Water Sensor 7 +} --- Determine whether the passed device is zwave water temperature humidiry sensor +local function can_handle_zwave_water_temp_humidity_sensor(opts, driver, device, ...) + for _, fingerprint in ipairs(ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("aeotec-water-sensor") + return true, subdriver + end + end + return false +end --- Default handler for notification command class reports --- @@ -44,7 +68,7 @@ local zwave_water_temp_humidity_sensor = { }, }, NAME = "zwave water temp humidity sensor", - can_handle = require("aeotec-water-sensor.can_handle"), + can_handle = can_handle_zwave_water_temp_humidity_sensor } return zwave_water_temp_humidity_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua deleted file mode 100644 index 4913e9a25e..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua +++ /dev/null @@ -1,35 +0,0 @@ --- 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 }) - --- doing refresh would cause incorrect state for device, see comments in wakeup-no-poll -local NORTEK_FP = {mfr = 0x014F, prod = 0x2001, model = 0x0102} -- NorTek open/close sensor -local POPP_THERMOSTAT_FP = {mfr = 0x0002, prod = 0x0115, model = 0xA010} --Popp thermostat -local AEOTEC_MULTISENSOR_6_FP = {mfr = 0x0086, model = 0x0064} --Aeotec multisensor 6 -local AEOTEC_MULTISENSOR_7_FP = {mfr = 0x0371, model = 0x0018} --Aeotec multisensor 7 -local ENERWAVE_MOTION_FP = {mfr = 0x011A} --Enerwave motion sensor -local HOMESEER_MULTI_SENSOR_FP = {mfr = 0x001E, prod = 0x0002, model = 0x0001} -- Homeseer multi sensor HSM100 -local SENSATIVE_STRIP_FP = {mfr = 0x019A, model = 0x000A} -local FPS = {NORTEK_FP, POPP_THERMOSTAT_FP, - AEOTEC_MULTISENSOR_6_FP, AEOTEC_MULTISENSOR_7_FP, - ENERWAVE_MOTION_FP, HOMESEER_MULTI_SENSOR_FP, SENSATIVE_STRIP_FP} - -local function can_handle(opts, driver, device, cmd, ...) - local version = require "version" - if version.api == 6 and - cmd.cmd_class == cc.WAKE_UP and - cmd.cmd_id == WakeUp.NOTIFICATION then - - for _, fp in ipairs(FPS) do - if device:id_match(fp.mfr, fp.prod, fp.model) then return false end - end - local subdriver = require("apiv6_bugfix") - return true, subdriver - else - return false - end -end - -return can_handle diff --git a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua index 94dc5975ab..322333d565 100644 --- a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua @@ -1,9 +1,34 @@ --- 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 }) +-- doing refresh would cause incorrect state for device, see comments in wakeup-no-poll +local NORTEK_FP = {mfr = 0x014F, prod = 0x2001, model = 0x0102} -- NorTek open/close sensor +local POPP_THERMOSTAT_FP = {mfr = 0x0002, prod = 0x0115, model = 0xA010} --Popp thermostat +local AEOTEC_MULTISENSOR_6_FP = {mfr = 0x0086, model = 0x0064} --Aeotec multisensor 6 +local AEOTEC_MULTISENSOR_7_FP = {mfr = 0x0371, model = 0x0018} --Aeotec multisensor 7 +local ENERWAVE_MOTION_FP = {mfr = 0x011A} --Enerwave motion sensor +local HOMESEER_MULTI_SENSOR_FP = {mfr = 0x001E, prod = 0x0002, model = 0x0001} -- Homeseer multi sensor HSM100 +local SENSATIVE_STRIP_FP = {mfr = 0x019A, model = 0x000A} +local FPS = {NORTEK_FP, POPP_THERMOSTAT_FP, + AEOTEC_MULTISENSOR_6_FP, AEOTEC_MULTISENSOR_7_FP, + ENERWAVE_MOTION_FP, HOMESEER_MULTI_SENSOR_FP, SENSATIVE_STRIP_FP} + +local function can_handle(opts, driver, device, cmd, ...) + local version = require "version" + if version.api == 6 and + cmd.cmd_class == cc.WAKE_UP and + cmd.cmd_id == WakeUp.NOTIFICATION then + + for _, fp in ipairs(FPS) do + if device:id_match(fp.mfr, fp.prod, fp.model) then return false end + end + local subdriver = require("apiv6_bugfix") + return true, subdriver + else + return false + end +end + local function wakeup_notification(driver, device, cmd) device:refresh() end @@ -15,7 +40,7 @@ local apiv6_bugfix = { } }, NAME = "apiv6_bugfix", - can_handle = require("apiv6_bugfix.can_handle"), + can_handle = can_handle } return apiv6_bugfix diff --git a/drivers/SmartThings/zwave-sensor/src/configurations.lua b/drivers/SmartThings/zwave-sensor/src/configurations.lua index 0a3c62ead8..2883e70384 100644 --- a/drivers/SmartThings/zwave-sensor/src/configurations.lua +++ b/drivers/SmartThings/zwave-sensor/src/configurations.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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.Configuration diff --git a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua deleted file mode 100644 index a707d7493a..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua +++ /dev/null @@ -1,12 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_enerwave_motion_sensor(opts, driver, device, cmd, ...) - local ENERWAVE_MFR = 0x011A - if device.zwave_manufacturer_id == ENERWAVE_MFR then - local subdriver = require("enerwave-motion-sensor") - return true, subdriver, require("enerwave-motion-sensor") - else return false end -end - -return can_handle_enerwave_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua index 012a8884a3..6fb712e3b0 100644 --- a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -10,6 +20,15 @@ local Association = (require "st.zwave.CommandClass.Association")({version=2}) --- @type st.zwave.CommandClass.WakeUp local WakeUp = (require "st.zwave.CommandClass.WakeUp")({version=1}) +local ENERWAVE_MFR = 0x011A + +local function can_handle_enerwave_motion_sensor(opts, driver, device, cmd, ...) + if device.zwave_manufacturer_id == ENERWAVE_MFR then + local subdriver = require("enerwave-motion-sensor") + return true, subdriver + else return false end +end + local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher --This is done to help the hub correctly set the checkInterval for migrated devices. @@ -39,7 +58,7 @@ local enerwave_motion_sensor = { doConfigure = do_configure }, NAME = "enerwave_motion_sensor", - can_handle = require("enerwave-motion-sensor.can_handle") + can_handle = can_handle_enerwave_motion_sensor } return enerwave_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua deleted file mode 100644 index c9fd2eafd1..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua +++ /dev/null @@ -1,17 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_everspring_motion_light(opts, driver, device, ...) - local EVERSPRING_MOTION_LIGHT_FINGERPRINT = { mfr = 0x0060, prod = 0x0012, model = 0x0001 } - if device:id_match( - EVERSPRING_MOTION_LIGHT_FINGERPRINT.mfr, - EVERSPRING_MOTION_LIGHT_FINGERPRINT.prod, - EVERSPRING_MOTION_LIGHT_FINGERPRINT.model - ) then - local subdriver = require("everspring-motion-light-sensor") - return true, subdriver - end - return false -end - -return can_handle_everspring_motion_light diff --git a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua index 8baa3756d6..1b11aadabe 100644 --- a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua @@ -1,12 +1,34 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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" local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=2,strict=true}) local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({version=2}) +local EVERSPRING_MOTION_LIGHT_FINGERPRINT = { mfr = 0x0060, prod = 0x0012, model = 0x0001 } + +local function can_handle_everspring_motion_light(opts, driver, device, ...) + if device:id_match( + EVERSPRING_MOTION_LIGHT_FINGERPRINT.mfr, + EVERSPRING_MOTION_LIGHT_FINGERPRINT.prod, + EVERSPRING_MOTION_LIGHT_FINGERPRINT.model + ) then + local subdriver = require("everspring-motion-light-sensor") + return true, subdriver + else return false end +end + local function device_added(driver, device) device:emit_event(capabilities.motionSensor.motion.inactive()) device:send(SwitchBinary:Get({})) @@ -18,7 +40,7 @@ local everspring_motion_light = { lifecycle_handlers = { added = device_added }, - can_handle = require("everspring-motion-light-sensor.can_handle"), + can_handle = can_handle_everspring_motion_light } return everspring_motion_light diff --git a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua deleted file mode 100644 index 5596894fbb..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_ezmultipli_multipurpose_sensor(opts, driver, device, ...) - local EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0004, productId = 0x0001 } - if device:id_match(EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.manufacturerId, - EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productType, - EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productId) then - local subdriver = require("ezmultipli-multipurpose-sensor") - return true, subdriver - end - return false -end - -return can_handle_ezmultipli_multipurpose_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua index f4cac30faa..1e4b3bf0ce 100644 --- a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -19,6 +28,17 @@ local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=2}) local CAP_CACHE_KEY = "st.capabilities." .. capabilities.colorControl.ID +local EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0004, productId = 0x0001 } + +local function can_handle_ezmultipli_multipurpose_sensor(opts, driver, device, ...) + if device:id_match(EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.manufacturerId, + EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productType, + EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productId) then + local subdriver = require("ezmultipli-multipurpose-sensor") + return true, subdriver + else return false end +end + local function basic_report_handler(driver, device, cmd) local event local value = (cmd.args.target_value ~= nil) and cmd.args.target_value or cmd.args.value @@ -82,7 +102,7 @@ local ezmultipli_multipurpose_sensor = { [capabilities.colorControl.commands.setColor.NAME] = set_color } }, - can_handle = require("ezmultipli-multipurpose-sensor.can_handle"), + can_handle = can_handle_ezmultipli_multipurpose_sensor } -return ezmultipli_multipurpose_sensor +return ezmultipli_multipurpose_sensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua deleted file mode 100644 index c088fd2b2f..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_fibaro_door_window_sensor(opts, driver, device, ...) - local FINGERPRINTS = require("fibaro-door-window-sensor.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.prod, fingerprint.productId) then - local subdriver = require("fibaro-door-window-sensor") - return true, subdriver, require("fibaro-door-window-sensor") - end - end - return false -end - -return can_handle_fibaro_door_window_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua deleted file mode 100644 index 992ea8fd9d..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_fibaro_door_window_sensor_1(opts, driver, device, cmd, ...) - local FINGERPRINTS = require("fibaro-door-window-sensor.fibaro-door-window-sensor-1.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true, require("fibaro-door-window-sensor.fibaro-door-window-sensor-1") - end - end - return false -end - -return can_handle_fibaro_door_window_sensor_1 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua deleted file mode 100644 index 50727133bb..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua +++ /dev/null @@ -1,8 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS = { - { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } -} - -return FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua index 4dbca58919..698fffcceb 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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" local cc = require "st.zwave.CommandClass" @@ -10,6 +21,19 @@ local SensorAlarm = (require "st.zwave.CommandClass.SensorAlarm")({ version = 1 local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 1 }) local configurationsMap = require "configurations" +local FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS = { + { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } +} + +local function can_handle_fibaro_door_window_sensor_1(opts, driver, device, cmd, ...) + for _, fingerprint in ipairs(FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true + end + end + return false +end + local function sensor_alarm_report_handler(driver, device, cmd) if (cmd.args.sensor_state == SensorAlarm.sensor_state.ALARM) then device:emit_event(capabilities.tamperAlert.tamper.detected()) @@ -68,7 +92,7 @@ local fibaro_door_window_sensor_1 = { [capabilities.refresh.ID] = { [capabilities.refresh.commands.refresh.NAME] = do_refresh }, - can_handle = require("fibaro-door-window-sensor.fibaro-door-window-sensor-1.can_handle"), + can_handle = can_handle_fibaro_door_window_sensor_1 } return fibaro_door_window_sensor_1 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua deleted file mode 100644 index 4493496f94..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_fibaro_door_window_sensor_2(opts, driver, device, cmd, ...) - local FINGERPRINTS = require("fibaro-door-window-sensor.fibaro-door-window-sensor-2.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true, require("fibaro-door-window-sensor.fibaro-door-window-sensor-2") - end - end - return false -end - -return can_handle_fibaro_door_window_sensor_2 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua deleted file mode 100644 index 6103c107d1..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua +++ /dev/null @@ -1,10 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS = { - { manufacturerId = 0x010F, productType = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe - { manufacturerId = 0x010F, productType = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA - { manufacturerId = 0x010F, productType = 0x0702, productId = 0x3000 } -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ -} - -return FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua index 250203b0cd..16c5ec2017 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -7,6 +18,21 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Alarm local Alarm = (require "st.zwave.CommandClass.Alarm")({ version = 2 }) +local FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS = { + { manufacturerId = 0x010F, productType = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe + { manufacturerId = 0x010F, productType = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA + { manufacturerId = 0x010F, productType = 0x0702, productId = 0x3000 } -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ +} + +local function can_handle_fibaro_door_window_sensor_2(opts, driver, device, cmd, ...) + for _, fingerprint in ipairs(FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true + end + end + return false +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) @@ -57,7 +83,7 @@ local fibaro_door_window_sensor_2 = { lifecycle_handlers = { added = device_added }, - can_handle = require("fibaro-door-window-sensor.fibaro-door-window-sensor-2.can_handle"), + can_handle = can_handle_fibaro_door_window_sensor_2, } return fibaro_door_window_sensor_2 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua deleted file mode 100644 index 699df3f623..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS = { - { manufacturerId = 0x010F, prod = 0x0700, productId = 0x1000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / Europe - { manufacturerId = 0x010F, prod = 0x0700, productId = 0x2000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / NA - { manufacturerId = 0x010F, prod = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe - { manufacturerId = 0x010F, prod = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA - { manufacturerId = 0x010F, prod = 0x0702, productId = 0x3000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ - { manufacturerId = 0x010F, prod = 0x0701, productId = 0x2001 }, -- Fibaro Open/Closed Sensor with temperature (FGK-10X) / NA - { manufacturerId = 0x010F, prod = 0x0701, productId = 0x1001 }, -- Fibaro Open/Closed Sensor - { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } -- Fibaro Open/Closed Sensor -} - -return FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua index 6c30508fd1..86cf865348 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 cc = require "st.zwave.CommandClass" local capabilities = require "st.capabilities" @@ -13,6 +24,27 @@ local preferencesMap = require "preferences" local FIBARO_DOOR_WINDOW_SENSOR_WAKEUP_INTERVAL = 21600 --seconds +local FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS = { + { manufacturerId = 0x010F, prod = 0x0700, productId = 0x1000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / Europe + { manufacturerId = 0x010F, prod = 0x0700, productId = 0x2000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / NA + { manufacturerId = 0x010F, prod = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe + { manufacturerId = 0x010F, prod = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA + { manufacturerId = 0x010F, prod = 0x0702, productId = 0x3000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ + { manufacturerId = 0x010F, prod = 0x0701, productId = 0x2001 }, -- Fibaro Open/Closed Sensor with temperature (FGK-10X) / NA + { manufacturerId = 0x010F, prod = 0x0701, productId = 0x1001 }, -- Fibaro Open/Closed Sensor + { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } -- Fibaro Open/Closed Sensor +} + +local function can_handle_fibaro_door_window_sensor(opts, driver, device, ...) + for _, fingerprint in ipairs(FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.prod, fingerprint.productId) then + local subdriver = require("fibaro-door-window-sensor") + return true, subdriver + end + end + return false +end + local function parameterNumberToParameterName(preferences,parameterNumber) for id, parameter in pairs(preferences) do if parameter.parameter_number == parameterNumber then @@ -122,8 +154,11 @@ local fibaro_door_window_sensor = { [capabilities.refresh.commands.refresh.NAME] = do_refresh } }, - sub_drivers = require("fibaro-door-window-sensor.sub_drivers"), - can_handle = require("fibaro-door-window-sensor.can_handle"), + sub_drivers = { + require("fibaro-door-window-sensor/fibaro-door-window-sensor-1"), + require("fibaro-door-window-sensor/fibaro-door-window-sensor-2") + }, + can_handle = can_handle_fibaro_door_window_sensor } return fibaro_door_window_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua deleted file mode 100644 index 0c4ddd4e43..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua +++ /dev/null @@ -1,9 +0,0 @@ --- 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("fibaro-door-window-sensor/fibaro-door-window-sensor-1"), - lazy_load_if_possible("fibaro-door-window-sensor/fibaro-door-window-sensor-2"), -} -return sub_drivers diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua deleted file mode 100644 index 341fbcd6f9..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_fibaro_flood_sensor(opts, driver, device, ...) - local FIBARO_MFR_ID = 0x010F - local FIBARO_FLOOD_PROD_TYPES = { 0x0000, 0x0B00 } - if device:id_match(FIBARO_MFR_ID, FIBARO_FLOOD_PROD_TYPES, nil) then - local subdriver = require("fibaro-flood-sensor") - return true, subdriver - end - return false -end - -return can_handle_fibaro_flood_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua index 9a0a8e7eaa..144be985ae 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -17,6 +26,17 @@ local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = local preferences = require "preferences" local configurations = require "configurations" +local FIBARO_MFR_ID = 0x010F +local FIBARO_FLOOD_PROD_TYPES = { 0x0000, 0x0B00 } + +local function can_handle_fibaro_flood_sensor(opts, driver, device, ...) + if device:id_match(FIBARO_MFR_ID, FIBARO_FLOOD_PROD_TYPES, nil) then + local subdriver = require("fibaro-flood-sensor") + return true, subdriver + else return false end +end + + local function basic_set_handler(self, device, cmd) local value = cmd.args.target_value and cmd.args.target_value or cmd.args.value device:emit_event(value == 0xFF and capabilities.waterSensor.water.wet() or capabilities.waterSensor.water.dry()) @@ -76,7 +96,7 @@ local fibaro_flood_sensor = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = require("fibaro-flood-sensor.can_handle"), + can_handle = can_handle_fibaro_flood_sensor } return fibaro_flood_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua deleted file mode 100644 index cd3f647394..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_fibaro_motion_sensor(opts, driver, device, ...) - - local FIBARO_MOTION_MFR = 0x010F - local FIBARO_MOTION_PROD = 0x0800 - if device:id_match(FIBARO_MOTION_MFR, FIBARO_MOTION_PROD) then - local subdriver = require("fibaro-motion-sensor") - return true, subdriver, require("fibaro-motion-sensor") - end - return false -end - -return can_handle_fibaro_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua index ed035bde18..ae45f5a27b 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -8,6 +18,15 @@ local cc = require "st.zwave.CommandClass" local SensorAlarm = (require "st.zwave.CommandClass.SensorAlarm")({ version = 1 }) local capabilities = require "st.capabilities" +local FIBARO_MOTION_MFR = 0x010F +local FIBARO_MOTION_PROD = 0x0800 + +local function can_handle_fibaro_motion_sensor(opts, driver, device, ...) + if device:id_match(FIBARO_MOTION_MFR, FIBARO_MOTION_PROD) then + local subdriver = require("fibaro-motion-sensor") + return true, subdriver + else return false end +end local function sensor_alarm_report(driver, device, cmd) if (cmd.args.sensor_state ~= SensorAlarm.sensor_state.NO_ALARM) then @@ -24,7 +43,7 @@ local fibaro_motion_sensor = { [SensorAlarm.REPORT] = sensor_alarm_report } }, - can_handle = require("fibaro-motion-sensor.can_handle") + can_handle = can_handle_fibaro_motion_sensor } -return fibaro_motion_sensor +return fibaro_motion_sensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua deleted file mode 100644 index 3ecdc2baf0..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua +++ /dev/null @@ -1,21 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local capabilities = require "st.capabilities" - ---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 -} - -return function(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 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua b/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua index 528cf7da45..058a7f955c 100644 --- a/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua @@ -1,6 +1,16 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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" --- @type st.zwave.CommandClass @@ -10,6 +20,22 @@ 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) @@ -47,7 +73,7 @@ end local firmware_version = { NAME = "firmware_version", - can_handle = require("firmware-version.can_handle"), + can_handle = can_handle_fw, lifecycle_handlers = { added = added_handler, diff --git a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua deleted file mode 100644 index e24d7b9cf2..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua +++ /dev/null @@ -1,20 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - ---- Determine whether the passed device is glentronics water leak sensor ---- ---- @param driver Driver driver instance ---- @param device Device device isntance ---- @return boolean true if the device proper, else false -local function can_handle_glentronics_water_leak_sensor(opts, driver, device, ...) - local GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS = { manufacturerId = 0x0084, productType = 0x0093, productId = 0x0114 } -- glentronics water leak sensor - if device:id_match( - GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.manufacturerId, - GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productType, - GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productId) then - return true, require("glentronics-water-leak-sensor") - end - return false -end - -return can_handle_glentronics_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua index 7400d889b7..3dba7351d6 100644 --- a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -9,6 +18,23 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +local GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS = { manufacturerId = 0x0084, productType = 0x0093, productId = 0x0114 } -- glentronics water leak sensor + +--- Determine whether the passed device is glentronics water leak sensor +--- +--- @param driver Driver driver instance +--- @param device Device device isntance +--- @return boolean true if the device proper, else false +local function can_handle_glentronics_water_leak_sensor(opts, driver, device, ...) + if device:id_match( + GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.manufacturerId, + GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productType, + GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productId) then + local subdriver = require("glentronics-water-leak-sensor") + return true, subdriver + else return false end +end + local function notification_report_handler(self, device, cmd) local event if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then @@ -52,7 +78,7 @@ local glentronics_water_leak_sensor = { added = device_added }, NAME = "glentronics water leak sensor", - can_handle = require("glentronics-water-leak-sensor.can_handle"), + can_handle = can_handle_glentronics_water_leak_sensor } return glentronics_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua deleted file mode 100644 index 992c1f7c7f..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua +++ /dev/null @@ -1,20 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - ---- Determine whether the passed device is homeseer multi sensor ---- ---- @param driver Driver driver instance ---- @param device Device device instance ---- @return boolean true if the device proper, else false -local function can_handle_homeseer_multi_sensor(opts, driver, device, ...) - local HOMESEER_MULTI_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0002, productId = 0x0001 } -- Homeseer multi sensor HSM100 - if device:id_match( - HOMESEER_MULTI_SENSOR_FINGERPRINTS.manufacturerId, - HOMESEER_MULTI_SENSOR_FINGERPRINTS.productType, - HOMESEER_MULTI_SENSOR_FINGERPRINTS.productId) then - return true, require("homeseer-multi-sensor") - end - return false -end - -return can_handle_homeseer_multi_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua index f89e6a7870..2330f28106 100644 --- a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -13,6 +22,23 @@ local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({version = 5}) local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1}) +local HOMESEER_MULTI_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0002, productId = 0x0001 } -- Homeseer multi sensor HSM100 + +--- Determine whether the passed device is homeseer multi sensor +--- +--- @param driver Driver driver instance +--- @param device Device device instance +--- @return boolean true if the device proper, else false +local function can_handle_homeseer_multi_sensor(opts, driver, device, ...) + if device:id_match( + HOMESEER_MULTI_SENSOR_FINGERPRINTS.manufacturerId, + HOMESEER_MULTI_SENSOR_FINGERPRINTS.productType, + HOMESEER_MULTI_SENSOR_FINGERPRINTS.productId) then + local subdriver = require("homeseer-multi-sensor") + return true, subdriver + else return false end +end + local function basic_set_handler(self, device, cmd) if cmd.args.value ~= nil then device:emit_event(cmd.args.value == 0xFF and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) @@ -61,7 +87,7 @@ local homeseer_multi_sensor = { init = device_init, }, NAME = "homeseer multi sensor", - can_handle = require("homeseer-multi-sensor.can_handle"), + can_handle = can_handle_homeseer_multi_sensor } return homeseer_multi_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/init.lua b/drivers/SmartThings/zwave-sensor/src/init.lua index 2c18e4c2ed..213aa8c389 100644 --- a/drivers/SmartThings/zwave-sensor/src/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/init.lua @@ -1,5 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -16,6 +27,19 @@ local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) local preferences = require "preferences" local configurations = require "configurations" +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 + --- Handle preference changes --- --- @param driver st.zwave.Driver @@ -110,7 +134,27 @@ local driver_template = { capabilities.powerMeter, capabilities.smokeDetector }, - sub_drivers = require("sub_drivers"), + sub_drivers = { + lazy_load_if_possible("zooz-4-in-1-sensor"), + lazy_load_if_possible("vision-motion-detector"), + lazy_load_if_possible("fibaro-flood-sensor"), + lazy_load_if_possible("aeotec-water-sensor"), + lazy_load_if_possible("glentronics-water-leak-sensor"), + lazy_load_if_possible("homeseer-multi-sensor"), + lazy_load_if_possible("fibaro-door-window-sensor"), + lazy_load_if_possible("sensative-strip"), + lazy_load_if_possible("enerwave-motion-sensor"), + lazy_load_if_possible("aeotec-multisensor"), + lazy_load_if_possible("zwave-water-leak-sensor"), + lazy_load_if_possible("everspring-motion-light-sensor"), + lazy_load_if_possible("ezmultipli-multipurpose-sensor"), + lazy_load_if_possible("fibaro-motion-sensor"), + 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"), + }, lifecycle_handlers = { added = added_handler, init = device_init, diff --git a/drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua deleted file mode 100644 index 45115081e4..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua +++ /dev/null @@ -1,18 +0,0 @@ --- 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-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index 70293b10fa..9585b6ffe9 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/src/preferences.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) diff --git a/drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua deleted file mode 100644 index e639c32c94..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_sensative_strip(opts, driver, device, cmd, ...) - local SENSATIVE_MFR = 0x019A - local SENSATIVE_MODEL = 0x000A - if device:id_match(SENSATIVE_MFR, nil, SENSATIVE_MODEL) then - local subdriver = require("sensative-strip") - return true, subdriver, require("sensative-strip") - end - return false -end - -return can_handle_sensative_strip diff --git a/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua b/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua index 7fd9fb2258..73f1cb8459 100644 --- a/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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. --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -9,11 +19,20 @@ local Configuration = (require "st.zwave.CommandClass.Configuration")({ version --- @type st.zwave.CommandClass.WakeUp local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) +local SENSATIVE_MFR = 0x019A +local SENSATIVE_MODEL = 0x000A local LEAKAGE_ALARM_PARAM = 12 local LEAKAGE_ALARM_OFF = 0 local SENSATIVE_COMFORT_PROFILE = "illuminance-temperature" local CONFIG_REPORT_RECEIVED = "configReportReceived" +local function can_handle_sensative_strip(opts, driver, device, cmd, ...) + if device:id_match(SENSATIVE_MFR, nil, SENSATIVE_MODEL) then + local subdriver = require("sensative-strip") + return true, subdriver + else return false end +end + local function configuration_report(driver, device, cmd) local parameter_number = cmd.args.parameter_number local configuration_value = cmd.args.configuration_value @@ -56,7 +75,7 @@ local sensative_strip = { doConfigure = do_configure }, NAME = "sensative_strip", - can_handle = require("sensative-strip.can_handle") + can_handle = can_handle_sensative_strip } return sensative_strip diff --git a/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua deleted file mode 100644 index 9504479304..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua +++ /dev/null @@ -1,26 +0,0 @@ --- 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("zooz-4-in-1-sensor"), - lazy_load_if_possible("vision-motion-detector"), - lazy_load_if_possible("fibaro-flood-sensor"), - lazy_load_if_possible("aeotec-water-sensor"), - lazy_load_if_possible("glentronics-water-leak-sensor"), - lazy_load_if_possible("homeseer-multi-sensor"), - lazy_load_if_possible("fibaro-door-window-sensor"), - lazy_load_if_possible("sensative-strip"), - lazy_load_if_possible("enerwave-motion-sensor"), - lazy_load_if_possible("aeotec-multisensor"), - lazy_load_if_possible("zwave-water-leak-sensor"), - lazy_load_if_possible("everspring-motion-light-sensor"), - lazy_load_if_possible("ezmultipli-multipurpose-sensor"), - lazy_load_if_possible("fibaro-motion-sensor"), - 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"), -} diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua index cfdb08f89d..2d83fd6e25 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua index e78e63e214..02c2adf751 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua index 5bbd1b4f0f..7fd57e42b2 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua index 57584e779c..3b73ff17c3 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua index e58badc224..8af011f51f 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua index eb951cef9b..a5f44ff12e 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua index bf2d800817..a90655669d 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua index 87d87b75b5..45e0672c11 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua index c6192bcddf..1cb2735a31 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua index c576543902..097016fc81 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua index f6db164cfe..04103db0db 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua index 6e04140010..17a627a736 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua index 2e0ead247a..74b6d6196c 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua index d3f51b0bb5..248c22ab60 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua index d597b72bdc..6904f318a9 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua index 2bbe391a62..bc78bd02ca 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua index 0fd2d2c8df..2b27d66868 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua index 98edcbf9f6..7a9743a69e 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua index 0ad90d3479..45cde29b8f 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua index 6d1f459361..24a7f31eaf 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua index 4b322b315d..e514a2f2e5 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua index 950da6f412..e6fff57b0d 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua index 5ba9ee537e..747c4cbce1 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua index 52a273f0b7..f57bfe7950 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua b/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua index 65f30fb7a8..9818f1422c 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua @@ -1,6 +1,16 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - +-- Copyright 2023 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. local test = require "integration_test" local capabilities = require "st.capabilities" @@ -103,4 +113,4 @@ test.register_message_test( } ) -test.run_registered_tests() +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua b/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua index a5be01cb81..873909df23 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua index 1b89db9f9f..2321b5bd09 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua b/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua index b4de025737..866508e3d6 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua @@ -1,6 +1,16 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - +-- Copyright 2023 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua b/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua index 1cf3da742a..e1b82a6b38 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua index c0cc183c41..1bef24c5d6 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua index 4b149c9be7..4ff11090c9 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua index 2256522888..d918f47179 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua index 3719a31f1c..a390c35db9 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua index 2a8eea8eab..e6caf1f07c 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua @@ -1,6 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua deleted file mode 100644 index c05cbdcf7d..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua +++ /dev/null @@ -1,23 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_tamper_event(opts, driver, device, cmd, ...) - local cc = require "st.zwave.CommandClass" - local Notification = (require "st.zwave.CommandClass.Notification")({ version = 4 }) - local FIBARO_DOOR_WINDOW_MFR_ID = 0x010F - - if device.zwave_manufacturer_id ~= FIBARO_DOOR_WINDOW_MFR_ID and - 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 - return true, require("timed-tamper-clear") - end - return false -end - -return can_handle_tamper_event 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 c2420791b5..2007bedb0d 100644 --- a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua @@ -1,7 +1,16 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - - +-- 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. --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -11,6 +20,23 @@ 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 + 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 + end +end -- This behavior is from zwave-door-window-sensor.groovy. We've seen this behavior -- in Ecolink and several other z-wave sensors that do not send tamper clear events @@ -34,7 +60,7 @@ local timed_tamper_clear = { } }, NAME = "timed tamper clear", - can_handle = require("timed-tamper-clear.can_handle"), + can_handle = can_handle_tamper_event } return timed_tamper_clear diff --git a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua deleted file mode 100644 index 2fa45d8015..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua +++ /dev/null @@ -1,22 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_v1_contact_event(opts, driver, device, cmd, ...) - local cc = require "st.zwave.CommandClass" - local Notification = (require "st.zwave.CommandClass.Notification")({ version = 4 }) - - if 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.v1_alarm_type == 0x07 then - local subdriver = require("v1-contact-event") - return true, subdriver, require("v1-contact-event") - else - return false - end -end - -return can_handle_v1_contact_event diff --git a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua index 887ac32bf0..40efc7633e 100644 --- a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua @@ -1,5 +1,16 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 +-- 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. --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -7,6 +18,20 @@ local cc = require "st.zwave.CommandClass" local Notification = (require "st.zwave.CommandClass.Notification")({ version = 4 }) local capabilities = require "st.capabilities" +local function can_handle_v1_contact_event(opts, driver, device, cmd, ...) + if 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.v1_alarm_type == 0x07 then + local subdriver = require("v1-contact-event") + return true, subdriver + else + return false + end +end -- This behavior is from zwave-door-window-sensor.groovy, where it is -- indicated that certain monoprice sensors had this behavior. Also, @@ -28,7 +53,7 @@ local v1_contact_event = { } }, NAME = "v1 contact event", - can_handle = require("v1-contact-event.can_handle"), + can_handle = can_handle_v1_contact_event } return v1_contact_event diff --git a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua deleted file mode 100644 index d270a7954d..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua +++ /dev/null @@ -1,17 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - ---- Determine whether the passed device is zwave-plus-motion-temp-sensor -local function can_handle_vision_motion_detector(opts, driver, device, ...) - local VISION_MOTION_DETECTOR_FINGERPRINTS = { manufacturerId = 0x0109, productType = 0x2002, productId = 0x0205 } -- Vision Motion Detector ZP3102 - if device:id_match( - VISION_MOTION_DETECTOR_FINGERPRINTS.manufacturerId, - VISION_MOTION_DETECTOR_FINGERPRINTS.productType, - VISION_MOTION_DETECTOR_FINGERPRINTS.productId - ) then - return true, require("vision-motion-detector") - end - return false -end - -return can_handle_vision_motion_detector diff --git a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua index 72934362c5..320bf3824f 100644 --- a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -13,6 +22,20 @@ local Configuration = (require "st.zwave.CommandClass.Configuration")({ version --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +local VISION_MOTION_DETECTOR_FINGERPRINTS = { manufacturerId = 0x0109, productType = 0x2002, productId = 0x0205 } -- Vision Motion Detector ZP3102 + +--- Determine whether the passed device is zwave-plus-motion-temp-sensor +local function can_handle_vision_motion_detector(opts, driver, device, ...) + if device:id_match( + VISION_MOTION_DETECTOR_FINGERPRINTS.manufacturerId, + VISION_MOTION_DETECTOR_FINGERPRINTS.productType, + VISION_MOTION_DETECTOR_FINGERPRINTS.productId + ) then + local subdriver = require("vision-motion-detector") + return true, subdriver + else return false end +end + --- Handler for notification report command class from sensor --- --- @param self st.zwave.Driver @@ -60,7 +83,7 @@ local vision_motion_detector = { doConfigure = do_configure, }, NAME = "Vision motion detector", - can_handle = require("vision-motion-detector.can_handle"), + can_handle = can_handle_vision_motion_detector } return vision_motion_detector diff --git a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua deleted file mode 100644 index 15ac66d439..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua +++ /dev/null @@ -1,12 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle(opts, driver, device, ...) - local fingerprint = {manufacturerId = 0x014F, productType = 0x2001, productId = 0x0102} -- NorTek open/close sensor - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true, require("wakeup-no-poll") - end - return false -end - -return can_handle diff --git a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua index 1270cd4557..59d298a0e4 100644 --- a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua @@ -1,7 +1,16 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - - +-- Copyright 2023 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -12,6 +21,17 @@ local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({version = 2 --- @type st.zwave.CommandClass.Battery local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +local fingerprint = {manufacturerId = 0x014F, productType = 0x2001, productId = 0x0102} -- NorTek open/close sensor + +local function can_handle(opts, driver, device, ...) + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("wakeup-no-poll") + return true, subdriver + else + return false + end +end + -- Nortek open/closed sensors _always_ respond with "open" when polled, and they are polled after wakeup local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher @@ -33,7 +53,7 @@ local wakeup_no_poll = { [WakeUp.NOTIFICATION] = wakeup_notification } }, - can_handle = require("wakeup-no-poll.can_handle"), + can_handle = can_handle } -return wakeup_no_poll +return wakeup_no_poll \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua deleted file mode 100644 index 17c80b70fb..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_zooz_4_in_1_sensor(opts, driver, device, ...) - local FINGERPRINTS = require("zooz-4-in-1-sensor.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("zooz-4-in-1-sensor") - return true, subdriver, require("zooz-4-in-1-sensor") - end - end - return false -end - -return can_handle_zooz_4_in_1_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua deleted file mode 100644 index 12d853b147..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua +++ /dev/null @@ -1,10 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local ZOOZ_4_IN_1_FINGERPRINTS = { - { manufacturerId = 0x027A, productType = 0x2021, productId = 0x2101 }, -- Zooz 4-in-1 sensor - { manufacturerId = 0x0109, productType = 0x2021, productId = 0x2101 }, -- ZP3111US 4-in-1 Motion - { manufacturerId = 0x0060, productType = 0x0001, productId = 0x0004 } -- Everspring Immune Pet PIR Sensor SP815 -} - -return ZOOZ_4_IN_1_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua index 7321c3f9b4..5d4570e525 100644 --- a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua @@ -1,7 +1,16 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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 @@ -13,8 +22,22 @@ local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ ve --- @type st.utils local utils = require "st.utils" +local ZOOZ_4_IN_1_FINGERPRINTS = { + { manufacturerId = 0x027A, productType = 0x2021, productId = 0x2101 }, -- Zooz 4-in-1 sensor + { manufacturerId = 0x0109, productType = 0x2021, productId = 0x2101 }, -- ZP3111US 4-in-1 Motion + { manufacturerId = 0x0060, productType = 0x0001, productId = 0x0004 } -- Everspring Immune Pet PIR Sensor SP815 +} --- Determine whether the passed device is zooz_4_in_1_sensor +local function can_handle_zooz_4_in_1_sensor(opts, driver, device, ...) + for _, fingerprint in ipairs(ZOOZ_4_IN_1_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("zooz-4-in-1-sensor") + return true, subdriver + end + end + return false +end --- Handler for notification report command class --- @@ -86,7 +109,7 @@ local zooz_4_in_1_sensor = { } }, NAME = "zooz 4 in 1 sensor", - can_handle = require("zooz-4-in-1-sensor.can_handle"), + can_handle = can_handle_zooz_4_in_1_sensor } return zooz_4_in_1_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua deleted file mode 100644 index ad7e736a7c..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_water_leak_sensor(opts, driver, device, ...) - local FINGERPRINTS = require("zwave-water-leak-sensor.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("zwave-water-leak-sensor") - return true, subdriver, require("zwave-water-leak-sensor") - end - end - return false -end - -return can_handle_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua deleted file mode 100644 index c07bdb52cb..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua +++ /dev/null @@ -1,19 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local WATER_LEAK_SENSOR_FINGERPRINTS = { - {mfr = 0x0084, prod = 0x0063, model = 0x010C}, -- SmartThings Water Leak Sensor - {mfr = 0x0084, prod = 0x0053, model = 0x0216}, -- FortrezZ Water Leak Sensor - {mfr = 0x021F, prod = 0x0003, model = 0x0085}, -- Dome Leak Sensor - {mfr = 0x0258, prod = 0x0003, model = 0x0085}, -- NEO Coolcam Water Sensor - {mfr = 0x0258, prod = 0x0003, model = 0x1085}, -- NEO Coolcam Water Sensor - {mfr = 0x0258, prod = 0x0003, model = 0x2085}, -- NEO Coolcam Water Sensor - {mfr = 0x0086, prod = 0x0002, model = 0x007A}, -- Aeotec Water Sensor 6 (EU) - {mfr = 0x0086, prod = 0x0102, model = 0x007A}, -- Aeotec Water Sensor 6 (US) - {mfr = 0x0086, prod = 0x0202, model = 0x007A}, -- Aeotec Water Sensor 6 (AU) - {mfr = 0x000C, prod = 0x0201, model = 0x000A}, -- HomeSeer LS100+ Water Sensor - {mfr = 0x0173, prod = 0x4C47, model = 0x4C44}, -- Leak Gopher Z-Wave Leak Detector - {mfr = 0x027A, prod = 0x7000, model = 0xE002} -- Zooz ZSE42 XS Water Leak Sensor -} - -return WATER_LEAK_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua index 4575de352a..1eefab7479 100644 --- a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua @@ -1,10 +1,47 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.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" local cc = require "st.zwave.CommandClass" local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) + +local WATER_LEAK_SENSOR_FINGERPRINTS = { + {mfr = 0x0084, prod = 0x0063, model = 0x010C}, -- SmartThings Water Leak Sensor + {mfr = 0x0084, prod = 0x0053, model = 0x0216}, -- FortrezZ Water Leak Sensor + {mfr = 0x021F, prod = 0x0003, model = 0x0085}, -- Dome Leak Sensor + {mfr = 0x0258, prod = 0x0003, model = 0x0085}, -- NEO Coolcam Water Sensor + {mfr = 0x0258, prod = 0x0003, model = 0x1085}, -- NEO Coolcam Water Sensor + {mfr = 0x0258, prod = 0x0003, model = 0x2085}, -- NEO Coolcam Water Sensor + {mfr = 0x0086, prod = 0x0002, model = 0x007A}, -- Aeotec Water Sensor 6 (EU) + {mfr = 0x0086, prod = 0x0102, model = 0x007A}, -- Aeotec Water Sensor 6 (US) + {mfr = 0x0086, prod = 0x0202, model = 0x007A}, -- Aeotec Water Sensor 6 (AU) + {mfr = 0x000C, prod = 0x0201, model = 0x000A}, -- HomeSeer LS100+ Water Sensor + {mfr = 0x0173, prod = 0x4C47, model = 0x4C44}, -- Leak Gopher Z-Wave Leak Detector + {mfr = 0x027A, prod = 0x7000, model = 0xE002} -- Zooz ZSE42 XS Water Leak Sensor +} + +local function can_handle_water_leak_sensor(opts, driver, device, ...) + for _, fingerprint in ipairs(WATER_LEAK_SENSOR_FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("zwave-water-leak-sensor") + return true, subdriver + 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 device:emit_event(value == 0xFF and capabilities.waterSensor.water.wet() or capabilities.waterSensor.water.dry()) @@ -17,7 +54,7 @@ local water_leak_sensor = { [Basic.SET] = basic_set_handler } }, - can_handle = require("zwave-water-leak-sensor.can_handle"), + can_handle = can_handle_water_leak_sensor } return water_leak_sensor From 314b6627c61e3ff6ca07d4ffb0848ae946565a71 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 24 Feb 2026 13:52:12 -0800 Subject: [PATCH 128/538] WWSTCERT-10426 Halo Select Plus --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 +++++ .../matter-lock/src/new-matter-lock/fingerprints.lua | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 3efd33b364..29c37dd69c 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -72,6 +72,11 @@ matterManufacturer: vendorId: 0x1421 productId: 0x0042 deviceProfileName: lock-user-pin-battery + - id: "5153/65" + deviceLabel: Halo Select Plus + vendorId: 0x1421 + productId: 0x0041 + deviceProfileName: lock-user-pin-battery - id: "5153/129" deviceLabel: Kwikset Aura Reach vendorId: 0x1421 diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index 799c20b9ae..ac0352c75a 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -28,6 +28,7 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x15F2, 0x0001}, -- Viomi, AiSafety Smart Lock E100 {0x158B, 0x0001}, -- Deasino, DS-MT01 {0x10E1, 0x2002}, -- VDA + {0x1421, 0x0041}, -- Kwikset Halo Select Plus {0x1421, 0x0042}, -- Kwikset Halo Select Plus {0x1421, 0x0081}, -- Kwikset Aura Reach {0x1236, 0xa538}, -- Schlage Sense Pro From a2a8611ea9c97430f8fecfd0f5fa994f4e337b1c Mon Sep 17 00:00:00 2001 From: cjswedes Date: Fri, 20 Feb 2026 14:43:23 -0600 Subject: [PATCH 129/538] Increase Z-Wave driver coverage Add comments about aeotec doorbell siren bugs --- .../src/test/test_zwave_multi_button.lua | 26 ++ .../src/test/test_aeon_multisensor.lua | 24 ++ .../src/test/test_aeotec_multisensor_6.lua | 22 ++ .../src/test/test_fibaro_flood_sensor.lua | 29 ++ .../src/test/test_firmware_version.lua | 33 +++ .../src/test/test_zooz_4_in_1_sensor.lua | 18 ++ .../src/test/test_zwave_sensor.lua | 36 +++ .../src/aeotec-doorbell-siren/init.lua | 15 +- .../src/test/test_fibaro_roller_shutter.lua | 26 ++ .../src/test/test_qubino_flush_shutter.lua | 255 ++++++++++-------- .../test_zwave_iblinds_window_treatment.lua | 48 ++++ .../src/test/test_zwave_window_treatment.lua | 39 +++ 12 files changed, 461 insertions(+), 110 deletions(-) diff --git a/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua index 27c9b851f9..3ddf585681 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua @@ -864,4 +864,30 @@ test.register_coroutine_test( end ) +test.register_message_test( + "Central scene notification with scene_number beyond profile buttons falls back to main component", + { + { + channel = "zwave", + direction = "receive", + message = { + mock_aeotec_wallmote_quad.id, + zw_test_utils.zwave_test_build_receive_command( + CentralScene:Notification({ key_attributes = CentralScene.key_attributes.KEY_PRESSED_1_TIME, scene_number = 5 }) + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_aeotec_wallmote_quad:generate_test_message("main", capabilities.button.button.pushed({ state_change = true })) + }, + { + channel = "capability", + direction = "send", + message = mock_aeotec_wallmote_quad:generate_test_message("main", capabilities.button.button.pushed({ state_change = true })) + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua index 2d83fd6e25..59435aa68a 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua @@ -15,7 +15,9 @@ local test = require "integration_test" local zw = require "st.zwave" local zw_test_utils = require "integration_test.zwave_test_utils" +local capabilities = require "st.capabilities" local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 1 }) +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 }) local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ version = 5 }) local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) @@ -92,4 +94,26 @@ test.register_coroutine_test( end ) +test.register_message_test( + "Notification HOME_SECURITY MOTION_DETECTION should be handled as motion active", + { + { + 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.MOTION_DETECTION + })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.active()) + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua index 02c2adf751..f858438d9a 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua @@ -413,4 +413,26 @@ test.register_coroutine_test( end ) +test.register_message_test( + "Notification HOME_SECURITY MOTION_DETECTION should be handled as motion active", + { + { + 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.MOTION_DETECTION + })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.active()) + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua index 7a9743a69e..2d93278b47 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua @@ -20,6 +20,8 @@ local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) local SensorAlarm = (require "st.zwave.CommandClass.SensorAlarm")({ version = 1 }) local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 }) local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ version = 5 }) +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) +local Association = (require "st.zwave.CommandClass.Association")({ version = 1 }) local t_utils = require "integration_test.utils" local sensor_endpoints = { @@ -277,4 +279,31 @@ test.register_coroutine_test( ) +test.register_coroutine_test( + "doConfigure should call initial_configuration and preferences for non-wakeup device", + function () + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_sensor.id, "doConfigure" }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Configuration:Set({ parameter_number = 74, configuration_value = 3, size = 1 }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Association:Set({ grouping_identifier = 2, node_ids = {} }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Association:Set({ grouping_identifier = 3, node_ids = {} }) + ) + ) + mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua b/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua index 9790b5cfb5..d4903fc30d 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua @@ -17,6 +17,7 @@ local test = require "integration_test" local cc = require "st.zwave.CommandClass" local zw_test_utils = require "integration_test.zwave_test_utils" local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass.Version local Version = (require "st.zwave.CommandClass.Version")({ version = 1 }) --- @type st.zwave.CommandClass.WakeUp @@ -88,4 +89,36 @@ test.register_message_test( } ) +test.register_message_test( + "Version:Report should emit firmwareUpdate.currentVersion", + { + { + channel = "zwave", + direction = "receive", + message = { mock_device.id, zw_test_utils.zwave_test_build_receive_command(Version:Report({ + application_version = 1, + application_sub_version = 5, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.firmwareUpdate.currentVersion({ value = "1.05" })) + } + } +) + +test.register_coroutine_test( + "added lifecycle event should emit initial state and request firmware version", + function () + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command(mock_device, Version:Get({})) + ) + end +) + test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua index 1bef24c5d6..66cca4afe4 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua @@ -228,4 +228,22 @@ test.register_message_test( } ) +test.register_message_test( + "Sensor multilevel luminance report with value=0 uses default lux conversion", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ + sensor_type = SensorMultilevel.sensor_type.LUMINANCE, + sensor_value = 0 })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({value = 0, unit = "lux"})) + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua index a390c35db9..c15daeb188 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua @@ -735,4 +735,40 @@ test.register_message_test( } ) +test.register_message_test( + "Basic Set value=0 for contact sensor should emit contact.closed", + { + { + channel = "zwave", + direction = "receive", + message = { mock_contact_device.id, zw_test_utils.zwave_test_build_receive_command(Basic:Set({ + value = 0 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_contact_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + } + } +) + +test.register_message_test( + "Basic Set value=0 for motion sensor should emit motion.inactive", + { + { + channel = "zwave", + direction = "receive", + message = { mock_motion_device.id, zw_test_utils.zwave_test_build_receive_command(Basic:Set({ + value = 0 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_motion_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) + } + } +) + test.run_registered_tests() 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 35f006e94f..716c46186e 100644 --- a/drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/init.lua +++ b/drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/init.lua @@ -33,7 +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 querySoundStatus(device) for endpoint = 2, NUMBER_OF_SOUND_COMPONENTS do device:send_to_component(Basic:Get({}), "sound"..endpoint) @@ -208,6 +207,7 @@ local function changeDeviceProfileIfNeeded(device, endpoint) end end +-- Note that endpoint should be a number not a dst_channels table. local function setActiveEndpoint(device, endpoint) if (endpoint) then device:set_field(LAST_TRIGGERED_ENDPOINT, endpoint, {persist = true}) @@ -271,20 +271,22 @@ end local function alarmChimeOnOff(device, command, newValue) if (device and command and newValue) then + -- Note that zwave/device.lua send_to_component expects the component_to_endpoint function to + -- return a dst_channels table, not a single endpoint number local endpoint = component_to_endpoint(device, command.component) - device:send(Basic:Set({value = newValue})):to_endpoint(endpoint) + device:send_to_component(Basic:Set({value = newValue}), command.component) if (newValue == ON) then - setActiveEndpoint(endpoint) + setActiveEndpoint(device, endpoint[1]) end end end -local function alarm_chime_on(device, command) +local function alarm_chime_on(self, device, command) resetActiveEndpoint(device) alarmChimeOnOff(device, command, ON) end -local function alarm_chime_off(device, command) +local function alarm_chime_off(self, device, command) alarmChimeOnOff(device, command, OFF) end @@ -306,6 +308,9 @@ local aeotec_doorbell_siren = { [Notification.REPORT] = notification_report_handler } }, + -- This typo is a bug. There are many unit tests that fail, when + -- it is enabled, and it is not clear what the correct functionality is + -- without real device testing. capabilities_handlers = { [capabilities.refresh.ID] = { [capabilities.refresh.commands.refresh.NAME] = do_refresh 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 c601ba630f..c53e314f3a 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 @@ -740,4 +740,30 @@ do end ) end +test.register_coroutine_test( + "Configuration:Report for OPERATING_MODE=1 should update to roller shutter profile", + function() + test.socket.zwave:__queue_receive({ + mock_fibaro_roller_shutter_venetian.id, + zw_test_utils.zwave_test_build_receive_command( + Configuration:Report({ parameter_number = 151, configuration_value = 1 }) + ) + }) + mock_fibaro_roller_shutter_venetian:expect_metadata_update({ profile = "fibaro-roller-shutter" }) + end +) + +test.register_coroutine_test( + "Configuration:Report for OPERATING_MODE=2 should update to venetian profile", + function() + test.socket.zwave:__queue_receive({ + mock_fibaro_roller_shutter_venetian.id, + zw_test_utils.zwave_test_build_receive_command( + Configuration:Report({ parameter_number = 151, configuration_value = 2 }) + ) + }) + mock_fibaro_roller_shutter_venetian:expect_metadata_update({ profile = "fibaro-roller-shutter-venetian" }) + end +) + test.run_registered_tests() 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 edaffb4214..d9bd11e01f 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 @@ -510,122 +510,113 @@ test.register_coroutine_test( end ) -do - test.register_coroutine_test( - "Mode should be changed to venetian blinds after receiving configuration report with value 1", - function() - test.wait_for_events() - test.socket.zwave:__queue_receive({ - mock_qubino_flush_shutter.id, - Configuration:Report({ - parameter_number = 71, - size = 1, - configuration_value = 1 - }) +test.register_coroutine_test( + "Mode should be changed to venetian blinds after receiving configuration report with value 1", + function() + test.wait_for_events() + test.socket.zwave:__queue_receive({ + mock_qubino_flush_shutter.id, + Configuration:Report({ + parameter_number = 71, + size = 1, + configuration_value = 1 }) - mock_qubino_flush_shutter:expect_metadata_update({ profile = "qubino-flush-shutter-venetian" }) - end - ) -end + }) + mock_qubino_flush_shutter:expect_metadata_update({ profile = "qubino-flush-shutter-venetian" }) + end +) -do - test.register_coroutine_test( - "Mode should be changed to shutter after receiving configuration report with value 0", - function() - test.wait_for_events() - test.socket.zwave:__queue_receive({ - mock_qubino_flush_shutter.id, - Configuration:Report({ - parameter_number = 71, - size = 1, - configuration_value = 0 - }) +test.register_coroutine_test( + "Mode should be changed to shutter after receiving configuration report with value 0", + function() + test.wait_for_events() + test.socket.zwave:__queue_receive({ + mock_qubino_flush_shutter.id, + Configuration:Report({ + parameter_number = 71, + size = 1, + configuration_value = 0 }) - mock_qubino_flush_shutter:expect_metadata_update({ profile = "qubino-flush-shutter" }) - end - ) -end + }) + mock_qubino_flush_shutter:expect_metadata_update({ profile = "qubino-flush-shutter" }) + end +) -do - test.register_coroutine_test( - "SwitchMultilevel:Set() should be correctly interpreted by the driver", - function() - local targetValue = 50 - test.wait_for_events() - test.socket.zwave:__queue_receive({ - mock_qubino_flush_shutter.id, - SwitchMultilevel:Set({ - value = targetValue - }) +test.register_coroutine_test( + "SwitchMultilevel:Set() should be correctly interpreted by the driver", + function() + local targetValue = 50 + test.wait_for_events() + test.socket.zwave:__queue_receive({ + mock_qubino_flush_shutter.id, + SwitchMultilevel:Set({ + value = targetValue }) - local expectedCachedEvent = utils.stringify_table(capabilities.windowShade.windowShade.opening()) - test.wait_for_events() - local actualCachedEvent = utils.stringify_table(mock_qubino_flush_shutter.transient_store.blinds_last_command) - assert(expectedCachedEvent == actualCachedEvent, "driver should cache 'opening' event when targetLevel > currentLevel") - assert(targetValue == mock_qubino_flush_shutter.transient_store.shade_target, "driver should chache correct level value") - end - ) -end + }) + local expectedCachedEvent = utils.stringify_table(capabilities.windowShade.windowShade.opening()) + test.wait_for_events() + local actualCachedEvent = utils.stringify_table(mock_qubino_flush_shutter.transient_store.blinds_last_command) + assert(expectedCachedEvent == actualCachedEvent, "driver should cache 'opening' event when targetLevel > currentLevel") + assert(targetValue == mock_qubino_flush_shutter.transient_store.shade_target, "driver should chache correct level value") + end +) -do - test.register_coroutine_test( - "Meter:Report() with meter_value > 0 should be correctly interpreted by the driver", - function() - local cachedShadesEvent = capabilities.windowShade.windowShade.opening() - local targetValue = 50 - mock_qubino_flush_shutter:set_field("blinds_last_command", cachedShadesEvent) - mock_qubino_flush_shutter:set_field("shade_target", targetValue) - test.wait_for_events() - test.socket.zwave:__queue_receive({ - mock_qubino_flush_shutter.id, - Meter:Report({ - scale = Meter.scale.electric_meter.WATTS, - meter_value = 10 - }) +test.register_coroutine_test( + "Meter:Report() with meter_value > 0 should be correctly interpreted by the driver", + function() + local cachedShadesEvent = capabilities.windowShade.windowShade.opening() + local targetValue = 50 + mock_qubino_flush_shutter:set_field("blinds_last_command", cachedShadesEvent) + mock_qubino_flush_shutter:set_field("shade_target", targetValue) + test.wait_for_events() + test.socket.zwave:__queue_receive({ + mock_qubino_flush_shutter.id, + Meter:Report({ + scale = Meter.scale.electric_meter.WATTS, + meter_value = 10 }) - test.socket.capability:__expect_send( - mock_qubino_flush_shutter:generate_test_message("main", cachedShadesEvent) - ) - test.socket.capability:__expect_send( - mock_qubino_flush_shutter:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(targetValue)) - ) - test.socket.capability:__expect_send( - mock_qubino_flush_shutter:generate_test_message("main", capabilities.powerMeter.power({value = 10, unit = "W"})) - ) - end - ) -end + }) + test.socket.capability:__expect_send( + mock_qubino_flush_shutter:generate_test_message("main", cachedShadesEvent) + ) + test.socket.capability:__expect_send( + mock_qubino_flush_shutter:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(targetValue)) + ) + test.socket.capability:__expect_send( + mock_qubino_flush_shutter:generate_test_message("main", capabilities.powerMeter.power({value = 10, unit = "W"})) + ) + end +) -do - test.register_coroutine_test( - "Meter:Report() with meter_value == 0 should be correctly interpreted by the driver", - function() - test.wait_for_events() - test.socket.zwave:__queue_receive({ - mock_qubino_flush_shutter.id, - Meter:Report({ - scale = Meter.scale.electric_meter.WATTS, - meter_value = 0 - }) + +test.register_coroutine_test( + "Meter:Report() with meter_value == 0 should be correctly interpreted by the driver", + function() + test.wait_for_events() + test.socket.zwave:__queue_receive({ + mock_qubino_flush_shutter.id, + Meter:Report({ + scale = Meter.scale.electric_meter.WATTS, + meter_value = 0 }) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_qubino_flush_shutter, - SwitchMultilevel:Get({}) - ) - ) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_qubino_flush_shutter, - Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}) - ) + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_qubino_flush_shutter, + SwitchMultilevel:Get({}) ) - test.socket.capability:__expect_send( - mock_qubino_flush_shutter:generate_test_message("main", capabilities.powerMeter.power({value = 0, unit = "W"})) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_qubino_flush_shutter, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}) ) - end - ) -end + ) + test.socket.capability:__expect_send( + mock_qubino_flush_shutter:generate_test_message("main", capabilities.powerMeter.power({value = 0, unit = "W"})) + ) + end +) test.register_message_test( "Energy meter reports should be generating events", @@ -651,4 +642,58 @@ test.register_message_test( } ) +test.register_coroutine_test( + "SwitchMultilevel:Set with lower target than current should cache closing command and fire Meter:Get after 4s", + function() + -- Pre-set state so currentLevel (80) > targetLevel (10) + mock_qubino_flush_shutter_venetian:update_state_cache_entry( + "main", capabilities.windowShadeLevel.ID, "shadeLevel", { value = 80 } + ) + test.timer.__create_and_queue_test_time_advance_timer(4, "oneshot") + test.socket.zwave:__queue_receive({ + mock_qubino_flush_shutter_venetian.id, + SwitchMultilevel:Set({ value = 10 }) + }) + test.wait_for_events() + test.mock_time.advance_time(4) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_qubino_flush_shutter_venetian, + Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) + ) + ) + end +) + +do + local new_param_value = 1 + test.register_coroutine_test( + "infoChanged on venetian qubino should send Configuration:Set then Configuration:Get after 1s", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + local device_data = utils.deep_copy(mock_qubino_flush_shutter_venetian.raw_st_data) + device_data.preferences["operatingModes"] = new_param_value + local device_data_json = dkjson.encode(device_data) + test.socket.device_lifecycle:__queue_receive({ mock_qubino_flush_shutter_venetian.id, "infoChanged", device_data_json }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_qubino_flush_shutter_venetian, + Configuration:Set({ + parameter_number = 71, + configuration_value = new_param_value, + size = 1 + }) + ) + ) + test.mock_time.advance_time(1) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_qubino_flush_shutter_venetian, + Configuration:Get({ parameter_number = 71 }) + ) + ) + end + ) +end + test.run_registered_tests() 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 e2134b1163..8913a42e53 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 @@ -475,4 +475,52 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Setting window shade level to 0 on iblinds v1 should emit windowShade.closed", + function() + test.socket.capability:__queue_receive( + { + mock_blind.id, + { capability = "windowShadeLevel", command = "setShadeLevel", args = { 0 } } + } + ) + test.socket.capability:__expect_send( + mock_blind:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + ) + test.socket.capability:__expect_send( + mock_blind:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_blind, + SwitchMultilevel:Set({ value = 0 }) + ) + ) + end +) + +test.register_coroutine_test( + "Setting window shade level to 0 on iblinds v3 should emit windowShade.closed", + function() + test.socket.capability:__queue_receive( + { + mock_blind_v3.id, + { capability = "windowShadeLevel", command = "setShadeLevel", args = { 0 } } + } + ) + test.socket.capability:__expect_send( + mock_blind_v3:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + ) + test.socket.capability:__expect_send( + mock_blind_v3:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_blind_v3, + SwitchMultilevel:Set({ value = 0 }) + ) + ) + end +) + test.run_registered_tests() 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 fa6fd809e7..be25647509 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 @@ -531,4 +531,43 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Setting window shade preset on basic-only device should generate Basic:Set and Basic:Get", + function() + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + test.socket.capability:__queue_receive( + { + mock_window_shade_basic.id, + { capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} } + } + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_window_shade_basic, + Basic:Set({ value = 50 }) + ) + ) + test.wait_for_events() + test.mock_time.advance_time(5) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_window_shade_basic, + Basic:Get({}) + ) + ) + end +) + +test.register_coroutine_test( + "Adding a window treatment device should emit supportedWindowShadeCommands", + function() + test.socket.device_lifecycle():__queue_receive({ mock_window_shade_basic.id, "added" }) + test.socket.capability:__expect_send( + mock_window_shade_basic:generate_test_message("main", capabilities.windowShade.supportedWindowShadeCommands( + {"open", "close", "pause"}, { visibility = { displayed = false } } + )) + ) + end +) + test.run_registered_tests() From fb5a39ee38be37b219bea92eefbfe5d4a95517d8 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 24 Feb 2026 13:59:25 -0800 Subject: [PATCH 130/538] WWSTCERT-10480 GELUBU Door Lock G30 --- drivers/SmartThings/zigbee-lock/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/zigbee-lock/fingerprints.yml b/drivers/SmartThings/zigbee-lock/fingerprints.yml index 5c04d78ce5..083dd3a11e 100644 --- a/drivers/SmartThings/zigbee-lock/fingerprints.yml +++ b/drivers/SmartThings/zigbee-lock/fingerprints.yml @@ -5,6 +5,11 @@ zigbeeManufacturer: manufacturer: GELUBU model: S93 deviceProfileName: lock-battery + - id: "GELUBU/G30" + deviceLabel: GELUBU Door Lock G30 + manufacturer: GELUBU + model: G30 + deviceProfileName: lock-battery # YALE - id: "Yale YRD220/240" deviceLabel: "Yale Door Lock" From 3087d96ea7ee75d45009f08570736a0947dcbe59 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Wed, 25 Feb 2026 11:12:12 -0600 Subject: [PATCH 131/538] revert the recent profile components check additions --- .../matter-lock/src/new-matter-lock/init.lua | 33 ++------ .../air_quality_sensor_utils/utils.lua | 24 ++---- .../sub_drivers/air_quality_sensor/init.lua | 3 +- .../SmartThings/matter-switch/src/init.lua | 3 +- .../sub_drivers/camera/camera_utils/utils.lua | 18 +++++ .../src/sub_drivers/camera/init.lua | 2 +- .../src/switch_utils/device_configuration.lua | 1 + .../matter-switch/src/switch_utils/fields.lua | 2 + .../matter-switch/src/switch_utils/utils.lua | 27 ------- .../src/test/test_matter_light_fan.lua | 81 ++----------------- 10 files changed, 45 insertions(+), 149 deletions(-) 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 a91790cedb..11aa2b0884 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -22,6 +22,8 @@ local MIN_EPOCH_S = 0 local MAX_EPOCH_S = 0xffffffff local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 +local MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED" + local RESPONSE_STATUS_MAP = { [DoorLock.types.DlStatus.SUCCESS] = "success", [DoorLock.types.DlStatus.FAILURE] = "failure", @@ -201,6 +203,7 @@ local function match_profile_modular(driver, device) table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) + device:set_field(MODULAR_PROFILE_UPDATED, true) end local function match_profile_switch(driver, device) @@ -238,37 +241,11 @@ local function match_profile_switch(driver, device) device:try_update_metadata({profile = profile_name}) end -local function profile_changed(latest_profile, previous_profile) - if latest_profile.id ~= previous_profile.id then - return true - end - for component_id, synced_component in pairs(latest_profile.components or {}) do - local prev_component = previous_profile.components[component_id] - if prev_component == nil then - return true - end - if #synced_component.capabilities ~= #prev_component.capabilities then - return true - end - -- Build a table of capability IDs from the previous component. Then, use this map to check - -- that all capabilities in the synced component existed in the previous component. - local prev_cap_ids = {} - for _, capability in ipairs(prev_component.capabilities or {}) do - prev_cap_ids[capability.id] = true - end - for _, capability in ipairs(synced_component.capabilities or {}) do - if not prev_cap_ids[capability.id] then - return true - end - end - end - return false -end - local function info_changed(driver, device, event, args) - if not profile_changed(device.profile, args.old_st_store.profile) then + if device.profile.id == args.old_st_store.profile.id and not device:get_field(MODULAR_PROFILE_UPDATED) then return end + device:set_field(MODULAR_PROFILE_UPDATED, nil) for cap_id, attributes in pairs(subscribed_attributes) do if device:supports_capability_by_id(cap_id) then for _, attr in ipairs(attributes) do 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 index 5c1726d6d8..95ca80964c 100644 --- 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 @@ -77,26 +77,17 @@ function AirQualitySensorUtils.set_supported_health_concern_values(device) end end -function AirQualitySensorUtils.profile_changed(latest_profile, previous_profile) - if latest_profile.id ~= previous_profile.id then +function AirQualitySensorUtils.profile_changed(synced_components, prev_components) + if #synced_components ~= #prev_components then return true end - for component_id, synced_component in pairs(latest_profile.components or {}) do - local prev_component = previous_profile.components[component_id] - if prev_component == nil then + 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 - if #synced_component.capabilities ~= #prev_component.capabilities then - return true - end - -- Build a table of capability IDs from the previous component. Then, use this map to check - -- that all capabilities in the synced component existed in the previous component. - local prev_cap_ids = {} - for _, capability in ipairs(prev_component.capabilities or {}) do - prev_cap_ids[capability.id] = true - end - for _, capability in ipairs(synced_component.capabilities or {}) do - if not prev_cap_ids[capability.id] then + for _, capability in pairs(component.capabilities or {}) do + if prev_components[component.id][capability.id] == nil then return true end end @@ -104,5 +95,4 @@ function AirQualitySensorUtils.profile_changed(latest_profile, previous_profile) return false end - return AirQualitySensorUtils 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 index 41c6514b40..98b8430c98 100644 --- 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 @@ -66,7 +66,8 @@ function AirQualitySensorLifecycleHandlers.device_init(driver, device) end function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, args) - if aqs_utils.profile_changed(device.profile, args.old_st_store.profile) then + if device.profile.id ~= args.old_st_store.profile.id or + aqs_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) 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) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 12ee2b3662..cac42e4483 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -64,7 +64,8 @@ function SwitchLifecycleHandlers.driver_switched(driver, device) end function SwitchLifecycleHandlers.info_changed(driver, device, event, args) - if switch_utils.profile_changed(device.profile, args.old_st_store.profile) then + 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, 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 index 5f3205d73b..1caa9737bb 100644 --- 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 @@ -134,6 +134,24 @@ function CameraUtils.build_supported_resolutions(device, max_encoded_pixel_rate, return resolutions 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 = {} diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index c32e131572..f13589ff41 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -47,7 +47,7 @@ function CameraLifecycleHandlers.driver_switched(driver, device) end function CameraLifecycleHandlers.info_changed(driver, device, event, args) - if switch_utils.profile_changed(device.profile, args.old_st_store.profile) then + 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 98a300924f..8542972320 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -238,6 +238,7 @@ function DeviceConfiguration.match_profile(driver, device) 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index ec620eaa65..6eb03b1472 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -147,6 +147,8 @@ SwitchFields.ELECTRICAL_SENSOR_EPS = "__electrical_sensor_eps" --- for an Electrical Sensor EP with a "primary" endpoint, used during device profiling. SwitchFields.ELECTRICAL_TAGS = "__electrical_tags" +SwitchFields.MODULAR_PROFILE_UPDATED = "__modular_profile_updated" + SwitchFields.profiling_data = { POWER_TOPOLOGY = "__power_topology", BATTERY_SUPPORT = "__battery_support", diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 16d602cfc1..0592d9a342 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -325,33 +325,6 @@ function utils.create_multi_press_values_list(size, supportsHeld) return list end -function utils.profile_changed(latest_profile, previous_profile) - if latest_profile.id ~= previous_profile.id then - return true - end - for component_id, synced_component in pairs(latest_profile.components or {}) do - local prev_component = previous_profile.components[component_id] - if prev_component == nil then - return true - end - if #synced_component.capabilities ~= #prev_component.capabilities then - return true - end - -- Build a table of capability IDs from the previous component. Then, use this map to check - -- that all capabilities in the synced component existed in the previous component. - local prev_cap_ids = {} - for _, capability in ipairs(prev_component.capabilities or {}) do - prev_cap_ids[capability.id] = true - end - for _, capability in ipairs(synced_component.capabilities or {}) do - if not prev_cap_ids[capability.id] then - return true - end - end - end - return false -end - function utils.detect_bridge(device) return #utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.AGGREGATOR) > 0 end 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 b5ae89fd7a..e1af3ad52b 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 @@ -16,8 +16,7 @@ local mock_device_ep2 = 2 local mock_device = test.mock_device.build_test_matter_device({ label = "Matter Fan Light", - profile = t_utils.get_profile_definition("fan-modular.yml", - {enabled_optional_capabilities = {{"main", {"fanSpeedPercent", "fanMode"}}}}), + profile = t_utils.get_profile_definition("fan-modular.yml", {}), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -59,40 +58,6 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) -local mock_device_capabilities_disabled = test.mock_device.build_test_matter_device({ - label = "Matter Fan Light", - profile = t_utils.get_profile_definition("fan-modular.yml", - {enabled_optional_capabilities = {{"main", {}}}}), - manufacturer_info = { - vendor_id = 0x0000, - product_id = 0x0000, - }, - matter_version = { - software = 1, - hardware = 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 = mock_device_ep2, - clusters = { - {cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 15}, - }, - device_types = { - {device_type_id = 0x002B, device_type_revision = 1,} -- Fan - } - } - } -}) - local CLUSTER_SUBSCRIBE_LIST ={ clusters.OnOff.attributes.OnOff, clusters.LevelControl.attributes.CurrentLevel, @@ -145,48 +110,16 @@ local function test_init() }) 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) -test.register_coroutine_test( - "Component-capability update without profile ID update should cause re-subscribe in infoChanged handler", function() - local cluster_subscribe_list ={ - clusters.FanControl.attributes.FanModeSequence, - clusters.FanControl.attributes.FanMode, - clusters.FanControl.attributes.PercentCurrent, - } - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_capabilities_disabled) - for i, clus in ipairs(cluster_subscribe_list) do - if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_capabilities_disabled)) end - end - test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed( - {profile = {id = "00000000-1111-2222-3333-000000000004", components = { main = {capabilities={{id="fanSpeedPercent", version=1}, {id="fanMode", version=1}, {id="firmwareUpdate", version=1}, {id="refresh", version=1}}}}}}) - ) - test.socket.matter:__expect_send({mock_device_capabilities_disabled.id, subscribe_request}) - end, - { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } -) - -test.register_coroutine_test( - "No component-capability update an no profile ID update should not cause a re-subscribe in infoChanged handler", function() - local cluster_subscribe_list ={ - clusters.FanControl.attributes.FanModeSequence, - clusters.FanControl.attributes.FanMode, - clusters.FanControl.attributes.PercentCurrent, - } - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_capabilities_disabled) - for i, clus in ipairs(cluster_subscribe_list) do - if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_capabilities_disabled)) end - end - test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed( - {profile = {id = "00000000-1111-2222-3333-000000000004", components = { main = {capabilities={{id="firmwareUpdate", version=1}, {id="refresh", version=1}}}}}}) - ) - end, - { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } -) - - test.register_coroutine_test( "Switch capability should send the appropriate commands", function() test.socket.capability:__queue_receive( From 35a98ca3e899a29b2a479896f83d0f5f45f05756 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:13:16 -0600 Subject: [PATCH 132/538] Merge pull request #2809 from SmartThingsCommunity/revert/component-check-updates Revert Recent Profile Components Check Additions --- .../matter-lock/src/new-matter-lock/init.lua | 33 ++------ .../air_quality_sensor_utils/utils.lua | 24 ++---- .../sub_drivers/air_quality_sensor/init.lua | 3 +- .../SmartThings/matter-switch/src/init.lua | 3 +- .../sub_drivers/camera/camera_utils/utils.lua | 18 +++++ .../src/sub_drivers/camera/init.lua | 2 +- .../src/switch_utils/device_configuration.lua | 1 + .../matter-switch/src/switch_utils/fields.lua | 2 + .../matter-switch/src/switch_utils/utils.lua | 27 ------- .../src/test/test_matter_light_fan.lua | 81 ++----------------- 10 files changed, 45 insertions(+), 149 deletions(-) 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 a91790cedb..11aa2b0884 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -22,6 +22,8 @@ local MIN_EPOCH_S = 0 local MAX_EPOCH_S = 0xffffffff local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 +local MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED" + local RESPONSE_STATUS_MAP = { [DoorLock.types.DlStatus.SUCCESS] = "success", [DoorLock.types.DlStatus.FAILURE] = "failure", @@ -201,6 +203,7 @@ local function match_profile_modular(driver, device) table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) + device:set_field(MODULAR_PROFILE_UPDATED, true) end local function match_profile_switch(driver, device) @@ -238,37 +241,11 @@ local function match_profile_switch(driver, device) device:try_update_metadata({profile = profile_name}) end -local function profile_changed(latest_profile, previous_profile) - if latest_profile.id ~= previous_profile.id then - return true - end - for component_id, synced_component in pairs(latest_profile.components or {}) do - local prev_component = previous_profile.components[component_id] - if prev_component == nil then - return true - end - if #synced_component.capabilities ~= #prev_component.capabilities then - return true - end - -- Build a table of capability IDs from the previous component. Then, use this map to check - -- that all capabilities in the synced component existed in the previous component. - local prev_cap_ids = {} - for _, capability in ipairs(prev_component.capabilities or {}) do - prev_cap_ids[capability.id] = true - end - for _, capability in ipairs(synced_component.capabilities or {}) do - if not prev_cap_ids[capability.id] then - return true - end - end - end - return false -end - local function info_changed(driver, device, event, args) - if not profile_changed(device.profile, args.old_st_store.profile) then + if device.profile.id == args.old_st_store.profile.id and not device:get_field(MODULAR_PROFILE_UPDATED) then return end + device:set_field(MODULAR_PROFILE_UPDATED, nil) for cap_id, attributes in pairs(subscribed_attributes) do if device:supports_capability_by_id(cap_id) then for _, attr in ipairs(attributes) do 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 index 5c1726d6d8..95ca80964c 100644 --- 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 @@ -77,26 +77,17 @@ function AirQualitySensorUtils.set_supported_health_concern_values(device) end end -function AirQualitySensorUtils.profile_changed(latest_profile, previous_profile) - if latest_profile.id ~= previous_profile.id then +function AirQualitySensorUtils.profile_changed(synced_components, prev_components) + if #synced_components ~= #prev_components then return true end - for component_id, synced_component in pairs(latest_profile.components or {}) do - local prev_component = previous_profile.components[component_id] - if prev_component == nil then + 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 - if #synced_component.capabilities ~= #prev_component.capabilities then - return true - end - -- Build a table of capability IDs from the previous component. Then, use this map to check - -- that all capabilities in the synced component existed in the previous component. - local prev_cap_ids = {} - for _, capability in ipairs(prev_component.capabilities or {}) do - prev_cap_ids[capability.id] = true - end - for _, capability in ipairs(synced_component.capabilities or {}) do - if not prev_cap_ids[capability.id] then + for _, capability in pairs(component.capabilities or {}) do + if prev_components[component.id][capability.id] == nil then return true end end @@ -104,5 +95,4 @@ function AirQualitySensorUtils.profile_changed(latest_profile, previous_profile) return false end - return AirQualitySensorUtils 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 index 41c6514b40..98b8430c98 100644 --- 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 @@ -66,7 +66,8 @@ function AirQualitySensorLifecycleHandlers.device_init(driver, device) end function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, args) - if aqs_utils.profile_changed(device.profile, args.old_st_store.profile) then + if device.profile.id ~= args.old_st_store.profile.id or + aqs_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) 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) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 12ee2b3662..cac42e4483 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -64,7 +64,8 @@ function SwitchLifecycleHandlers.driver_switched(driver, device) end function SwitchLifecycleHandlers.info_changed(driver, device, event, args) - if switch_utils.profile_changed(device.profile, args.old_st_store.profile) then + 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, 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 index 5f3205d73b..1caa9737bb 100644 --- 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 @@ -134,6 +134,24 @@ function CameraUtils.build_supported_resolutions(device, max_encoded_pixel_rate, return resolutions 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 = {} diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index c32e131572..f13589ff41 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -47,7 +47,7 @@ function CameraLifecycleHandlers.driver_switched(driver, device) end function CameraLifecycleHandlers.info_changed(driver, device, event, args) - if switch_utils.profile_changed(device.profile, args.old_st_store.profile) then + 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 98a300924f..8542972320 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -238,6 +238,7 @@ function DeviceConfiguration.match_profile(driver, device) 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index ec620eaa65..6eb03b1472 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -147,6 +147,8 @@ SwitchFields.ELECTRICAL_SENSOR_EPS = "__electrical_sensor_eps" --- for an Electrical Sensor EP with a "primary" endpoint, used during device profiling. SwitchFields.ELECTRICAL_TAGS = "__electrical_tags" +SwitchFields.MODULAR_PROFILE_UPDATED = "__modular_profile_updated" + SwitchFields.profiling_data = { POWER_TOPOLOGY = "__power_topology", BATTERY_SUPPORT = "__battery_support", diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 16d602cfc1..0592d9a342 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -325,33 +325,6 @@ function utils.create_multi_press_values_list(size, supportsHeld) return list end -function utils.profile_changed(latest_profile, previous_profile) - if latest_profile.id ~= previous_profile.id then - return true - end - for component_id, synced_component in pairs(latest_profile.components or {}) do - local prev_component = previous_profile.components[component_id] - if prev_component == nil then - return true - end - if #synced_component.capabilities ~= #prev_component.capabilities then - return true - end - -- Build a table of capability IDs from the previous component. Then, use this map to check - -- that all capabilities in the synced component existed in the previous component. - local prev_cap_ids = {} - for _, capability in ipairs(prev_component.capabilities or {}) do - prev_cap_ids[capability.id] = true - end - for _, capability in ipairs(synced_component.capabilities or {}) do - if not prev_cap_ids[capability.id] then - return true - end - end - end - return false -end - function utils.detect_bridge(device) return #utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.AGGREGATOR) > 0 end 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 b5ae89fd7a..e1af3ad52b 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 @@ -16,8 +16,7 @@ local mock_device_ep2 = 2 local mock_device = test.mock_device.build_test_matter_device({ label = "Matter Fan Light", - profile = t_utils.get_profile_definition("fan-modular.yml", - {enabled_optional_capabilities = {{"main", {"fanSpeedPercent", "fanMode"}}}}), + profile = t_utils.get_profile_definition("fan-modular.yml", {}), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -59,40 +58,6 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) -local mock_device_capabilities_disabled = test.mock_device.build_test_matter_device({ - label = "Matter Fan Light", - profile = t_utils.get_profile_definition("fan-modular.yml", - {enabled_optional_capabilities = {{"main", {}}}}), - manufacturer_info = { - vendor_id = 0x0000, - product_id = 0x0000, - }, - matter_version = { - software = 1, - hardware = 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 = mock_device_ep2, - clusters = { - {cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 15}, - }, - device_types = { - {device_type_id = 0x002B, device_type_revision = 1,} -- Fan - } - } - } -}) - local CLUSTER_SUBSCRIBE_LIST ={ clusters.OnOff.attributes.OnOff, clusters.LevelControl.attributes.CurrentLevel, @@ -145,48 +110,16 @@ local function test_init() }) 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) -test.register_coroutine_test( - "Component-capability update without profile ID update should cause re-subscribe in infoChanged handler", function() - local cluster_subscribe_list ={ - clusters.FanControl.attributes.FanModeSequence, - clusters.FanControl.attributes.FanMode, - clusters.FanControl.attributes.PercentCurrent, - } - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_capabilities_disabled) - for i, clus in ipairs(cluster_subscribe_list) do - if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_capabilities_disabled)) end - end - test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed( - {profile = {id = "00000000-1111-2222-3333-000000000004", components = { main = {capabilities={{id="fanSpeedPercent", version=1}, {id="fanMode", version=1}, {id="firmwareUpdate", version=1}, {id="refresh", version=1}}}}}}) - ) - test.socket.matter:__expect_send({mock_device_capabilities_disabled.id, subscribe_request}) - end, - { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } -) - -test.register_coroutine_test( - "No component-capability update an no profile ID update should not cause a re-subscribe in infoChanged handler", function() - local cluster_subscribe_list ={ - clusters.FanControl.attributes.FanModeSequence, - clusters.FanControl.attributes.FanMode, - clusters.FanControl.attributes.PercentCurrent, - } - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_capabilities_disabled) - for i, clus in ipairs(cluster_subscribe_list) do - if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_capabilities_disabled)) end - end - test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed( - {profile = {id = "00000000-1111-2222-3333-000000000004", components = { main = {capabilities={{id="firmwareUpdate", version=1}, {id="refresh", version=1}}}}}}) - ) - end, - { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } -) - - test.register_coroutine_test( "Switch capability should send the appropriate commands", function() test.socket.capability:__queue_receive( From 48451c814f036d0c718d0ab865ed21e5e299d7af Mon Sep 17 00:00:00 2001 From: cjswedes Date: Tue, 24 Feb 2026 15:53:44 -0600 Subject: [PATCH 133/538] Improve Zigbee driver test coverage --- .../test_MultiIR_air_quality_detector.lua | 194 ++++++++++++++++++ .../src/test/test_shus_mattress.lua | 37 ++++ .../zigbee-button/src/aqara/init.lua | 1 + .../src/test/test_aqara_button.lua | 35 ++++ .../src/test/test_frient_contact_sensor.lua | 62 +++--- .../src/test/test_smartsense_multi.lua | 67 ++++++ .../test/test_smartthings_multi_sensor.lua | 32 +++ .../src/test/test_zigbee_accessory_dimmer.lua | 77 +++++++ .../test_zigbee_battery_accessory_dimmer.lua | Bin 22479 -> 28081 bytes .../zigbee-fan/src/test/test_fan_light.lua | 42 ++++ .../src/test/test_aqara_sensor.lua | 25 +++ .../test/test_frient_air_quality_sensor.lua | 35 ++++ .../src/test/test_frient_sensor.lua | 33 +++ .../test/test_illuminance_sensor_aqara.lua | 52 +++++ .../zigbee-lock/src/test/test_zigbee_lock.lua | 84 ++++++++ .../zigbee-lock/src/test/test_zigbee_yale.lua | 94 +++++++++ .../test/test_aqara_motion_illuminance.lua | 82 ++++++++ .../src/test/test_frient_motion_sensor.lua | 77 ++++--- .../test/test_frient_motion_sensor_pro.lua | 79 +++---- .../src/test/test_gator_motion.lua | 16 ++ .../src/test/test_ikea_motion.lua | 25 +++ .../src/test/test_thirdreality_sensor.lua | 19 ++ .../src/test/test_zigbee_power_meter_1p.lua | 96 +++++++++ ...e_power_meter_consumption_report_sihas.lua | 36 ++++ .../test/test_zigbee_power_meter_frient.lua | 102 +++++++++ .../src/test/test_zigbee_presence_sensor.lua | 93 +++++++++ .../test_frient_zigbee_range_extender.lua | 32 +++ .../src/test/test_frient_siren.lua | 116 +++++++++++ .../src/test/test_frient_siren_tamper.lua | 54 +++++ .../src/test/test_zigbee_siren.lua | 57 +++++ .../src/test/test_aqara_gas_detector.lua | 60 ++++++ .../src/test/test_aqara_smoke_detector.lua | 62 ++++-- .../src/test/test_frient_heat_detector.lua | 32 +++ .../src/test/test_frient_smoke_detector.lua | 90 ++++++++ .../src/test/test_aqara_light.lua | 34 +++ .../src/test/test_ge_link_bulb.lua | 34 +++ .../src/test/test_jasco_switch.lua | 31 +++ .../src/test/test_laisiao_bath_heather.lua | 17 ++ .../src/test/test_multi_switch_no_master.lua | 41 ++++ .../src/test/test_wallhero_switch.lua | 32 ++- .../src/test/test_sinope_thermostat.lua | 27 +++ .../test_stelpro_ki_zigbee_thermostat.lua | 46 +++++ .../src/test/test_zenwithin_thermostat.lua | 40 ++++ .../zigbee-valve/src/test/test_ezex_valve.lua | 16 ++ .../src/test/test_sinope_valve.lua | 8 + .../src/test/test_leaksmart_water.lua | 30 +-- .../test_thirdreality_water_leak_sensor.lua | 8 + .../test/test_thirdreality_watering_kit.lua | 46 +++++ .../test_zigbee_window_shade_battery_ikea.lua | 110 ++++++++++ ...est_zigbee_window_shade_battery_yoolax.lua | 105 ++++++++++ .../test_zigbee_window_treatment_somfy.lua | 92 +++++++++ .../test_zigbee_window_treatment_vimar.lua | 118 +++++++++++ 52 files changed, 2684 insertions(+), 149 deletions(-) create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua diff --git a/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua b/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua index 883a3dc5a5..09f539b53b 100755 --- a/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua +++ b/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua @@ -227,4 +227,198 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "AQI moderate (51-100) emits moderate airQualityHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 75 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC5, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "moderate"}))) + end +) + +test.register_coroutine_test( + "AQI slightlyUnhealthy (101-150) emits slightlyUnhealthy airQualityHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 125 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC5, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "slightlyUnhealthy"}))) + end +) + +test.register_coroutine_test( + "AQI unhealthy (151-200) emits unhealthy airQualityHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 175 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC5, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "unhealthy"}))) + end +) + +test.register_coroutine_test( + "AQI veryUnhealthy (201-300) emits veryUnhealthy airQualityHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 250 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC5, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "veryUnhealthy"}))) + end +) + +test.register_coroutine_test( + "AQI hazardous (>=301) emits hazardous airQualityHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 350 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC5, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "hazardous"}))) + end +) + +test.register_coroutine_test( + "carbonDioxide moderate (1501-2500) emits moderate carbonDioxideHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 2000 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC3, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.carbonDioxideMeasurement.carbonDioxide({value = 2000, unit = "ppm"}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "moderate"}))) + end +) + +test.register_coroutine_test( + "carbonDioxide unhealthy (>2500) emits unhealthy carbonDioxideHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 3000 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC3, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.carbonDioxideMeasurement.carbonDioxide({value = 3000, unit = "ppm"}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "unhealthy"}))) + end +) + +test.register_coroutine_test( + "pm2.5 moderate emits moderate fineDustHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 90 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC1, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fineDustSensor.fineDustLevel({value = 90}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fineDustHealthConcern.fineDustHealthConcern({value = "moderate"}))) + end +) + +test.register_coroutine_test( + "pm2.5 unhealthy (>=115) emits unhealthy fineDustHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 120 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC1, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fineDustSensor.fineDustLevel({value = 120}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fineDustHealthConcern.fineDustHealthConcern({value = "unhealthy"}))) + end +) + +test.register_coroutine_test( + "pm1.0 unhealthy (>100) emits unhealthy veryFineDustHealthConcern", + function() + local attr_report_data = { + { 0x0001, data_types.Uint16.ID, 150 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC1, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.veryFineDustSensor.veryFineDustLevel({value = 150}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern({value = "unhealthy"}))) + end +) + +test.register_coroutine_test( + "pm10 unhealthy (>150) emits unhealthy dustHealthConcern", + function() + local attr_report_data = { + { 0x0002, data_types.Uint16.ID, 200 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC1, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.dustSensor.dustLevel({value = 200}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.dustHealthConcern.dustHealthConcern({value = "unhealthy"}))) + end +) + +test.register_coroutine_test( + "tvoc good (<600) emits good tvocHealthConcern", + function() + local attr_report_data = { + { 0x0001, data_types.SinglePrecisionFloat.ID, SinglePrecisionFloat(0, 8, 0.953125) } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC2, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tvocMeasurement.tvocLevel({value = 500.0, unit = "ug/m3"}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tvocHealthConcern.tvocHealthConcern({value = "good"}))) + end +) + test.run_registered_tests() 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 7e5ade0cf5..a3227b04bc 100755 --- a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua +++ b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua @@ -398,6 +398,21 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Device reported yoga 3 and driver emit custom_capabilities.yoga.state.both()", + function() + local attr_report_data = { + { 0x0008, 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", + custom_capabilities.yoga.state.both())) + end +) + test.register_coroutine_test( "Device reported yoga 2 and driver emit custom_capabilities.yoga.state.right()", function() @@ -910,4 +925,26 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "capability left_control backControl soft emits idle event after delay", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="backControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0000, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftback.soft())) + test.wait_for_events() + + test.mock_time.advance_time(1) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftback("idle", { visibility = { displayed = false }}))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/aqara/init.lua b/drivers/SmartThings/zigbee-button/src/aqara/init.lua index 7467fcfe42..b9545e4721 100644 --- a/drivers/SmartThings/zigbee-button/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-button/src/aqara/init.lua @@ -85,6 +85,7 @@ local function battery_level_handler(driver, device, value, zb_rx) batteryLevel = "warning" end + -- Note that all aqara buttons use batteryLevel and not battery capability. 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 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 67314ef651..b2d5a986d6 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua @@ -21,6 +21,21 @@ local PRIVATE_ATTRIBUTE_ID_ALIVE = 0x00F7 local MODE_CHANGE = "stse.allowOperationModeChange" local COMP_LIST = { "button1", "button2", "all" } + +local mock_device_h1_single = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-single-button-mode.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.remote.b18ac1", + server_clusters = { 0x0001, 0x0012 } + } + } + } +) + local mock_device_e1 = test.mock_device.build_test_zigbee_device( { profile = t_utils.get_profile_definition("one-button-batteryLevel.yml"), @@ -51,6 +66,7 @@ local mock_device_h1_double_rocker = 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_h1_single) test.mock_device.add_test_device(mock_device_e1) test.mock_device.add_test_device(mock_device_h1_double_rocker) end @@ -286,4 +302,23 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Handle added lifecycle - H1 single rocker (sets mode=1)", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_h1_single.id, "added" }) + test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }))) + test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", + capabilities.button.button.pushed({ state_change = false }))) + test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", + capabilities.batteryLevel.battery.normal())) + test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", + capabilities.batteryLevel.type("CR2450"))) + test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", + capabilities.batteryLevel.quantity(1))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua index bd7e7512dd..b73f7ee3fe 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua @@ -165,36 +165,6 @@ test.register_message_test( } ) --- test.register_coroutine_test( --- "Health check should check all relevant attributes", --- function() --- test.wait_for_events() - --- test.mock_time.advance_time(50000) -- battery is 21600 for max reporting interval --- test.socket.zigbee:__set_channel_ordering("relaxed") - --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- PowerConfiguration.attributes.BatteryVoltage:read(mock_device) --- } --- ) - --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- IASZone.attributes.ZoneStatus:read(mock_device) --- } --- ) --- end, --- { --- test_init = function() --- test.mock_device.add_test_device(mock_device) --- test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "health_check") --- end --- } --- ) - test.register_message_test( "Refresh should read all necessary attributes", { @@ -260,4 +230,36 @@ test.register_message_test( } ) +test.register_message_test( + "ZoneStatusChangeNotification should be handled: contact/open", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0001, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) + } + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: contact/closed", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua index 035c49d27f..d5482e7d6e 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua @@ -5,6 +5,9 @@ local test = require "integration_test" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local t_utils = require "integration_test.utils" local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" + +local IASZone = clusters.IASZone local SMARTSENSE_PROFILE_ID = 0xFC01 local MFG_CODE = 0x110A @@ -426,4 +429,68 @@ test.register_coroutine_test( end ) +test.register_message_test( + "ZoneStatusChangeNotification should generate contact event when garageSensor not set: open", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0001, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) + } + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should generate contact event when garageSensor not set: closed", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + } + } +) + +test.register_message_test( + "ZoneStatus attr report should generate contact event when garageSensor not set: open", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0001) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) + } + } +) + +test.register_message_test( + "ZoneStatus attr report should generate contact event when garageSensor not set: closed", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua index 5ef40d5aaf..c86078f224 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua @@ -219,6 +219,38 @@ test.register_coroutine_test( end ) +test.register_message_test( + "ZoneStatusChangeNotification should generate contact event when garageSensor not set: open", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0001, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) + } + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should generate contact event when garageSensor not set: closed", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + } + } +) + test.register_coroutine_test( "Refresh necessary attributes", function() 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 90d29656d8..751fc1bee9 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 @@ -217,4 +217,81 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "On command when current level is 0 should reset level then toggle switch", + function() + mock_device:set_field("current_level", 0) + test.socket.zigbee:__queue_receive({ mock_device.id, OnOff.server.commands.On.build_test_rx(mock_device) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(10))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + end +) + +test.register_coroutine_test( + "Step command(MoveStepMode.DOWN) to level 0 should emit switch off and level 0", + function() + mock_device:set_field("current_level", 10) + local step_command = Level.server.commands.Step.build_test_rx(mock_device, Level.types.MoveStepMode.DOWN, 0x00, + 0x0000, 0x00, 0x00) + local frm_ctrl = FrameCtrl(0x01) + step_command.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({ mock_device.id, step_command }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(0))) + end +) + +test.register_coroutine_test( + "Step command(MoveStepMode.UP) when device is off should emit switch on", + function() + mock_device:set_field("current_status", "off") + local step_command = Level.server.commands.Step.build_test_rx(mock_device, Level.types.MoveStepMode.UP, 0x00, + 0x0000, 0x00, 0x00) + local frm_ctrl = FrameCtrl(0x01) + step_command.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({ mock_device.id, step_command }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(100))) + end +) + +test.register_coroutine_test( + "Capability command setLevel should be handled", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 50 } } }) + test.wait_for_events() + test.mock_time.advance_time(1) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(50))) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Capability command setLevel 0 should restore previous level", + function() + mock_device:set_field("current_level", 30) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 0 } } }) + test.wait_for_events() + test.mock_time.advance_time(1) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(30))) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "device added lifecycle should initialize device state", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(100))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "held"}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua index b16fe8cd11536c8bf2e155496b8b72e4c2497dc6..243f55946688f45d6e8bfe18217e6f2cf025daf2 100644 GIT binary patch delta 1013 zcmb7DOKTHR6eh)rEp5}#nh4tFOb8Oem>CcX9SyAu)7Kj1>$xYIk6OkY94)eQHZ^PTfO?)z7sH=jJOx2qTo z9mBWjvBk1pR#i~6rp1T}WgQfw(UA2Ttk;PS8fg&?s0AWeb)%`(fHWxU9-G7>4D4EK zoie2khK|12xD<`TGQa5bGK2TFs0_Sz!9i2xHF=e4lx?`P5Mm`96DDwe(u?0N4x<4> z_%gPFUg0j@6Mvlp;FycXItSs?IxFvAm9lzwoXkK(F`sZCzK;g*;c%G4n79@g>XSqq z%_h++CGmdJho>(@;x*Eu3IVxRBQ?;dN|X&nBQ9C0Q$}Tt-XYK}w3y7$mpP7Kq<%al zjN^|hVGIaA@o9Ph{n-%=mtywpfsg|kf;Qd=2C*eYa3_5vRWTe5s(j%N3rD0G@wRwS z;9WsMHB2bY7cM`^wfjU7H&b)?IW@ZVG55lQ-b;gcBKy4ZSJYy$W24eEO`^M&{L{|8 zc_#zCMyk}b*de?8Xii?k-?4tKa}sx^)7-L6{!Q4LV?_#`KWN`oqeXf)<^k$j$i*lY zQOLx%=5o(Gd%hVcPQ`E{yNWx$5qwlQyOiUUOzsXNU}L&zf_%BUcqqO+7Wwk^>_Xw@ za;323+UsQ5m0DHp@MaEk-rp9H#54b$<;LJ3HYQh&N+Ps*8f=a1qg#m?oSu5@{C`c% wI@yd1xn8L{bar3mu1>%?am?r}v}CPGz$CI#C;X*4+I@(>^WcMmAGfRiKct;m9{>OV delta 13 VcmdmZoALa5#tl2eC(q6m0{}6E27Ukl diff --git a/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua index 0fa987e35a..1a61602c1e 100644 --- a/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua +++ b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua @@ -432,4 +432,46 @@ test.register_message_test( } ) +test.register_message_test( + "Fan switch on command from main component", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switch", component = "main", command = "on", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:write(mock_base_device, 1) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + +test.register_message_test( + "Fan switch off command from main component", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switch", component = "main", command = "off", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:write(mock_base_device, 0x00) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + test.run_registered_tests() 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 497395ac55..5ced35a0d3 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 @@ -18,11 +18,17 @@ 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 cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" local PowerConfiguration = clusters.PowerConfiguration local TemperatureMeasurement = clusters.TemperatureMeasurement local RelativeHumidity = clusters.RelativeHumidity +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F + local mock_device = test.mock_device.build_test_zigbee_device( { profile = t_utils.get_profile_definition("humidity-temp-battery-aqara.yml"), @@ -256,4 +262,23 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Added handler should send manufacturer attribute and initialize device state", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + 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.temperatureMeasurement.temperature({ value = 0, unit = "C" }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.relativeHumidityMeasurement.humidity(0))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.batteryLevel.battery("normal"))) + test.wait_for_events() + end +) + test.run_registered_tests() 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 index e985dafce5..063afbdf9f 100644 --- 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 @@ -123,6 +123,7 @@ test.register_message_test( test.register_coroutine_test( "Configure should configure all necessary attributes", function() + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) test.socket.zigbee:__set_channel_ordering("relaxed") @@ -191,6 +192,26 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + + --refresh happens after configure + test.mock_time.advance_time(5) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_manufacturer_specific_attribute(mock_device, Frient_VOCMeasurement.ID, Frient_VOCMeasurement.attributes.MeasuredValue.ID, Frient_VOCMeasurement.ManufacturerSpecificCode):to_endpoint(0x26) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(0x26) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + HumidityMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(0x26) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:read(mock_device) + }) end ) @@ -291,4 +312,18 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Added handler should initialize VOC and air quality state", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.airQualitySensor.airQuality({ value = 0 }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tvocHealthConcern.tvocHealthConcern({ value = "good" }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tvocMeasurement.tvocLevel({ value = 0, unit = "ppb" }))) + test.wait_for_events() + end +) + 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 5b9ef77634..7d925ae94d 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 @@ -105,6 +105,7 @@ test.register_message_test( test.register_coroutine_test( "Configure should configure all necessary attributes", function() + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) test.socket.zigbee:__set_channel_ordering("relaxed") test.socket.zigbee:__expect_send({ @@ -141,6 +142,22 @@ test.register_coroutine_test( TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 0x001E, 0x0E10, 100) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + + test.mock_time.advance_time(5) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + HumidityMeasurement.attributes.MeasuredValue:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) + }) + test.wait_for_events() end ) @@ -190,6 +207,7 @@ test.register_message_test( test.register_coroutine_test( "info_changed to check for necessary preferences settings: Temperature Sensitivity", function() + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") local updates = { preferences = { temperatureSensitivity = 0.9, @@ -217,6 +235,21 @@ test.register_coroutine_test( ) }) test.wait_for_events() + + test.mock_time.advance_time(5) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + HumidityMeasurement.attributes.MeasuredValue:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) + }) + test.wait_for_events() end ) diff --git a/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua index 557d4e2345..81fee9249f 100644 --- a/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua +++ b/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua @@ -9,6 +9,10 @@ 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 messages = require "st.zigbee.messages" +local zb_const = require "st.zigbee.constants" +local write_attribute_response = require "st.zigbee.zcl.global_commands.write_attribute_response" +local zcl_messages = require "st.zigbee.zcl" test.add_package_capability("detectionFrequency.yaml") local IlluminanceMeasurement = clusters.IlluminanceMeasurement @@ -19,8 +23,10 @@ local detectionFrequency = capabilities["stse.detectionFrequency"] local PRIVATE_CLUSTER_ID = 0xFCC0 local PRIVATE_ATTRIBUTE_ID = 0x0009 local MFG_CODE = 0x115F +local FREQUENCY_ATTRIBUTE_ID = 0x0000 local FREQUENCY_DEFAULT_VALUE = 5 +local FREQUENCY_PREF = "frequencyPref" local mock_device = test.mock_device.build_test_zigbee_device( { @@ -131,4 +137,50 @@ test.register_message_test( } ) +local function build_write_attr_res(cluster, status) + local addr_header = messages.AddressHeader( + mock_device:get_short_address(), + mock_device.fingerprinted_endpoint_id, + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + zb_const.HA_PROFILE_ID, + cluster + ) + local write_attribute_body = write_attribute_response.WriteAttributeResponse(status, {}) + local zcl_header = zcl_messages.ZclHeader({ + cmd = data_types.ZCLCommandId(write_attribute_body.ID) + }) + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_header, + zcl_body = write_attribute_body + }) + return messages.ZigbeeMessageRx({ + address_header = addr_header, + body = message_body + }) +end + +test.register_coroutine_test( + "Handle setDetectionFrequency capability command", + function() + local frequency = 10 + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "stse.detectionFrequency", component = "main", command = "setDetectionFrequency", args = { frequency } } }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, FREQUENCY_ATTRIBUTE_ID, + MFG_CODE, data_types.Uint16, frequency) }) + end +) + +test.register_coroutine_test( + "Handle write attr res on PRIVATE_CLUSTER_ID emits detectionFrequency", + function() + mock_device:set_field(FREQUENCY_PREF, FREQUENCY_DEFAULT_VALUE) + test.socket.zigbee:__queue_receive({ mock_device.id, + build_write_attr_res(PRIVATE_CLUSTER_ID, 0x00) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + detectionFrequency.detectionFrequency(FREQUENCY_DEFAULT_VALUE, { visibility = { displayed = false } }))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua index 3ed037cd54..2fd55fd3f0 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua @@ -771,4 +771,88 @@ test.register_coroutine_test( end ) +test.register_message_test( + "Alarm code 0 should generate lock unknown event", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Alarm.client.commands.Alarm.build_test_rx(mock_device, 0x00, DoorLock.ID) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lock.lock.unknown()) + } + } +) + +test.register_message_test( + "Alarm code 1 should generate lock unknown event", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Alarm.client.commands.Alarm.build_test_rx(mock_device, 0x01, DoorLock.ID) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lock.lock.unknown()) + } + } +) + +test.register_message_test( + "Pin response for unoccupied slot with no existing code should generate unset event", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 0x05, + DoorLockUserStatus.OCCUPIED_DISABLED, + DoorLockUserType.UNRESTRICTED, + "" + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("5 unset", + { data = { codeName = "Code 5" }, state_change = true })) + } + } +) + +test.register_coroutine_test( + "Pin response for already-set slot should use changed change type", + function() + init_code_slot(1, "Code 1", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } }))) + test.wait_for_events() + + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 0x01, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 changed", { data = { codeName = "Code 1" }, state_change = true }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } }))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua index 75ad49a1f5..931a4b143c 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua @@ -319,4 +319,98 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Setting a user code for a slot that is empty should indicate failure and unset", + function() + test.timer.__create_and_queue_test_time_advance_timer(4, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "setCode", args = { 1, "1234", "test" } } }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) + } + ) + test.wait_for_events() + test.mock_time.advance_time(4) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 1) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 1, + DoorLockUserStatus.OCCUPIED_DISABLED, + DoorLockUserType.UNRESTRICTED, + "" + ) + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 failed", { state_change = true }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 is not set", { state_change = true }))) + end +) + +test.register_coroutine_test( + "Pin response for already-set slot without pending operation should use changed change type", + function() + init_code_slot(1, "initialName", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "initialName"}), { visibility = { displayed = false }}))) + test.wait_for_events() + + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 1, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 changed", { data = { codeName = "initialName" }, state_change = true }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "initialName"}), { visibility = { displayed = false }}))) + end +) + +test.register_coroutine_test( + "Pin response for already-set slot that is now empty should delete the code", + function() + init_code_slot(1, "initialName", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "initialName"}), { visibility = { displayed = false }}))) + test.wait_for_events() + + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 1, + DoorLockUserStatus.OCCUPIED_DISABLED, + DoorLockUserType.UNRESTRICTED, + "" + ) + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 deleted", { data = { codeName = "initialName" }, state_change = true }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({}), { visibility = { displayed = false }}))) + end +) + test.run_registered_tests() 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 2580af0ceb..c8bb2c20f0 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 @@ -10,6 +10,10 @@ local data_types = require "st.zigbee.data_types" local capabilities = require "st.capabilities" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local t_utils = require "integration_test.utils" +local messages = require "st.zigbee.messages" +local zb_const = require "st.zigbee.constants" +local write_attribute_response = require "st.zigbee.zcl.global_commands.write_attribute_response" +local zcl_messages = require "st.zigbee.zcl" test.add_package_capability("sensitivityAdjustment.yaml") test.add_package_capability("detectionFrequency.yaml") @@ -46,6 +50,29 @@ local function test_init() test.set_test_init_function(test_init) +local function build_write_attr_res(cluster, status) + local addr_header = messages.AddressHeader( + mock_device:get_short_address(), + mock_device.fingerprinted_endpoint_id, + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + zb_const.HA_PROFILE_ID, + cluster + ) + local write_attribute_body = write_attribute_response.WriteAttributeResponse(status, {}) + local zcl_header = zcl_messages.ZclHeader({ + cmd = data_types.ZCLCommandId(write_attribute_body.ID) + }) + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_header, + zcl_body = write_attribute_body + }) + return messages.ZigbeeMessageRx({ + address_header = addr_header, + body = message_body + }) +end + test.register_coroutine_test( "Handle added lifecycle", function() @@ -123,4 +150,59 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Motion detected twice cancels existing timer and creates a new one", + function() + local detect_duration = PREF_FREQUENCY_VALUE_DEFAULT + -- Pre-register two timers: first will be cancelled, second will fire + test.timer.__create_and_queue_test_time_advance_timer(detect_duration, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(detect_duration, "oneshot") + local attr_report_data = { + { MOTION_ILLUMINANCE_ATTRIBUTE_ID, data_types.Int32.ID, 0x0001006E } -- 65646 + } + -- First motion event + 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.motionSensor.motion.active()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance(110)) + ) + test.wait_for_events() + -- Second motion event before first timer fires - cancels first timer + 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.motionSensor.motion.active()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance(110)) + ) + test.wait_for_events() + -- Only the second timer fires + test.mock_time.advance_time(detect_duration) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.motionSensor.motion.inactive())) + end +) + +test.register_coroutine_test( + "WriteAttributeResponse with PREF_FREQUENCY_KEY updates detection frequency", + function() + mock_device:set_field(PREF_CHANGED_KEY, PREF_FREQUENCY_KEY) + mock_device:set_field(PREF_CHANGED_VALUE, PREF_FREQUENCY_VALUE_DEFAULT) + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_write_attr_res(PRIVATE_CLUSTER_ID, 0x00) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + detectionFrequency.detectionFrequency(PREF_FREQUENCY_VALUE_DEFAULT, {visibility = {displayed = false}}))) + end +) + test.run_registered_tests() 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 89d014dc90..3e3acf69e2 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 @@ -72,39 +72,6 @@ test.register_message_test( } ) --- test.register_coroutine_test( --- "Health check should check all relevant attributes", --- function() --- test.wait_for_events() --- test.mock_time.advance_time(50000) --- test.socket.zigbee:__set_channel_ordering("relaxed") --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- IASZone.attributes.ZoneStatus:read(mock_device) --- } --- ) --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- OccupancySensing.attributes.Occupancy:read(mock_device):to_endpoint(OCCUPANCY_ENDPOINT) --- } --- ) --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- PowerConfiguration.attributes.BatteryVoltage:read(mock_device):to_endpoint(POWER_CONFIGURATION_ENDPOINT) --- } --- ) --- end, --- { --- test_init = function() --- test.mock_device.add_test_device(mock_device) --- test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "health_check") --- end --- } --- ) - test.register_coroutine_test( "Refresh should read all necessary attributes", function() @@ -130,6 +97,7 @@ test.register_coroutine_test( ) test.wait_for_events() + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) test.socket.zigbee:__expect_send({ @@ -227,9 +195,52 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + -- Advance time to trigger the do_refresh call scheduled in do_configure + test.wait_for_events() + test.mock_time.advance_time(5) + test.socket.zigbee:__expect_send({ + mock_device.id, + OccupancySensing.attributes.Occupancy:read(mock_device):to_endpoint(OCCUPANCY_ENDPOINT) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:read(mock_device):to_endpoint(POWER_CONFIGURATION_ENDPOINT) + }) end ) +test.register_message_test( + "Occupancy attribute handler emits motion active", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, OccupancySensing.attributes.Occupancy:build_test_attr_report(mock_device, 0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) + } + } +) + +test.register_message_test( + "Occupancy attribute handler emits motion inactive", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, OccupancySensing.attributes.Occupancy:build_test_attr_report(mock_device, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) + } + } +) + test.register_coroutine_test( "infochanged to check for necessary preferences settings: Motion Turn-Off Delay, Motion Turn-On Delay, Movement Threshold in Turn-On Delay", function() 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 9556a6e58e..1f72d365e6 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 @@ -176,51 +176,6 @@ test.register_message_test( } ) --- test.register_coroutine_test( --- "Health check should check all relevant attributes", --- function() --- test.wait_for_events() --- test.mock_time.advance_time(50000) --- test.socket.zigbee:__set_channel_ordering("relaxed") --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- IASZone.attributes.ZoneStatus:read(mock_device):to_endpoint(TAMPER_ENDPOINT) --- } --- ) --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- IlluminanceMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(ILLUMINANCE_ENDPOINT) --- } --- ) --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- OccupancySensing.attributes.Occupancy:read(mock_device):to_endpoint(OCCUPANCY_ENDPOINT) --- } --- ) --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- PowerConfiguration.attributes.BatteryVoltage:read(mock_device):to_endpoint(POWER_CONFIGURATION_ENDPOINT) --- } --- ) --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- TemperatureMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(TEMPERATURE_MEASUREMENT_ENDPOINT) --- } --- ) --- end, --- { --- test_init = function() --- test.mock_device.add_test_device(mock_device) --- test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "health_check") --- end --- } --- ) - test.register_coroutine_test( "Refresh should read all necessary attributes", function() @@ -432,4 +387,38 @@ test.register_coroutine_test( end ) +test.register_message_test( + "IASZone attribute status handler: tamper detected", + { + { + channel = "zigbee", + direction = "receive", + -- ZoneStatus | Bit2: Tamper set to 1 + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0004) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + } +) + +test.register_message_test( + "IASZone attribute status handler: tamper clear", + { + { + channel = "zigbee", + direction = "receive", + -- ZoneStatus | Bit2: Tamper set to 0 + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + } +) + test.run_registered_tests() 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 7f47416e93..3afdbbcaa2 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 @@ -143,4 +143,20 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "ZoneStatusChangeNotification with alarm1 triggers motion active and inactive", + function() + test.timer.__create_and_queue_test_time_advance_timer(120, "oneshot") + 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.motionSensor.motion.active())) + test.mock_time.advance_time(120) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) + end +) + test.run_registered_tests() 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 12e0038c6c..36312c8fdb 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 @@ -223,4 +223,29 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Second OnWithTimedOff cancels existing timer and resets motion", + function() + local frm_ctrl = FrameCtrl(0x01) + -- Pre-register two timers: first will be cancelled, second will fire + test.timer.__create_and_queue_test_time_advance_timer(0x0708/10, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(0x0708/10, "oneshot") + -- First motion event + local first_cmd = OnOff.server.commands.OnWithTimedOff.build_test_rx(mock_device, 0x00, 0x0708, 0x0000) + first_cmd.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({mock_device.id, first_cmd}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) + test.wait_for_events() + -- Second motion event before first timer fires - cancels first timer + local second_cmd = OnOff.server.commands.OnWithTimedOff.build_test_rx(mock_device, 0x00, 0x0708, 0x0000) + second_cmd.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({mock_device.id, second_cmd}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) + test.wait_for_events() + -- Only the second timer fires + test.mock_time.advance_time(180) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) + end +) + test.run_registered_tests() 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 ddeaf77ce4..7a1655fc41 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 @@ -140,4 +140,23 @@ test.register_coroutine_test( end ) +test.register_message_test( + "Handle added lifecycle - reads ApplicationVersion", + { + { + channel = "device_lifecycle", + direction = "receive", + message = {mock_device1.id, "added"} + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device1.id, + Basic.attributes.ApplicationVersion:read(mock_device1) + } + } + } +) + test.run_registered_tests() 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 index 5308e11ee2..a148df5b1e 100644 --- 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 @@ -7,6 +7,7 @@ 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 constants = require "st.zigbee.constants" -- Mock out globals @@ -222,4 +223,99 @@ test.register_coroutine_test( end ) +test.register_message_test( + "resetEnergyMeter command should send OnOff On to reset device", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "energyMeter", component = "main", command = "resetEnergyMeter", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, clusters.OnOff.server.commands.On(mock_device) } + } + } +) + +test.register_coroutine_test( + "refresh capability command should read device attributes", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered: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, + cluster_base.read_attribute(mock_device, data_types.ClusterId(SimpleMetering.ID), data_types.AttributeId(0x0001)) + }) + 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) + }) + end +) + +test.register_coroutine_test( + "energy handler resets offset when reading is below stored offset", + function() + -- Set an offset larger than the incoming value (100 raw / 100 = 1.0 kWh, offset = 5.0) + mock_device:set_field(constants.ENERGY_METER_OFFSET, 5.0, {persist = true}) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 100) + }) + -- Offset resets to 0; raw_value_kilowatts = 1.0 - 0 = 1.0; no powerConsumptionReport (delta_tick < 15 min) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.0, unit = "kWh"})) + ) + end +) + +test.register_coroutine_test( + "energy handler resets save tick when timer has slipped beyond 30 minutes", + function() + -- Advance time > 30 min so that curr_save_tick + 15*60 < os.time() is true + test.timer.__create_and_queue_test_time_advance_timer(40*60, "oneshot") + test.mock_time.advance_time(40*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 100) + }) + -- raw_value = 100, divisor = 100, kWh = 1.0, watts = 1000.0; first report: deltaEnergy = 0.0 + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({energy = 1000.0, deltaEnergy = 0.0})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.0, unit = "kWh"})) + ) + 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_consumption_report_sihas.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua index a4b8b8c037..2949961fb1 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua @@ -26,6 +26,7 @@ local zb_const = require "st.zigbee.constants" local zcl_messages = require "st.zigbee.zcl" local data_types = require "st.zigbee.data_types" local Status = require "st.zigbee.generated.types.ZclStatus" +local constants = require "st.zigbee.constants" local mock_device = test.mock_device.build_test_zigbee_device( @@ -228,6 +229,41 @@ test.register_coroutine_test( end } ) +test.register_coroutine_test( + "energy handler resets shinasystems offset when reading is below stored offset", + function() + -- divisor=1000; raw_value=100 -> 0.1 kWh; offset=0.5 -> 0.1 < 0.5 triggers reset + mock_device:set_field(constants.ENERGY_METER_OFFSET, 0.5, {persist = true}) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 100) + }) + -- offset resets to 0; raw_value_kilowatts = 0.1; no powerConsumptionReport (delta_tick < 15 min) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 0.1, unit = "kWh"})) + ) + end +) + +test.register_coroutine_test( + "shinasystems energy handler resets save tick when timer has slipped beyond 30 minutes", + function() + -- Advance time > 30 min so that curr_save_tick + 15*60 < os.time() is true + test.timer.__create_and_queue_test_time_advance_timer(40*60, "oneshot") + test.mock_time.advance_time(40*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 1500) + }) + -- raw_value=1500, divisor=1000, kWh=1.5, watts=1500.0; first report: deltaEnergy=0.0 + 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"})) + ) + end +) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua new file mode 100644 index 0000000000..81caddae56 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua @@ -0,0 +1,102 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local ElectricalMeasurement = clusters.ElectricalMeasurement +local SimpleMetering = clusters.SimpleMetering +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local constants = require "st.zigbee.constants" + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("power-meter.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Develco Products A/S", + model = "EMIZB-132", + server_clusters = {SimpleMetering.ID, ElectricalMeasurement.ID} + } + } +}) + +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) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "frient device_init sets divisor fields", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.wait_for_events() + assert(mock_device:get_field(constants.SIMPLE_METERING_DIVISOR_KEY) == 1000, + "SIMPLE_METERING_DIVISOR_KEY should be 1000") + assert(mock_device:get_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY) == 10000, + "ELECTRICAL_MEASUREMENT_DIVISOR_KEY should be 10000") + end +) + +test.register_coroutine_test( + "frient 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, + SimpleMetering.attributes.InstantaneousDemand:read(mock_device) + }) + 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, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, SimpleMetering.ID) + }) + 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, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ElectricalMeasurement.ID) + }) + 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, + 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, + 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) + }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.run_registered_tests() 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 6957148647..ca98e8a1a7 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 @@ -10,6 +10,7 @@ local PowerConfiguration = clusters.PowerConfiguration local capabilities = require "st.capabilities" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local t_utils = require "integration_test.utils" +local presence_utils = require "presence_utils" -- Needed for building ConfigureReportingResponse msg local messages = require "st.zigbee.messages" @@ -294,4 +295,96 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "battery_config_response_handler cancels pre-existing recurring poll timer", + function() + -- Place a live timer in the field so the nil-check branch is taken. + local pre_timer = test.timer.__create_test_time_advance_timer(60, "interval") + mock_simple_device:set_field(presence_utils.RECURRING_POLL_TIMER, pre_timer) + test.socket.zigbee:__queue_receive({ + mock_simple_device.id, + build_config_response_msg(PowerConfiguration.ID, 0x00) + }) + -- poke() emits "present" for every inbound zigbee message + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present")) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "info_changed with changed check_interval cancels existing recurring poll timer", + function() + local pre_timer = test.timer.__create_test_time_advance_timer(60, "interval") + mock_simple_device:set_field(presence_utils.RECURRING_POLL_TIMER, pre_timer) + test.socket.device_lifecycle():__queue_receive( + mock_simple_device:generate_info_changed({ preferences = { check_interval = 100 } }) + ) + test.wait_for_events() + end +) + +-- Build two additional mock devices (module-level) for checkInterval type variants. +-- The profile default sets checkInterval = 120 (number); we override after building. +local mock_device_str_interval = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("smartthings-arrival-sensor.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "SmartThings", + model = "tagv4", + server_clusters = {0x0000, 0x0001, 0x0003} + } + } + } +) +mock_device_str_interval.preferences.checkInterval = "120" -- string → triggers elseif branch + +local mock_device_nil_interval = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("smartthings-arrival-sensor.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "SmartThings", + model = "tagv4", + server_clusters = {0x0000, 0x0001, 0x0003} + } + } + } +) +mock_device_nil_interval.preferences.checkInterval = nil -- nil → triggers default-return branch + +test.register_coroutine_test( + "init with string checkInterval uses parsed value for presence timeout", + function() + test.mock_device.add_test_device(mock_device_str_interval) + test.timer.__create_and_queue_test_time_advance_timer(120, "oneshot") + test.socket.device_lifecycle:__queue_receive({ mock_device_str_interval.id, "init" }) + test.wait_for_events() + test.mock_time.advance_time(121) + test.socket.capability:__expect_send( + mock_device_str_interval:generate_test_message("main", capabilities.presenceSensor.presence("not present")) + ) + end, + { test_init = function() end } +) + +test.register_coroutine_test( + "init with nil checkInterval uses default presence timeout", + function() + test.mock_device.add_test_device(mock_device_nil_interval) + test.timer.__create_and_queue_test_time_advance_timer(120, "oneshot") + test.socket.device_lifecycle:__queue_receive({ mock_device_nil_interval.id, "init" }) + test.wait_for_events() + test.mock_time.advance_time(121) + test.socket.capability:__expect_send( + mock_device_nil_interval:generate_test_message("main", capabilities.presenceSensor.presence("not present")) + ) + end, + { test_init = function() end } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua b/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua index 4aa03d2bca..bdd79dd59f 100644 --- a/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua +++ b/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua @@ -174,4 +174,36 @@ test.register_message_test( } ) +test.register_message_test( + "ZoneStatusChangeNotification - mains should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0001, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) + } + } +) + +test.register_message_test( + "Device added lifecycle should emit mains powerSource", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_device.id, "added" } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua index 8140da64bd..c88d272807 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua @@ -1001,4 +1001,120 @@ test.register_message_test( } ) +local function build_sw_version_attr_report(device, fw_bytes) + local zcl_messages_mod = require "st.zigbee.zcl" + local messages_mod = require "st.zigbee.messages" + local zb_const_mod = require "st.zigbee.constants" + local report_attr = require "st.zigbee.zcl.global_commands.report_attribute" + local zcl_cmds_mod = require "st.zigbee.zcl.global_commands" + + local attr_record = report_attr.ReportAttributeAttributeRecord( + DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR, + data_types.CharString.ID, + fw_bytes + ) + local report_body = report_attr.ReportAttribute({ attr_record }) + local zclh = zcl_messages_mod.ZclHeader({ + cmd = data_types.ZCLCommandId(zcl_cmds_mod.REPORT_ATTRIBUTE_ID) + }) + local addrh = messages_mod.AddressHeader( + device:get_short_address(), + device:get_endpoint(Basic.ID), + zb_const_mod.HUB.ADDR, + zb_const_mod.HUB.ENDPOINT, + zb_const_mod.HA_PROFILE_ID, + Basic.ID + ) + local message_body = zcl_messages_mod.ZclMessageBody({ zcl_header = zclh, zcl_body = report_body }) + return messages_mod.ZigbeeMessageRx({ address_header = addrh, body = message_body }) +end + +test.register_coroutine_test( + "SW version attr handler with new firmware should configure battery percentage", + function() + mock_device:set_field(PRIMARY_SW_VERSION, nil, {persist = true}) + mock_device:set_field("_frient_battery_config_applied", nil, {persist = true}) + + -- Binary "\x01\x09\x03" -> hex "010903" (new firmware >= SIREN_FIXED_ENDIAN_SW_VERSION) + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_sw_version_attr_report(mock_device, "\x01\x09\x03") + }) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "SW version attr handler with old firmware should configure battery voltage", + function() + mock_device:set_field(PRIMARY_SW_VERSION, nil, {persist = true}) + mock_device:set_field("_frient_battery_config_applied", nil, {persist = true}) + + -- Binary "\x01\x09\x01" -> hex "010901" (old firmware < SIREN_FIXED_ENDIAN_SW_VERSION) + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_sw_version_attr_report(mock_device, "\x01\x09\x01") + }) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "doConfigure with existing sw_version should call configure_battery_handling", + function() + mock_device:set_field(PRIMARY_SW_VERSION, "010903", {persist = true}) + mock_device:set_field("_frient_battery_config_applied", nil, {persist = true}) + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + + 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, + zigbee_test_utils.build_bind_request( + mock_device, + zigbee_test_utils.mock_hub_eui, + PowerConfiguration.ID, + 0x2B + ):to_endpoint(0x2B) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining: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, + IASZone.ID, + 0x2B + ):to_endpoint(0x2B) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.ZoneStatus:configure_reporting(mock_device, 0, 21600, 1) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.IASCIEAddress:write(mock_device, zigbee_test_utils.mock_hub_eui) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.server.commands.ZoneEnrollResponse(mock_device, IasEnrollResponseCode.SUCCESS, 0x00) + }) + + 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-siren/src/test/test_frient_siren_tamper.lua b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua index 810e691849..aed318f49a 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua @@ -64,6 +64,24 @@ local mock_device = test.mock_device.build_test_zigbee_device( } ) +local mock_device_112 = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("frient-siren-battery-source-tamper.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "frient A/S", + model = "SIRZB-112", + server_clusters = { Scenes.ID, OnOff.ID} + }, + [0x2B] = { + id = 0x2B, + server_clusters = { Basic.ID, Identify.ID, PowerConfiguration.ID, Groups.ID, IASZone.ID, IASWD.ID } + } + } + } +) + zigbee_test_utils.prepare_zigbee_env_info() local function test_init() test.mock_device.add_test_device(mock_device) @@ -1026,4 +1044,40 @@ test.register_message_test( } ) +local function get_siren_OFF_commands_112(duration) + local expectedSirenOFFConfiguration = SirenConfiguration(0x00) + expectedSirenOFFConfiguration:set_warning_mode(WarningMode.STOP) + expectedSirenOFFConfiguration:set_siren_level(IaswdLevel.LOW_LEVEL) + + test.socket.zigbee:__expect_send({ + mock_device_112.id, + IASWD.server.commands.StartWarning( + mock_device_112, + expectedSirenOFFConfiguration, + data_types.Uint16(duration and duration or ALARM_DURATION_TEST_VALUE), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) +end + +test.register_coroutine_test( + "SIRZB-112 alarm off command should be handled via frient sub-driver", + function() + mock_device_112:set_field(PRIMARY_SW_VERSION, "010903", {persist = true}) + mock_device_112:set_field(ALARM_DURATION, ALARM_DURATION_TEST_VALUE, {persist = true}) + + test.socket.capability:__queue_receive({ + mock_device_112.id, + { capability = "alarm", component = "main", command = "off", args = {} } + }) + + get_siren_OFF_commands_112() + test.wait_for_events() + end, + { test_init = function() + test.mock_device.add_test_device(mock_device_112) + end } +) + test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua index 3dc707a297..52db9f96f4 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua @@ -340,6 +340,63 @@ test.register_coroutine_test( end ) +local function build_default_response_zigbee_msg() + local zcl_messages = require "st.zigbee.zcl" + local messages = require "st.zigbee.messages" + local zb_const = require "st.zigbee.constants" + local buf_lib = require "st.buf" + local buf_from_str = function(str) return buf_lib.Reader(str) end + local frame = "\x00\x00" + local default_response = zcl_cmds.DefaultResponse.deserialize(buf_from_str(frame)) + local zclh = zcl_messages.ZclHeader({ cmd = data_types.ZCLCommandId(zcl_cmds.DefaultResponse.ID) }) + local addrh = messages.AddressHeader( + mock_device:get_short_address(), + mock_device:get_endpoint(data_types.ClusterId(IASWD.ID)), + zb_const.HUB.ADDR, zb_const.HUB.ENDPOINT, zb_const.HA_PROFILE_ID, IASWD.ID) + local message_body = zcl_messages.ZclMessageBody({ zcl_header = zclh, zcl_body = default_response }) + return messages.ZigbeeMessageRx({ address_header = addrh, body = message_body }) +end + +test.register_coroutine_test( + "Default response with OFF alarm command should emit off events", + function() + mock_device:set_field("alarmCommand", 0, {persist = true}) + test.socket.zigbee:__queue_receive({ mock_device.id, build_default_response_zigbee_msg() }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.off())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + end +) + +test.register_coroutine_test( + "Default response with STROBE alarm command should emit strobe event and timer off", + function() + test.timer.__create_and_queue_test_time_advance_timer(180, "oneshot") + mock_device:set_field("alarmCommand", 2, {persist = true}) + test.socket.zigbee:__queue_receive({ mock_device.id, build_default_response_zigbee_msg() }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.strobe())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) + test.mock_time.advance_time(180) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.off())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Default response with BOTH alarm command should emit both event", + function() + test.timer.__create_and_queue_test_time_advance_timer(180, "oneshot") + mock_device:set_field("alarmCommand", 3, {persist = true}) + test.socket.zigbee:__queue_receive({ mock_device.id, build_default_response_zigbee_msg() }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.both())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) + test.mock_time.advance_time(180) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.off())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + test.wait_for_events() + end +) + test.register_coroutine_test( "Setting a max duration should be handled", function() diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua index 4c1dcb1552..1203ffa00a 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua @@ -244,6 +244,66 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "selfCheck report should be handled, idle", + function() + local attr_report_data = { + { PRIVATE_SELF_CHECK_ATTRIBUTE_ID, data_types.Uint8.ID, 0x00 } + } + 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", + selfCheck.selfCheckState.idle())) + end +) + +test.register_coroutine_test( + "sensitivityAdjustment report should be handled, High", + function() + local attr_report_data = { + { PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, data_types.Uint8.ID, 0x02 } + } + 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", + sensitivityAdjustment.sensitivityAdjustment.High())) + end +) + +test.register_coroutine_test( + "Capability on command should be handled : setSensitivityAdjustment High", + function() + local attr_report_data = { + { PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, data_types.Uint8.ID, 0x02 } + } + test.socket.capability:__queue_receive({ mock_device.id, + { capability = sensitivityAdjustmentId, component = "main", command = "setSensitivityAdjustment", args = {"High"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x02) + }) + test.wait_for_events() + 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", + sensitivityAdjustment.sensitivityAdjustment.High()) + ) + test.wait_for_events() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = sensitivityAdjustmentId, component = "main", command = "setSensitivityAdjustment", args = {"High"}} + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + sensitivityAdjustment.sensitivityAdjustment.High()) + ) + end +) test.register_coroutine_test( diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua index 1af67b36cd..b0b6a4875a 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua @@ -7,13 +7,10 @@ local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" - - local PowerConfiguration = clusters.PowerConfiguration local selfCheck = capabilities["stse.selfCheck"] local selfCheckId = "stse.selfCheck" - test.add_package_capability("selfCheck.yaml") local PRIVATE_CLUSTER_ID = 0xFCC0 @@ -23,8 +20,6 @@ local PRIVATE_MUTE_ATTRIBUTE_ID = 0x0126 local PRIVATE_SELF_CHECK_ATTRIBUTE_ID = 0x0127 local PRIVATE_SMOKE_ZONE_STATUS_ATTRIBUTE_ID = 0x013A - - local mock_device = test.mock_device.build_test_zigbee_device( { profile = t_utils.get_profile_definition("smoke-battery-aqara.yml"), @@ -39,15 +34,11 @@ local mock_device = 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) end - - test.set_test_init_function(test_init) test.register_coroutine_test( @@ -89,7 +80,6 @@ test.register_coroutine_test( end ) - test.register_coroutine_test( "smokeDetector report should be handled", function() @@ -105,8 +95,6 @@ test.register_coroutine_test( end ) - - test.register_coroutine_test( "audioMute report should be handled", function() @@ -122,8 +110,6 @@ test.register_coroutine_test( end ) - - test.register_coroutine_test( "Capability on command should be handled : device mute", function() @@ -135,8 +121,6 @@ test.register_coroutine_test( end ) - - test.register_coroutine_test( "selfCheck report should be handled", function() @@ -152,8 +136,6 @@ test.register_coroutine_test( end ) - - test.register_coroutine_test( "Capability on command should be handled : device selfCheck", function() @@ -166,7 +148,50 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "smokeDetector report should be handled, smoke clear", + function() + local attr_report_data = { + { PRIVATE_SMOKE_ZONE_STATUS_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0000 } + } + 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.smokeDetector.smoke.clear())) + end +) + +test.register_coroutine_test( + "audioMute report should be handled, unmuted", + function() + local attr_report_data = { + { PRIVATE_MUTE_ATTRIBUTE_ID, data_types.Uint8.ID, 0x00 } + } + 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.audioMute.mute.unmuted())) + end +) +test.register_coroutine_test( + "selfCheck report should be handled, idle", + function() + local attr_report_data = { + { PRIVATE_SELF_CHECK_ATTRIBUTE_ID, data_types.Uint8.ID, 0x00 } + } + 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", + selfCheck.selfCheckState.idle())) + end +) test.register_message_test( "Battery voltage report should be handled", @@ -184,5 +209,4 @@ test.register_message_test( } ) - test.run_registered_tests() 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 index 364aae57a0..21b1660c1a 100644 --- 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 @@ -569,4 +569,36 @@ test.register_coroutine_test( end ) +test.register_message_test( + "IASZone attribute report should be handled: detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0001) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) + } + } +) + +test.register_coroutine_test( + "IASZone attribute report 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.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) + }) + 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.run_registered_tests() 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 fa012a321b..fdb05cf22d 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 @@ -28,6 +28,12 @@ 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 messages = require "st.zigbee.messages" +local default_response = require "st.zigbee.zcl.global_commands.default_response" +local zb_const = require "st.zigbee.constants" +local Status = require "st.zigbee.generated.types.ZclStatus" +local zcl_messages = require "st.zigbee.zcl" +local ALARM_COMMAND = "alarmCommand" local mock_device = test.mock_device.build_test_zigbee_device( @@ -609,4 +615,88 @@ test.register_coroutine_test( end ) +local function build_default_response_msg(device, cluster, command, status) + local addr_header = messages.AddressHeader( + device:get_short_address(), + device.fingerprinted_endpoint_id, + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + zb_const.HA_PROFILE_ID, + cluster + ) + local default_response_body = default_response.DefaultResponse(command, status) + local zcl_header = zcl_messages.ZclHeader({ + cmd = data_types.ZCLCommandId(default_response_body.ID) + }) + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_header, + zcl_body = default_response_body + }) + return messages.ZigbeeMessageRx({ + address_header = addr_header, + body = message_body + }) +end + +test.register_message_test( + "IASZone attribute report should be handled: detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0001) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", smokeDetector.smoke.detected()) + } + } +) + +test.register_coroutine_test( + "IASZone attribute report 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.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) + }) + test.mock_time.advance_time(6) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", smokeDetector.smoke.clear()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "default_response_handler: siren active, emit siren then off after delay", + function() + mock_device:set_field(ALARM_COMMAND, 1) + test.timer.__create_and_queue_test_time_advance_timer(ALARM_DEFAULT_MAX_DURATION, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_default_response_msg(mock_device, IASWD.ID, IASWD.server.commands.StartWarning.ID, Status.SUCCESS) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", alarm.alarm.siren())) + test.mock_time.advance_time(ALARM_DEFAULT_MAX_DURATION) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", alarm.alarm.off())) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "default_response_handler: alarm off, emit off", + function() + mock_device:set_field(ALARM_COMMAND, 0) + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_default_response_msg(mock_device, IASWD.ID, IASWD.server.commands.StartWarning.ID, Status.SUCCESS) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", alarm.alarm.off())) + test.wait_for_events() + end +) + test.run_registered_tests() 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 c521ba3e44..516a18c326 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua @@ -180,4 +180,38 @@ test.register_coroutine_test( end ) +local mock_device_cwacn1 = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-light.yml"), + preferences = { ["stse.lightFadeInTimeInSec"] = 0, ["stse.lightFadeOutTimeInSec"] = 0 }, + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.light.cwacn1", + server_clusters = { 0x0006, 0x0008, 0x0300 } + } + } + } +) + +test.register_coroutine_test( + "Handle added lifecycle for lumi.light.cwacn1 model (colorTemperatureRange max = 6500)", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device_cwacn1.id, "added" }) + + test.socket.zigbee:__expect_send({ + mock_device_cwacn1.id, cluster_base.write_manufacturer_specific_attribute(mock_device_cwacn1, PRIVATE_CLUSTER_ID, + PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) + test.socket.capability:__expect_send(mock_device_cwacn1:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ minimum = 2700, maximum = 6500 }))) + end, + { + test_init = function() + test.mock_device.add_test_device(mock_device_cwacn1) + end + } +) + test.run_registered_tests() 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 d37dafe45d..bd54b1b20f 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 @@ -138,4 +138,38 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Handle infoChanged when dimOnOff changes from 1 to 0 should write transition time 0", + function() + -- First: change dimOnOff from default 0 to 1 (triggers write with dimRate=20) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { dimOnOff = 1 } + })) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 20) }) + test.wait_for_events() + -- Now: change dimOnOff from 1 to 0 (driver old=1, new=0 -> writes 0) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { dimOnOff = 0 } + })) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 0) }) + end +) + +test.register_coroutine_test( + "Handle infoChanged when dimRate changes while dimOnOff is 1 should write new dimRate", + function() + -- First: change dimOnOff from default 0 to 1 (triggers write with dimRate=20) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { dimOnOff = 1 } + })) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 20) }) + test.wait_for_events() + -- Now: change dimRate while dimOnOff stays 1 (driver old={dimOnOff=1,dimRate=20}, new={dimOnOff=1,dimRate=50}) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { dimOnOff = 1, dimRate = 50 } + })) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 50) }) + end +) + test.run_registered_tests() 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 52ba3ef2a6..d6dcc08ea4 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua @@ -5,11 +5,14 @@ local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" local OnOffCluster = clusters.OnOff 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 mock_device = test.mock_device.build_test_zigbee_device({ profile = t_utils.get_profile_definition("switch-power-energy.yml"), + fingerprinted_endpoint_id = 0x01, zigbee_endpoints = { [1] = { id = 1, @@ -21,6 +24,8 @@ local mock_device = test.mock_device.build_test_zigbee_device({ } }) +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) @@ -113,4 +118,30 @@ test.register_message_test( } ) +test.register_coroutine_test( + "doConfigure lifecycle should call refresh and configure on device", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + -- device:refresh() reads + test.socket.zigbee:__expect_send({ mock_device.id, OnOffCluster.attributes.OnOff: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, ElectricalMeasurementCluster.attributes.ActivePower: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, ElectricalMeasurementCluster.attributes.ACPowerMultiplier:read(mock_device) }) + -- device:configure() bind requests and configure_reporting + test.socket.zigbee:__expect_send({ mock_device.id, zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, OnOffCluster.ID) }) + test.socket.zigbee:__expect_send({ mock_device.id, OnOffCluster.attributes.OnOff:configure_reporting(mock_device, 0, 300) }) + 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, 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, zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ElectricalMeasurementCluster.ID) }) + 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, ElectricalMeasurementCluster.attributes.ACPowerDivisor:configure_reporting(mock_device, 1, 43200, 1) }) + test.socket.zigbee:__expect_send({ mock_device.id, ElectricalMeasurementCluster.attributes.ACPowerMultiplier:configure_reporting(mock_device, 1, 43200, 1) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + test.run_registered_tests() 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 0457c0eb62..3b004c8f91 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 @@ -458,4 +458,21 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "component main Capability on command emits on then off after delay", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "switch", component = "main", command = "on", args = {} } }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.switch.switch.on()) + ) + test.wait_for_events() + test.mock_time.advance_time(1) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.switch.switch.off()) + ) + end +) + test.run_registered_tests() 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 0f00d47fdb..b4ded05175 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 @@ -476,4 +476,45 @@ test.register_coroutine_test( end ) +local mock_non_mns_device = test.mock_device.build_test_zigbee_device( + { + label = "Generic Switch 1", + profile = profile, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "GenericMfr", + model = "GenericModel-DualSwitch", + server_clusters = { 0x0006 } + }, + [2] = { + id = 2, + manufacturer = "GenericMfr", + model = "GenericModel-DualSwitch", + server_clusters = { 0x0006 } + } + }, + fingerprinted_endpoint_id = 0x01 + } +) + +test.register_coroutine_test( + "device added lifecycle creates child device for secondary OnOff endpoint", + function() + test.socket.device_lifecycle:__queue_receive({ mock_non_mns_device.id, "added" }) + mock_non_mns_device:expect_device_create({ + type = "EDGE_CHILD", + label = "Generic Switch 1 2", + profile = "basic-switch", + parent_device_id = mock_non_mns_device.id, + parent_assigned_child_key = "02" + }) + end, + { + test_init = function() + test.mock_device.add_test_device(mock_non_mns_device) + end + } +) + test.run_registered_tests() 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 ce1a9e2882..1a8d42dba5 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua @@ -118,6 +118,23 @@ local mock_seventh_child = test.mock_device.build_test_child_device( } ) +-- Single button device matching WALL HERO fingerprint (used to test button capability events) +local mock_button_device = test.mock_device.build_test_zigbee_device( + { + label = "WALL HERO Switch 1", + profile = scene_switch_profile_def, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "WALL HERO", + model = "ACL-401S1I", + server_clusters = { 0x0003, 0x0004, 0x0005, 0x0006 } + } + }, + fingerprinted_endpoint_id = 0x01 + } +) + zigbee_test_utils.prepare_zigbee_env_info() local function test_init() @@ -129,7 +146,9 @@ local function test_init() test.mock_device.add_test_device(mock_fourth_child) test.mock_device.add_test_device(mock_fifth_child) test.mock_device.add_test_device(mock_sixth_child) - test.mock_device.add_test_device(mock_seventh_child)end + test.mock_device.add_test_device(mock_seventh_child) + test.mock_device.add_test_device(mock_button_device) +end test.set_test_init_function(test_init) @@ -710,4 +729,15 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "device_added lifecycle event should emit button capability events for button device", + function() + test.socket.device_lifecycle:__queue_receive({ mock_button_device.id, "added" }) + test.socket.capability:__expect_send(mock_button_device:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_button_device:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua index 0717fbb9fa..74cc7a2115 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua @@ -174,4 +174,31 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Refresh should send read requests for all necessary attributes", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.LocalTemperature:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.PIHeatingDemand:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.SystemMode:read(mock_device) + }) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua index afb36720a3..4ff17803fa 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua @@ -517,5 +517,51 @@ test.register_coroutine_test( }) end ) +test.register_coroutine_test( + "LocalTemperature handler should request PIHeatingDemand when setpoint > temperature", + function() + local RAW_SETPOINT_FIELD = "raw_setpoint" + mock_device:set_field(RAW_SETPOINT_FIELD, 3000, { persist = true }) + + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.LocalTemperature:build_test_attr_report(mock_device, 2000) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.PIHeatingDemand:read(mock_device) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 20.0, unit = "C" })) + ) + end +) + +test.register_coroutine_test( + "Setting an unsupported thermostat mode should re-emit the current mode", + function() + -- Establish a known current mode state + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, ThermostatSystemMode.OFF) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", ThermostatMode.thermostatMode.off()) + ) + test.wait_for_events() + + -- "cool" is not in SUPPORTED_MODES for stelpro-ki; the driver re-emits the current mode + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatMode", component = "main", command = "setThermostatMode", args = { "cool" } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", ThermostatMode.thermostatMode.off()) + ) + end +) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua index 638f3f7ff4..0886a7cd7f 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua @@ -450,4 +450,44 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Setting cooling setpoint while in heat mode should re-emit the current cooling setpoint", + function() + -- Put device in heat mode + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, 0x04) -- HEAT + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.heat()) + ) + test.wait_for_events() + + -- Set a known cooling setpoint state + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.OccupiedCoolingSetpoint:build_test_attr_report(mock_device, 2500) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 25.0, unit = "C" })) + ) + test.wait_for_events() + + -- Try to set cooling setpoint while in heat mode; driver defers and re-emits current + test.timer.__create_and_queue_test_time_advance_timer(10, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatCoolingSetpoint", component = "main", command = "setCoolingSetpoint", args = { 27 } } + }) + test.wait_for_events() + + test.mock_time.advance_time(10) + -- After the delay, update_device_setpoint re-emits the unchanged cooling setpoint (25.0 C) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 25.0, unit = "C" })) + ) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua index e925491c87..083d522bb3 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua @@ -82,6 +82,22 @@ test.register_message_test( } ) +test.register_message_test( + "Battery percentage report - not low should return 50%", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(50)) + } + } +) + test.register_message_test( "PowerSource(unknown) reporting should be handled", { diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua index 8710cef0c9..d30c60ddc6 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua @@ -131,4 +131,12 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Battery voltage above max should clamp to 100 percent", + function() + test.socket.zigbee:__queue_receive({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device, 65) }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(100)) ) + end +) + test.run_registered_tests() 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 86e4aa774a..082c9fb97c 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 @@ -79,27 +79,15 @@ test.register_coroutine_test( end ) --- test.register_coroutine_test( --- "Health check should check all relevant attributes", --- function() --- test.socket.device_lifecycle:__queue_receive({mock_device.id, "added"}) --- test.socket.capability:__expect_send( --- { --- mock_device.id, --- { --- capability_id = "waterSensor", component_id = "main", --- attribute_id = "water", state={value="dry"} --- } --- } --- ) --- end, --- { --- test_init = function() --- test.mock_device.add_test_device(mock_device) --- test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "health_check") --- end --- } --- ) +test.register_coroutine_test( + "Added lifecycle should emit water dry event", + function() + test.socket.device_lifecycle:__queue_receive({mock_device.id, "added"}) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) + ) + end +) test.register_coroutine_test( "Configure should configure all necessary attributes", 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 b52884f6a3..63be06f0a8 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 @@ -36,6 +36,14 @@ end test.set_test_init_function(test_init) +test.register_coroutine_test( + "Added lifecycle should read ApplicationVersion", + function() + test.socket.device_lifecycle:__queue_receive({mock_device.id, "added"}) + test.socket.zigbee:__expect_send({mock_device.id, Basic.attributes.ApplicationVersion:read(mock_device)}) + end +) + test.register_coroutine_test( "Refresh necessary attributes", function() diff --git a/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua b/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua index 35ab4d915c..1ad780782c 100644 --- a/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua +++ b/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua @@ -218,4 +218,50 @@ test.register_coroutine_test( end ) +test.register_message_test( + "ZoneStatusChangeNotification should be handled: detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0001, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.detected()) + } + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: clear", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) + } + } +) + +test.register_coroutine_test( + "fanspeed reported should be clamped to 0 when value >= 1000", + function() + local attr_report_data = { + { WATERING_TIME_ATTR, data_types.Uint16.ID, 1000 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, THIRDREALITY_WATERING_CLUSTER, attr_report_data, 0x1407) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(0))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua index a73bb6c5a3..8c87907886 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua @@ -215,4 +215,114 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Cancel existing set-status timer when a new partial level report arrives", + function() + -- First attr: level 90 sets T1 + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + -- Second attr arrives before T1 fires: should cancel T1 and create T2 + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 15) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 85 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) + ) + -- T2 fires; T1 was cancelled so only partially_open from T2 + test.mock_time.advance_time(1) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Timer callback emits closed when shade reaches level 0", + function() + -- First attr starts partial movement and arms a 1-second status timer + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + -- Second attr reports fully closed (level=0); goes through elseif branch, T1 still pending + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 100) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) + ) + -- T1 fires; get_latest_state returns 0 so the callback emits closed() + test.mock_time.advance_time(1) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Timer callback emits open when shade reaches level 100", + function() + -- First attr starts partial movement and arms a 1-second status timer + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + -- Second attr reports fully open (level=100); goes through elseif branch, T1 still pending + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 0) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) + ) + -- T1 fires; get_latest_state returns 100 so the callback emits open() + test.mock_time.advance_time(1) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) + ) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua index c13164df09..725fda3f62 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua @@ -322,4 +322,109 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Default response emits opening when current level is higher than target", + function() + -- Establish a partially-closed shade state (zigbee value 90 → shadeLevel 10) + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 90) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(10)) + ) + test.wait_for_events() + -- Send open command: MOST_RECENT_SETLEVEL = 0 (level = 100 - 100 = 0) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "windowShade", component = "main", command = "open", args = {} } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + WindowCovering.server.commands.GoToLiftPercentage(mock_device, 0) + }) + test.wait_for_events() + -- Default response: current_level_zigbee=90, most_recent=0 → 90 > 0 → opening() + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_default_response_msg(WindowCovering.ID, WindowCovering.server.commands.GoToLiftPercentage.ID, Status.SUCCESS) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Attr handler emits partially_open when report matches most-recent set level", + function() + -- Send presetPosition; preset level = 50 so MOST_RECENT_SETLEVEL = 50 (100-50=50) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + WindowCovering.server.commands.GoToLiftPercentage(mock_device, 50) + }) + test.wait_for_events() + -- Attr report value=50 matches MOST_RECENT_SETLEVEL; shade stops at partial level + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 50) + }) + -- current_level was nil → partially_open from the nil-check branch + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + -- most_recent matches and value is partial → partially_open again (from the match branch) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50)) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Spontaneous level report towards open emits opening event", + function() + -- First attr establishes a partial shade level (value=10 → shadeLevel=90) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(90)) + ) + -- Second attr moves toward open (value=5 < current zigbee 10 → opening) + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 5) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(95)) + ) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua index 6c8ea27d01..fa764a5db0 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua @@ -517,4 +517,96 @@ test.register_coroutine_test( end ) +test.register_message_test( + "Attribute handler reports closed when level is 0", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 100) + } + }, + { + channel = "capability", + direction = "send", + message = { + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 0 } } + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + } + } +) + +test.register_message_test( + "Attribute handler reports open when level is 100", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 0) + } + }, + { + channel = "capability", + direction = "send", + message = { + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 100 } } + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) + } + } +) + +test.register_coroutine_test( + "Cancel existing poll timer when a new partial level report arrives", + function() + -- First attr: level 90 creates T1 via overwrite_existing_timer_if_needed + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + -- Second attr before T1 fires: overwrite_existing_timer_if_needed cancels T1 and stores T2 + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 15) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 85 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) + ) + -- T2 fires; T1 was cancelled so only one partially_open + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua index ae87438c99..37841524c4 100755 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua @@ -315,4 +315,122 @@ test.register_coroutine_test( end ) +test.register_message_test( + "Attribute handler reports closed when shade reaches level 0", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 100) + } + }, + { + channel = "capability", + direction = "send", + message = { + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 0 } } + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + } + } +) + +test.register_message_test( + "Attribute handler reports open when shade reaches level 100", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 0) + } + }, + { + channel = "capability", + direction = "send", + message = { + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 100 } } + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) + } + } +) + +test.register_coroutine_test( + "SetLevel command emits closing when requested level is below current level", + function() + -- Attr report sets current shade level to 90 (inverted value=10) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + test.wait_for_events() + -- Now both vimar flags are false; requesting level 30 (< 90) triggers closing branch + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 30 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(30)) + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 70) + }) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "SetLevel command is ignored (early return) when shades are already moving", + function() + -- Open command: current=0 < 100 → opening, sets VIMAR_SHADES_OPENING=true + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "windowShade", component = "main", command = "open", args = {} } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 0) + }) + test.wait_for_events() + -- While opening, a different setShadeLevel is requested: ignored with current level re-emitted + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 50 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) + ) + test.wait_for_events() + end +) + test.run_registered_tests() From eb70a7cc00f028ba655990e503cfa077739044de Mon Sep 17 00:00:00 2001 From: Taejun Park Date: Fri, 27 Feb 2026 11:12:44 +0900 Subject: [PATCH 134/538] Change the icon for the Matter Ikea knob and dual button --- .../matter-switch/fingerprints.yml | 2 +- .../profiles/ikea-2-button-battery.yml | 53 +++++++++++++++++++ .../matter-switch/profiles/ikea-scroll.yml | 46 ++++++++++++++++ .../src/switch_utils/device_configuration.lua | 3 ++ .../matter-switch/src/switch_utils/fields.lua | 3 +- 5 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 33f1b6f1d4..1c8adddff4 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -867,7 +867,7 @@ matterManufacturer: deviceLabel: BILRESA dual button vendorId: 0x117C productId: 0x8001 - deviceProfileName: 2-button-battery + deviceProfileName: ikea-2-button-battery #Innovation Matters - id: "4978/1" deviceLabel: M2D Bridge diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml b/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml new file mode 100644 index 0000000000..0b256f3b65 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml @@ -0,0 +1,53 @@ +name: ikea-2-button-battery +components: + - id: main + label: Button 1 + capabilities: + - id: button + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController + - id: button2 + label: Button 2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController +deviceConfig: + icons: + - group: main + iconUrl: 'icon://button_multi' + dashboard: + states: + - component: main + capability: button + version: 1 + detailView: + - component: main + capability: button + version: 1 + - component: main + capability: battery + version: 1 + - component: button2 + capability: button + version: 1 + automation: + conditions: + - component: main + capability: button + version: 1 + - component: main + capability: battery + version: 1 + - component: button2 + capability: button + version: 1 + actions: [] diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml index e07006ce9d..166b0a62f1 100644 --- a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml +++ b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml @@ -33,3 +33,49 @@ components: version: 1 categories: - name: Button +deviceConfig: + icons: + - group: main + iconUrl: 'icon://button_wheel' + dashboard: + states: + - component: main + capability: button + version: 1 + detailView: + - component: main + capability: button + version: 1 + - component: main + capability: knob + version: 1 + - component: main + capability: battery + version: 1 + - component: group2 + capability: button + version: 1 + - component: group2 + capability: knob + version: 1 + - component: group3 + capability: button + version: 1 + - component: group3 + capability: knob + version: 1 + automation: + conditions: + - component: main + capability: button + version: 1 + - component: main + capability: battery + version: 1 + - component: group2 + capability: button + version: 1 + - component: group3 + capability: button + version: 1 + actions: [] diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 8542972320..18219ef459 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -132,6 +132,9 @@ function ButtonDeviceConfiguration.update_button_profile(device, default_endpoin if switch_utils.get_product_override_field(device, "is_climate_sensor_w100") then profile_name = "3-button-battery-temperature-humidity" end + if switch_utils.get_product_override_field(device, "is_ikea_dual_button") then + profile_name = "ikea-2-button-battery" + end return profile_name end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 6eb03b1472..2b315c6528 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -101,7 +101,8 @@ SwitchFields.vendor_overrides = { [0x2004] = { is_climate_sensor_w100 = true }, -- Climate Sensor W100, requires unique profile }, [0x117C] = { -- IKEA_MANUFACTURER_ID - [0x8000] = { is_ikea_scroll = true } + [0x8000] = { is_ikea_scroll = true }, -- BILRESA scroll wheel + [0x8001] = { is_ikea_dual_button = true}, -- BILRESA dual button }, [0x1189] = { -- LEDVANCE_MANUFACTURER_ID [0x0891] = { target_profile = "switch-binary", initial_profile = "light-binary" }, From 0298090b54fd3d56d76d4994673cd0c33e2e75b7 Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:32:59 -0600 Subject: [PATCH 135/538] Matter Camera: Remove trigger before zone (#2816) The server will return `INVALID_IN_STATE` if `RemoveZone` is issued while there is an existing trigger for the given zone. The trigger should be removed first before attempting to remove the zone. --- .../camera_handlers/capability_handlers.lua | 9 ++ .../src/test/test_matter_camera.lua | 93 ++++++++++++++++++- 2 files changed, 100 insertions(+), 2 deletions(-) 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 index fb85eb863f..09134a9757 100644 --- 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 @@ -259,6 +259,15 @@ end function CameraCapabilityHandlers.handle_remove_zone(driver, device, cmd) local endpoint_id = device:component_to_endpoint(cmd.component) + local triggers = device:get_latest_state( + camera_fields.profile_components.main, capabilities.zoneManagement.ID, capabilities.zoneManagement.triggers.NAME + ) or {} + for _, v in pairs(triggers) do + if v.zoneId == cmd.args.zoneId then + device:send(clusters.ZoneManagement.server.commands.RemoveTrigger(device, endpoint_id, cmd.args.zoneId)) + break + end + end device:send(clusters.ZoneManagement.server.commands.RemoveZone(device, endpoint_id, cmd.args.zoneId)) end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 930cb069dc..facc6ea984 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -1573,7 +1573,7 @@ test.register_coroutine_test( 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" + i .. " zone", {{value = {x = 0, y = 0}}, {value = {x = 1920, y = 1080}} }, i, "#FFFFFF" }} }) test.socket.matter:__expect_send({ @@ -1586,7 +1586,7 @@ test.register_coroutine_test( clusters.ZoneManagement.types.TwoDCartesianVertexStruct({x = 0, y = 0}), clusters.ZoneManagement.types.TwoDCartesianVertexStruct({x = 1920, y = 1080}) }, - color = "blue" + color = "#FFFFFF" } ) ) @@ -1773,6 +1773,95 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Removing a zone with an existing trigger should send RemoveTrigger followed by RemoveZone", + function() + update_device_profile() + test.wait_for_events() + + -- Create a zone + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "zoneManagement", component = "main", command = "newZone", args = { + "motion zone", {{value = {x = 0, y = 0}}, {value = {x = 1920, y = 1080}}}, "motion", "#FFFFFF" + }} + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ZoneManagement.server.commands.CreateTwoDCartesianZone(mock_device, CAMERA_EP, + clusters.ZoneManagement.types.TwoDCartesianZoneStruct({ + name = "motion zone", + 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" + }) + ) + }) + + -- Create a 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 + }) + }) + + -- Receive the Triggers attribute update from the device reflecting the new trigger + 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() + + -- Receive removeZone command: since a trigger exists for zone 1, RemoveTrigger is sent first, then RemoveZone + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "zoneManagement", component = "main", command = "removeZone", args = { 1 } } + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ZoneManagement.server.commands.RemoveTrigger(mock_device, CAMERA_EP, 1) + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ZoneManagement.server.commands.RemoveZone(mock_device, CAMERA_EP, 1) + }) + test.wait_for_events() + + -- Receive the updated Zones attribute from the device with the zone removed + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ZoneManagement.attributes.Zones:build_test_report_data(mock_device, CAMERA_EP, {}) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.zoneManagement.zones({value = {}})) + ) + end +) + test.register_coroutine_test( "Stream management commands should send the appropriate commands", function() From a5c8b2a769d81828e9639d6f1fa362ce1e622b94 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 3 Mar 2026 11:56:22 -0600 Subject: [PATCH 136/538] Fixed pre-commit script misread binaries and allowing for copyright symbol - Fixed issue where grep would read files as binaries and fail to parse them, so forcing reading of them as a text file - Added copyright logo to regex for better coverage of copyright notice detection --- tools/pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pre-commit b/tools/pre-commit index c9bc4d4d4f..41a9406bf5 100755 --- a/tools/pre-commit +++ b/tools/pre-commit @@ -28,7 +28,7 @@ for file in $files; do # 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 + && [[ ! $(grep $file --text -P -e "-{2,} Copyright (?:© )?\d{4}(?:-\d{4})? SmartThings") ]]; then echo "$file: SmartThings Copyright missing from file" fail=1 fi From 1437942a1ec78f5945d32e9a22a83bbb4f9af0be Mon Sep 17 00:00:00 2001 From: Konrad K <33450498+KKlimczukS@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:39:44 +0100 Subject: [PATCH 137/538] fingerprints for Zooz sensors: ZSE11 (WWSTCERT-10463) , ZSE41 (WWSTCERT-10451), ZSE44 (WWSTCERT-10455) (#2794) * fingerprints for Zooz sensors: ZSE11, ZSE41, ZSE44 * removes redundant white space * cleanup * decimal -> hex --- .../SmartThings/zwave-sensor/fingerprints.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/zwave-sensor/fingerprints.yml b/drivers/SmartThings/zwave-sensor/fingerprints.yml index 502e5bd5a7..0810b20a38 100644 --- a/drivers/SmartThings/zwave-sensor/fingerprints.yml +++ b/drivers/SmartThings/zwave-sensor/fingerprints.yml @@ -446,11 +446,23 @@ zwaveManufacturer: productId: 0x000B deviceProfileName: contact-battery-tamperalert - id: 027A/7000/E001 - deviceLabel: Zooz Open/Closed Sensor + deviceLabel: Open Close XS Sensor manufacturerId: 0x027A productType: 0x7000 productId: 0xE001 - deviceProfileName: contact-battery-tamperalert + deviceProfileName: base-contact + - id: 027A/0201/0006 + deviceLabel: Q Sensor + manufacturerId: 0x027A + productType: 0x0201 + productId: 0x0006 + deviceProfileName: multi-functional-motion + - id: 027A/7000/E004 + deviceLabel: Temperature Humidity XS Sensor + manufacturerId: 0x027A + productType: 0x7000 + productId: 0xE004 + deviceProfileName: humidity-temperature-battery - id: "aeotec/multisensor/7" deviceLabel: Aeotec Multipurpose Sensor manufacturerId: 0x0371 From 417f1fc464d5d74a6cce1d81028dc0c267aa88da Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 3 Mar 2026 11:13:57 -0800 Subject: [PATCH 138/538] CHAD-17711 Adds missing fix from BUG2-532 for Stelpro Ki Zigbee thermostat --- .../zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua index 7dd1d5f0d2..d9f53defe1 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua @@ -212,7 +212,7 @@ local function set_heating_setpoint(driver, device, command) end if value >= MIN_SETPOINT and value <= MAX_SETPOINT then - device:send(Thermostat.attributes.OccupiedHeatingSetpoint:write(device, value * 100)) + device:send(Thermostat.attributes.OccupiedHeatingSetpoint:write(device, utils.round(value * 100))) device:send(Thermostat.attributes.OccupiedHeatingSetpoint:read(device)) device:send(Thermostat.attributes.PIHeatingDemand:read(device)) end From 63e87c3e87619a10a8ccb083e1d10fca933113db Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:40:00 -0600 Subject: [PATCH 139/538] Matter Camera: fix gating of audioRecording capability (#2820) audioRecording should only be included in the camera profile if the AUDIO feature of CAVSM is available and if the device supports the PushAvStreamTransport cluster (previously only the former was being checked). --- .../sub_drivers/camera/camera_utils/device_configuration.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 index 20641ebd33..4f467cbd07 100644 --- 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 @@ -72,7 +72,9 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr 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) + if switch_utils.find_cluster_on_ep(camera_ep, clusters.PushAvStreamTransport.ID, "SERVER") then + table.insert(main_component_capabilities, capabilities.audioRecording.ID) + end table.insert(microphone_component_capabilities, capabilities.audioMute.ID) table.insert(microphone_component_capabilities, capabilities.audioVolume.ID) end From 8855768f8f3979ace74fae67e40011f4fe2bf384 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Fri, 13 Feb 2026 15:47:19 -0800 Subject: [PATCH 140/538] CHAD-12653 Zigbee: Add support for reading ColorTemperatureRange update fingerprints pointing to specific ranges --- .../src/aqara/multi-switch/init.lua | 2 +- .../color_temp_range_handlers/can_handle.lua | 12 ++++ .../src/color_temp_range_handlers/init.lua | 72 +++++++++++++++++++ .../zigbee-switch/src/configurations/init.lua | 14 +++- .../zigbee-switch/src/ezex/init.lua | 2 +- .../zigbee-switch/src/frient-IO/init.lua | 2 +- .../zigbee-switch/src/frient/init.lua | 2 +- .../zigbee-switch/src/hanssem/init.lua | 2 +- .../src/ikea-xy-color-bulb/init.lua | 2 +- .../SmartThings/zigbee-switch/src/init.lua | 2 +- .../zigbee-switch/src/laisiao/init.lua | 2 +- .../src/lifecycle_handlers/do_configure.lua | 7 ++ .../src/multi-switch-no-master/init.lua | 2 +- .../zigbee-switch/src/robb/init.lua | 2 +- .../zigbee-switch/src/sub_drivers.lua | 3 +- .../test/test_all_capability_zigbee_bulb.lua | 21 ++++++ .../test/test_duragreen_color_temp_bulb.lua | 2 + .../src/test/test_sengled_color_temp_bulb.lua | 2 + .../src/test/test_white_color_temp_bulb.lua | 2 + .../src/test/test_zll_rgbw_bulb.lua | 2 + .../zigbee-switch/src/wallhero/init.lua | 2 +- .../src/zigbee-dimmer-power-energy/init.lua | 2 +- .../src/zigbee-dimming-light/init.lua | 2 +- .../src/zigbee-dual-metering-switch/init.lua | 2 +- .../init.lua | 2 +- .../zigbee-switch/src/zll-polling/init.lua | 3 +- tools/run_driver_tests.py | 4 +- 27 files changed, 154 insertions(+), 20 deletions(-) create mode 100644 drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/init.lua 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 9280a30d0d..4b2146f03c 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/init.lua @@ -86,7 +86,7 @@ end local aqara_multi_switch_handler = { NAME = "Aqara Multi Switch Handler", lifecycle_handlers = { - init = configurations.power_reconfig_wrapper(device_init), + init = configurations.reconfig_wrapper(device_init), added = device_added }, can_handle = require("aqara.multi-switch.can_handle"), diff --git a/drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/can_handle.lua new file mode 100644 index 0000000000..56cd729659 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" + +return function(opts, driver, device) + if device:supports_capability(capabilities.colorTemperature) then + local subdriver = require("color_temp_range_handlers") + return true, subdriver + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/init.lua b/drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/init.lua new file mode 100644 index 0000000000..761ac49736 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/init.lua @@ -0,0 +1,72 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local utils = require "st.utils" +local KELVIN_MAX = "_max_kelvin" +local KELVIN_MIN = "_min_kelvin" +local MIREDS_CONVERSION_CONSTANT = 1000000 +local COLOR_TEMPERATURE_KELVIN_MAX = 15000 +local COLOR_TEMPERATURE_KELVIN_MIN = 1000 +local COLOR_TEMPERATURE_MIRED_MAX = utils.round(MIREDS_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MIN) -- 1000 +local COLOR_TEMPERATURE_MIRED_MIN = utils.round(MIREDS_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MAX) -- 67 + +local function color_temp_min_mireds_handler(driver, device, value, zb_rx) + local temp_in_mired = value.value + local endpoint = zb_rx.address_header.src_endpoint.value + if temp_in_mired == nil then + return + end + if (temp_in_mired < COLOR_TEMPERATURE_MIRED_MIN or temp_in_mired > COLOR_TEMPERATURE_MIRED_MAX) then + device.log.warn_with({hub_logs = true}, string.format("Device reported a color temperature %d mired outside of sane range of %.2f-%.2f", temp_in_mired, COLOR_TEMPERATURE_MIRED_MIN, COLOR_TEMPERATURE_MIRED_MAX)) + return + end + local temp_in_kelvin = utils.round(MIREDS_CONVERSION_CONSTANT / temp_in_mired) + device:set_field(KELVIN_MAX..endpoint, temp_in_kelvin) + local min = device:get_field(KELVIN_MIN..endpoint) + if min ~= nil then + if temp_in_kelvin > min then + device:emit_event_for_endpoint(endpoint, capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = min, maximum = temp_in_kelvin}})) + else + device.log.warn_with({hub_logs = true}, string.format("Device reported a max color temperature %d K that is not higher than the reported min color temperature %d K", min, temp_in_kelvin)) + end + end +end + +local function color_temp_max_mireds_handler(driver, device, value, zb_rx) + local temp_in_mired = value.value + local endpoint = zb_rx.address_header.src_endpoint.value + if temp_in_mired == nil then + return + end + if (temp_in_mired < COLOR_TEMPERATURE_MIRED_MIN or temp_in_mired > COLOR_TEMPERATURE_MIRED_MAX) then + device.log.warn_with({hub_logs = true}, string.format("Device reported a color temperature %d mired outside of sane range of %.2f-%.2f", temp_in_mired, COLOR_TEMPERATURE_MIRED_MIN, COLOR_TEMPERATURE_MIRED_MAX)) + return + end + local temp_in_kelvin = utils.round(MIREDS_CONVERSION_CONSTANT / temp_in_mired) + device:set_field(KELVIN_MIN..endpoint, temp_in_kelvin) + local max = device:get_field(KELVIN_MAX..endpoint) + if max ~= nil then + if temp_in_kelvin < max then + device:emit_event_for_endpoint(endpoint, capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = temp_in_kelvin, maximum = max}})) + else + device.log.warn_with({hub_logs = true}, string.format("Device reported a min color temperature %d K that is not lower than the reported max color temperature %d K", temp_in_kelvin, max)) + end + end +end + +local color_temp_range_handlers = { + NAME = "Color temp range handlers", + zigbee_handlers = { + attr = { + [clusters.ColorControl.ID] = { + [clusters.ColorControl.attributes.ColorTempPhysicalMinMireds.ID] = color_temp_min_mireds_handler, + [clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds.ID] = color_temp_max_mireds_handler + } + } + }, + can_handle = require("color_temp_range_handlers.can_handle") +} + +return color_temp_range_handlers diff --git a/drivers/SmartThings/zigbee-switch/src/configurations/init.lua b/drivers/SmartThings/zigbee-switch/src/configurations/init.lua index 01f42abf91..a5e55b3953 100644 --- a/drivers/SmartThings/zigbee-switch/src/configurations/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/configurations/init.lua @@ -84,7 +84,7 @@ configurations.handle_reporting_config_response = function(driver, device, zb_me end end -configurations.power_reconfig_wrapper = function(orig_function) +configurations.reconfig_wrapper = function(orig_function) local new_init = function(driver, device) local config_version = device:get_field(CONFIGURATION_VERSION_KEY) if config_version == nil or config_version < driver.current_config_version then @@ -92,6 +92,18 @@ configurations.power_reconfig_wrapper = function(orig_function) driver._reconfig_timer = driver:call_with_delay(5*60, configurations.check_and_reconfig_devices, "reconfig_power_devices") end end + + local capabilities = require "st.capabilities" + for id, _ in pairs(device.profile.components) do + if device:supports_capability(capabilities.colorTemperature, id) and + device:get_latest_state(id, capabilities.colorTemperature.ID, capabilities.colorTemperature.colorTemperatureRange.NAME) == nil then + local clusters = require "st.zigbee.zcl.clusters" + driver:call_with_delay(5*60, function() + device:send_to_component(id, clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:read(device)) + device:send_to_component(id, clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:read(device)) + end) + end + end orig_function(driver, device) end return new_init diff --git a/drivers/SmartThings/zigbee-switch/src/ezex/init.lua b/drivers/SmartThings/zigbee-switch/src/ezex/init.lua index 6c2d9e45e3..ff1b2deb7b 100644 --- a/drivers/SmartThings/zigbee-switch/src/ezex/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/ezex/init.lua @@ -12,7 +12,7 @@ end local ezex_switch_handler = { NAME = "ezex switch handler", lifecycle_handlers = { - init = configurations.power_reconfig_wrapper(do_init) + init = configurations.reconfig_wrapper(do_init) }, can_handle = require("ezex.can_handle"), } diff --git a/drivers/SmartThings/zigbee-switch/src/frient-IO/init.lua b/drivers/SmartThings/zigbee-switch/src/frient-IO/init.lua index 293583f442..d7601ab535 100644 --- a/drivers/SmartThings/zigbee-switch/src/frient-IO/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/frient-IO/init.lua @@ -478,7 +478,7 @@ local frient_bridge_handler = { }, lifecycle_handlers = { added = added_handler, - init = init_handler, + init = configurationMap.reconfig_wrapper(init_handler), doConfigure = configure_handler, infoChanged = info_changed_handler }, diff --git a/drivers/SmartThings/zigbee-switch/src/frient/init.lua b/drivers/SmartThings/zigbee-switch/src/frient/init.lua index a615c721f4..e546de1a14 100644 --- a/drivers/SmartThings/zigbee-switch/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/frient/init.lua @@ -152,7 +152,7 @@ local frient_smart_plug = { }, }, lifecycle_handlers = { - init = device_init, + init = configurationMap.reconfig_wrapper(device_init), doConfigure = do_configure, added = device_added, }, diff --git a/drivers/SmartThings/zigbee-switch/src/hanssem/init.lua b/drivers/SmartThings/zigbee-switch/src/hanssem/init.lua index 083893448b..0ab333af8e 100644 --- a/drivers/SmartThings/zigbee-switch/src/hanssem/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/hanssem/init.lua @@ -50,7 +50,7 @@ local HanssemSwitch = { NAME = "Zigbee Hanssem Switch", lifecycle_handlers = { added = device_added, - init = configurations.power_reconfig_wrapper(device_init) + init = configurations.reconfig_wrapper(device_init) }, can_handle = require("hanssem.can_handle"), } 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 a026ac6564..a236f1c1cc 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 @@ -147,7 +147,7 @@ end local ikea_xy_color_bulb = { NAME = "IKEA XY Color Bulb", lifecycle_handlers = { - init = configurationMap.power_reconfig_wrapper(device_init) + init = configurationMap.reconfig_wrapper(device_init) }, capability_handlers = { [capabilities.colorControl.ID] = { diff --git a/drivers/SmartThings/zigbee-switch/src/init.lua b/drivers/SmartThings/zigbee-switch/src/init.lua index aa245f5910..062ac68782 100644 --- a/drivers/SmartThings/zigbee-switch/src/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/init.lua @@ -80,7 +80,7 @@ local zigbee_switch_driver_template = { }, current_config_version = 1, lifecycle_handlers = { - init = configurationMap.power_reconfig_wrapper(device_init), + init = configurationMap.reconfig_wrapper(device_init), added = lazy_handler("lifecycle_handlers.device_added"), infoChanged = lazy_handler("lifecycle_handlers.info_changed"), doConfigure = lazy_handler("lifecycle_handlers.do_configure"), diff --git a/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua b/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua index 2833843793..22e5791b0e 100755 --- a/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua @@ -48,7 +48,7 @@ local laisiao_bath_heater = { capabilities.switch, }, lifecycle_handlers = { - init = configurations.power_reconfig_wrapper(device_init), + init = configurations.reconfig_wrapper(device_init), }, capability_handlers = { [capabilities.switch.ID] = { 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 465969a755..4058a953bb 100644 --- a/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/do_configure.lua +++ b/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/do_configure.lua @@ -17,4 +17,11 @@ return function(self, device) device:send(clusters.SimpleMetering.attributes.Divisor:read(device)) device:send(clusters.SimpleMetering.attributes.Multiplier:read(device)) end + + if device:supports_capability(capabilities.colorTemperature) then + local clusters = require "st.zigbee.zcl.clusters" + -- min and max for color temperature + device:send(clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:read(device)) + device:send(clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:read(device)) + end end 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 ba0ed07ae3..bac6368ad2 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 @@ -49,7 +49,7 @@ end local multi_switch_no_master = { NAME = "multi switch no master", lifecycle_handlers = { - init = configurations.power_reconfig_wrapper(device_init), + init = configurations.reconfig_wrapper(device_init), added = device_added }, can_handle = require("multi-switch-no-master.can_handle"), diff --git a/drivers/SmartThings/zigbee-switch/src/robb/init.lua b/drivers/SmartThings/zigbee-switch/src/robb/init.lua index 1a8c2948c3..aa2487c87a 100644 --- a/drivers/SmartThings/zigbee-switch/src/robb/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/robb/init.lua @@ -32,7 +32,7 @@ local robb_dimmer_handler = { } }, lifecycle_handlers = { - init = configurations.power_reconfig_wrapper(do_init) + init = configurations.reconfig_wrapper(do_init) }, can_handle = require("robb.can_handle"), } diff --git a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua index c232b40329..5dcf24ca74 100644 --- a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua @@ -34,5 +34,6 @@ return { lazy_load_if_possible("laisiao"), lazy_load_if_possible("tuya-multi"), lazy_load_if_possible("frient"), - lazy_load_if_possible("frient-IO") + lazy_load_if_possible("frient-IO"), + lazy_load_if_possible("color_temp_range_handlers") } 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 b581c5c61e..1da93191fa 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 @@ -393,6 +393,8 @@ test.register_coroutine_test( mock_device.id, SimpleMetering.attributes.Divisor:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -452,6 +454,7 @@ test.register_coroutine_test( test.register_coroutine_test( "configuration version below 1 config response not success", function() + test.timer.__create_and_queue_test_time_advance_timer(5*60, "oneshot") test.timer.__create_and_queue_test_time_advance_timer(5*60, "oneshot") assert(mock_device:get_field("_configuration_version") == nil) test.mock_device.add_test_device(mock_device) @@ -459,6 +462,8 @@ test.register_coroutine_test( test.wait_for_events() test.socket.zigbee:__expect_send({mock_device.id, ElectricalMeasurement.attributes.ActivePower:configure_reporting(mock_device, 5, 600, 5)}) test.socket.zigbee:__expect_send({mock_device.id, SimpleMetering.attributes.InstantaneousDemand:configure_reporting(mock_device, 5, 600, 5)}) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) test.mock_time.advance_time(5*60 + 1) test.wait_for_events() test.socket.zigbee:__queue_receive({mock_device.id, build_config_response_msg(mock_device, ElectricalMeasurement.ID, Status.UNSUPPORTED_ATTRIBUTE)}) @@ -476,6 +481,7 @@ test.register_coroutine_test( test.register_coroutine_test( "configuration version below 1 individual config response records ElectricalMeasurement", function() + test.timer.__create_and_queue_test_time_advance_timer(5*60, "oneshot") test.timer.__create_and_queue_test_time_advance_timer(5*60, "oneshot") assert(mock_device:get_field("_configuration_version") == nil) test.mock_device.add_test_device(mock_device) @@ -483,6 +489,8 @@ test.register_coroutine_test( test.wait_for_events() test.socket.zigbee:__expect_send({mock_device.id, ElectricalMeasurement.attributes.ActivePower:configure_reporting(mock_device, 5, 600, 5)}) test.socket.zigbee:__expect_send({mock_device.id, SimpleMetering.attributes.InstantaneousDemand:configure_reporting(mock_device, 5, 600, 5)}) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) test.mock_time.advance_time(5*60 + 1) test.wait_for_events() test.socket.zigbee:__queue_receive({mock_device.id, build_config_response_msg(mock_device, ElectricalMeasurement.ID, nil, ElectricalMeasurement.attributes.ActivePower.ID, Status.SUCCESS)}) @@ -499,6 +507,7 @@ test.register_coroutine_test( test.register_coroutine_test( "configuration version below 1 individual config response records SimpleMetering", function() + test.timer.__create_and_queue_test_time_advance_timer(5*60, "oneshot") test.timer.__create_and_queue_test_time_advance_timer(5*60, "oneshot") assert(mock_device:get_field("_configuration_version") == nil) test.mock_device.add_test_device(mock_device) @@ -506,6 +515,8 @@ test.register_coroutine_test( test.wait_for_events() test.socket.zigbee:__expect_send({mock_device.id, ElectricalMeasurement.attributes.ActivePower:configure_reporting(mock_device, 5, 600, 5)}) test.socket.zigbee:__expect_send({mock_device.id, SimpleMetering.attributes.InstantaneousDemand:configure_reporting(mock_device, 5, 600, 5)}) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) test.mock_time.advance_time(5*60 + 1) test.wait_for_events() test.socket.zigbee:__queue_receive({mock_device.id, build_config_response_msg(mock_device, SimpleMetering.ID, nil, SimpleMetering.attributes.InstantaneousDemand.ID, Status.SUCCESS)}) @@ -569,6 +580,16 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Color temperature range report test", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.zigbee:__queue_receive({mock_device.id, clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_attr_report(mock_device, 370)}) + test.socket.zigbee:__queue_receive({mock_device.id, clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_attr_report(mock_device, 153)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 2703, maximum = 6536}))) + end +) + test.register_coroutine_test( "energy meter reset command test", function() 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 55c491e77d..6441871df9 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 @@ -73,6 +73,8 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end ) 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 e2ddba42d3..4be9792fc1 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 @@ -73,6 +73,8 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end ) 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 f024aa45be..f59bc13c89 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 @@ -73,6 +73,8 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end ) 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 cf53f7e359..ddc3c419f9 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 @@ -83,6 +83,8 @@ test.register_coroutine_test( ColorControl.attributes.CurrentSaturation:configure_reporting(mock_device, 1, 3600, 16) } ) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end ) diff --git a/drivers/SmartThings/zigbee-switch/src/wallhero/init.lua b/drivers/SmartThings/zigbee-switch/src/wallhero/init.lua index 1e0e7eb26e..5ad97bfb29 100644 --- a/drivers/SmartThings/zigbee-switch/src/wallhero/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/wallhero/init.lua @@ -102,7 +102,7 @@ local wallheroswitch = { NAME = "Zigbee Wall Hero Switch", lifecycle_handlers = { added = device_added, - init = configurations.power_reconfig_wrapper(device_init), + init = configurations.reconfig_wrapper(device_init), infoChanged = device_info_changed }, zigbee_handlers = { 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 dc37bf1181..869678ef24 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 @@ -41,7 +41,7 @@ local zigbee_dimmer_power_energy_handler = { } }, lifecycle_handlers = { - init = configurations.power_reconfig_wrapper(device_init), + init = configurations.reconfig_wrapper(device_init), doConfigure = do_configure, }, can_handle = require("zigbee-dimmer-power-energy.can_handle"), 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 dc8629c57b..880e947163 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/init.lua @@ -48,7 +48,7 @@ end local zigbee_dimming_light = { NAME = "Zigbee Dimming Light", lifecycle_handlers = { - init = configurations.power_reconfig_wrapper(device_init), + init = configurations.reconfig_wrapper(device_init), added = device_added, doConfigure = do_configure }, 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 b1d10f94a6..f88c6396f5 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 @@ -52,7 +52,7 @@ local zigbee_dual_metering_switch = { } }, lifecycle_handlers = { - init = configurations.power_reconfig_wrapper(device_init), + init = configurations.reconfig_wrapper(device_init), added = device_added }, can_handle = require("zigbee-dual-metering-switch.can_handle"), 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 53a7e1eb99..1845878f19 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 @@ -41,7 +41,7 @@ local zigbee_metering_plug_power_conumption_report = { } }, lifecycle_handlers = { - init = configurations.power_reconfig_wrapper(device_init), + init = configurations.reconfig_wrapper(device_init), doConfigure = do_configure }, can_handle = require("zigbee-metering-plug-power-consumption-report.can_handle"), diff --git a/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua b/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua index 3478b39bd5..b464b30acc 100644 --- a/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua @@ -3,6 +3,7 @@ local device_lib = require "st.device" local clusters = require "st.zigbee.zcl.clusters" +local configurationMap = require "configurations" local function set_up_zll_polling(driver, device) local INFREQUENT_POLL_COUNTER = "_infrequent_poll_counter" @@ -33,7 +34,7 @@ end local ZLL_polling = { NAME = "ZLL Polling", lifecycle_handlers = { - init = set_up_zll_polling + init = configurationMap.reconfig_wrapper(set_up_zll_polling) }, can_handle = require("zll-polling.can_handle"), } diff --git a/tools/run_driver_tests.py b/tools/run_driver_tests.py index e3e58f2154..e686a0f4b7 100755 --- a/tools/run_driver_tests.py +++ b/tools/run_driver_tests.py @@ -117,10 +117,10 @@ def run_tests(verbosity_level, filter, junit, coverage_files, html): test_status = "" test_logs = "" test_done = False - if re.match("^\s*$", line) is None: + if re.match(r"^\s*$", line) is None: last_line = line - m = re.match("Passed (\d+) of (\d+) tests", last_line) + m = re.match(r"Passed (\d+) of (\d+) tests", last_line) if m is None: failure_files[test_file].append("\n ".join(a.stderr.decode().split("\n"))) test_case = junit_xml.TestCase(test_suite.name) From ca15beeadde9d710216571eadc654c2086e4a072 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 3 Mar 2026 16:13:02 -0800 Subject: [PATCH 141/538] WWSTCERT-10528 HAOJAI Smart Switch --- drivers/SmartThings/matter-switch/fingerprints.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 1c8adddff4..01bf9ba425 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -753,6 +753,14 @@ matterManufacturer: vendorId: 0x1387 productId: 0x6094 deviceProfileName: light-color-level + +# Haojai + - id: "5530/4098" + deviceLabel: HAOJAI Smart Switch + vendorId: 0x159A + productId: 0x1002 + deviceProfileName: 6-button-motion + # Hue - id: "4107/2049" deviceLabel: Hue W 1600 A21 E26 1P NAM From ae55d8f7ac96b31d1144e0bd65e3c7f54be156a7 Mon Sep 17 00:00:00 2001 From: Marcin Krystian Tyminski <81477027+marcintyminski@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:15:21 +0100 Subject: [PATCH 142/538] WWSTCERT-10511/WWSTCERT-10514 Add support to frient smoke co sensor (#2776) * initial commit * handle co value * complete driver with tests * removed unused variable * changes according to pr comments * pr comments changes --- .../fingerprints.yml | 10 + .../frient-smoke-co-temperature-battery.yml | 55 ++ .../src/frient/can_handle.lua | 15 + .../src/frient/fingerprints.lua | 9 + .../src/frient/init.lua | 236 ++++++++ .../src/init.lua | 5 + .../src/sub_drivers.lua | 3 +- ...st_frient_co_smoke_temperature_battery.lua | 506 ++++++++++++++++++ 8 files changed, 838 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/profiles/frient-smoke-co-temperature-battery.yml create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/init.lua create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_frient_co_smoke_temperature_battery.lua diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/fingerprints.yml b/drivers/SmartThings/zigbee-carbon-monoxide-detector/fingerprints.yml index 3c05e02436..eb0d7e0209 100644 --- a/drivers/SmartThings/zigbee-carbon-monoxide-detector/fingerprints.yml +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/fingerprints.yml @@ -14,3 +14,13 @@ zigbeeManufacturer: manufacturer: HEIMAN model: COSensor-EM deviceProfileName: carbonMonoxide-battery + - id: "frient A/S/SCAZB-143" + deviceLabel: Frient Carbon Monoxide Detector + manufacturer: frient A/S + model: SCAZB-143 + deviceProfileName: frient-smoke-co-temperature-battery + - id: "frient A/S/SCAZB-141" + deviceLabel: Frient Carbon Monoxide Detector + manufacturer: frient A/S + model: SCAZB-141 + deviceProfileName: frient-smoke-co-temperature-battery \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/profiles/frient-smoke-co-temperature-battery.yml b/drivers/SmartThings/zigbee-carbon-monoxide-detector/profiles/frient-smoke-co-temperature-battery.yml new file mode 100644 index 0000000000..3a22a0ad77 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/profiles/frient-smoke-co-temperature-battery.yml @@ -0,0 +1,55 @@ +name: frient-smoke-co-temperature-battery +components: +- id: main + capabilities: + - id: smokeDetector + version: 1 + - id: carbonMonoxideDetector + version: 1 + - id: carbonMonoxideMeasurement + version: 1 + - id: tamperAlert + version: 1 + - 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: SmokeDetector +preferences: +- title: "Max alarm duration (s)" + name: maxWarningDuration + description: "After how many seconds should the alarm turn off" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 65534 + default: 240 +- preferenceId: tempOffset + explicit: true +- title: "Temperature Sensitivity (°C)" + 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-carbon-monoxide-detector/src/frient/can_handle.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/can_handle.lua new file mode 100644 index 0000000000..f451011e03 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_frient_smoke_carbon_monoxide = function(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 is_frient_smoke_carbon_monoxide diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/fingerprints.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/fingerprints.lua new file mode 100644 index 0000000000..22180458e9 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FRIENT_SMOKE_CARBON_MONOXIDE_FINGERPRINTS = { + { mfr = "frient A/S", model = "SCAZB-141" }, + { mfr = "frient A/S", model = "SCAZB-143" } +} + +return FRIENT_SMOKE_CARBON_MONOXIDE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/init.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/init.lua new file mode 100644 index 0000000000..4f7d8fa8d5 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/init.lua @@ -0,0 +1,236 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local zcl_clusters = require "st.zigbee.zcl.clusters" +local data_types = require "st.zigbee.data_types" +local battery_defaults = require "st.zigbee.defaults.battery_defaults" +local SinglePrecisionFloat = require "st.zigbee.data_types.SinglePrecisionFloat" +local zcl_global_commands = require "st.zigbee.zcl.global_commands" +local Status = require "st.zigbee.generated.types.ZclStatus" + +local IASZone = zcl_clusters.IASZone +local CarbonMonoxideCluster = zcl_clusters.CarbonMonoxide +local TemperatureMeasurement = zcl_clusters.TemperatureMeasurement +local IASWD = zcl_clusters.IASWD +local SirenConfiguration = IASWD.types.SirenConfiguration +local carbonMonoxide = capabilities.carbonMonoxideDetector +local alarm = capabilities.alarm +local smokeDetector = capabilities.smokeDetector +local carbonMonoxideMeasurement = capabilities.carbonMonoxideMeasurement +local tamperAlert = capabilities.tamperAlert + +local ALARM_COMMAND = "alarmCommand" +local DEFAULT_MAX_WARNING_DURATION = 0x00F0 +local CarbonMonoxideEndpoint = 0x2E +local SmokeAlarmEndpoint = 0x23 +local TEMPERATURE_ENDPOINT = 0x26 + +local alarm_command = { + OFF = 0, + SIREN = 1 +} + +local CONFIGURATIONS = { + { + cluster = CarbonMonoxideCluster.ID, + attribute = CarbonMonoxideCluster.attributes.MeasuredValue.ID, + minimum_interval = 30, + maximum_interval = 600, + data_type = data_types.SinglePrecisionFloat, + reportable_change = SinglePrecisionFloat(0, -20, 0.048576) -- 0, -20, 0.048576 is 1ppm in SinglePrecisionFloat + } +} + +local function get_current_max_warning_duration(device) + return device.preferences.maxWarningDuration == nil and DEFAULT_MAX_WARNING_DURATION or device.preferences.maxWarningDuration +end + +local function device_added(driver, device) + device:emit_event(alarm.alarm.off()) + device:emit_event(smokeDetector.smoke.clear()) + device:emit_event(carbonMonoxide.carbonMonoxide.clear()) + device:emit_event(tamperAlert.tamper.clear()) + device:emit_event(carbonMonoxideMeasurement.carbonMonoxideLevel({value = 0, unit = "ppm"})) +end + +local function device_init(driver, device) + battery_defaults.build_linear_voltage_init(2.6, 3.1)(driver, device) + if CONFIGURATIONS ~= nil then + for _, attribute in ipairs(CONFIGURATIONS) do + device:add_configured_attribute(attribute) + end + end +end + +local function generate_event_from_zone_status(driver, device, zone_status, zigbee_message) + local endpoint = zigbee_message.address_header.src_endpoint.value + if endpoint == SmokeAlarmEndpoint then + if zone_status:is_test_set() then + device:emit_event(smokeDetector.smoke.tested()) + elseif zone_status:is_alarm1_set() then + device:emit_event(smokeDetector.smoke.detected()) + else + device.thread:call_with_delay(6, function () + device:emit_event(smokeDetector.smoke.clear()) + end) + end + elseif endpoint == CarbonMonoxideEndpoint then + if zone_status:is_test_set() then + device:emit_event(carbonMonoxide.carbonMonoxide.tested()) + elseif zone_status:is_alarm1_set() then + device:emit_event(carbonMonoxide.carbonMonoxide.detected()) + else + device.thread:call_with_delay(6, function () + device:emit_event(carbonMonoxide.carbonMonoxide.clear()) + end) + end + end + if zone_status:is_tamper_set() then + device:emit_event(tamperAlert.tamper.detected()) + else + device:emit_event(tamperAlert.tamper.clear()) + end +end + +local function ias_zone_status_change_handler(driver, device, zb_rx) + local zone_status = zb_rx.body.zcl_body.zone_status + generate_event_from_zone_status(driver, device, zone_status, zb_rx) +end + +local function carbon_monoxide_measure_value_attr_handler(driver, device, attr_val, zb_rx) + local co_value = attr_val.value + if co_value <= 1 then + co_value = co_value * 1000000 + else + return + end + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, carbonMonoxideMeasurement.carbonMonoxideLevel({value = co_value, unit = "ppm"})) +end + +local function do_configure(driver, device) + device:configure() + local maxWarningDuration = get_current_max_warning_duration(device) + device:send(IASWD.attributes.MaxDuration:write(device, maxWarningDuration):to_endpoint(0x23)) + + device.thread:call_with_delay(5, function() + device:refresh() + end) +end + +local function send_siren_command(device, warning_mode, warning_siren_level) + local warning_duration = get_current_max_warning_duration(device) + local siren_configuration + + siren_configuration = SirenConfiguration(0x00) + siren_configuration:set_warning_mode(warning_mode) + siren_configuration:set_siren_level(warning_siren_level) + + device:send( + IASWD.server.commands.StartWarning( + device, + siren_configuration, + data_types.Uint16(warning_duration), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + ) +end + +local function siren_switch_off_handler(driver, device, command) + device:set_field(ALARM_COMMAND, alarm_command.OFF, {persist = true}) + send_siren_command(device, 0x00, 0x00) +end + +local function siren_alarm_siren_handler(driver, device, command) + device:set_field(ALARM_COMMAND, alarm_command.SIREN, {persist = true}) + send_siren_command(device, 0x01 , 0x01) + + local warningDurationDelay = get_current_max_warning_duration(device) + + device.thread:call_with_delay(warningDurationDelay, function() -- Send command to switch from siren to off in the app when the siren is done + if(device:get_field(ALARM_COMMAND) == alarm_command.SIREN) then + siren_switch_off_handler(driver, device, command) + end + end) +end + +local emit_alarm_event = function(device, cmd) + if cmd == alarm_command.OFF then + device:emit_event(capabilities.alarm.alarm.off()) + elseif cmd == alarm_command.SIREN then + device:emit_event(capabilities.alarm.alarm.siren()) + end +end + +local default_response_handler = function(driver, device, zigbee_message) + local is_success = zigbee_message.body.zcl_body.status.value + local command = zigbee_message.body.zcl_body.cmd.value + local alarm_ev = device:get_field(ALARM_COMMAND) + + if command == IASWD.server.commands.StartWarning.ID and is_success == Status.SUCCESS then + if alarm_ev ~= alarm_command.OFF then + emit_alarm_event(device, alarm_ev) + local lastDuration = get_current_max_warning_duration(device) + device.thread:call_with_delay(lastDuration, function(d) + device:emit_event(capabilities.alarm.alarm.off()) + end) + else + emit_alarm_event(device,alarm_command.OFF) + end + end +end + +local function info_changed(driver, device, event, args) + for name, info in pairs(device.preferences) do + if (device.preferences[name] ~= nil and args.old_st_store.preferences[name] ~= device.preferences[name]) then + if (name == "maxWarningDuration") then + local input = device.preferences.maxWarningDuration + device:send(IASWD.attributes.MaxDuration:write(device, input)) + elseif (name == "temperatureSensitivity") then + local sensitivity = device.preferences.temperatureSensitivity + local temperatureSensitivity = math.floor(sensitivity * 100 + 0.5) + device:send(TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(device, 30, 600, temperatureSensitivity):to_endpoint(TEMPERATURE_ENDPOINT)) + end + end + end +end + +local frient_smoke_carbon_monoxide = { + NAME = "Frient Smoke Carbon Monoxide", + lifecycle_handlers = { + added = device_added, + init = device_init, + configure = do_configure, + infoChanged = info_changed, + }, + capability_handlers = { + [alarm.ID] = { + [alarm.commands.off.NAME] = siren_switch_off_handler, + [alarm.commands.siren.NAME] = siren_alarm_siren_handler + } + }, + zigbee_handlers = { + global = { + [IASWD.ID] = { + [zcl_global_commands.DEFAULT_RESPONSE_ID] = default_response_handler + } + }, + cluster = { + [IASZone.ID] = { + [IASZone.client.commands.ZoneStatusChangeNotification.ID] = ias_zone_status_change_handler + } + }, + attr = { + [IASZone.ID] = { + [IASZone.attributes.ZoneStatus.ID] = generate_event_from_zone_status + }, + [CarbonMonoxideCluster.ID] = { + [CarbonMonoxideCluster.attributes.MeasuredValue.ID] = carbon_monoxide_measure_value_attr_handler + } + } + }, + can_handle = require("frient.can_handle"), +} + +return frient_smoke_carbon_monoxide \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua index 8ef8a50795..4ddb66aa4a 100644 --- a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua @@ -12,6 +12,11 @@ local zigbee_carbon_monoxide_driver_template = { supported_capabilities = { capabilities.carbonMonoxideDetector, capabilities.battery, + capabilities.carbonMonoxideMeasurement, + capabilities.temperatureMeasurement, + capabilities.smokeDetector, + capabilities.tamperAlert, + capabilities.alarm }, ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, health_check = false, diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/sub_drivers.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/sub_drivers.lua index 6a7a185392..c826ab7d00 100644 --- a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/sub_drivers.lua @@ -4,7 +4,8 @@ local lazy_load_if_possible = require "lazy_load_subdriver" local sub_drivers = { - lazy_load_if_possible("ClimaxTechnology") + lazy_load_if_possible("ClimaxTechnology"), + lazy_load_if_possible("frient"), } return sub_drivers diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_frient_co_smoke_temperature_battery.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_frient_co_smoke_temperature_battery.lua new file mode 100644 index 0000000000..16625fdf92 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_frient_co_smoke_temperature_battery.lua @@ -0,0 +1,506 @@ +-- Copyright 2026 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 IASZone = clusters.IASZone +local IASWD = clusters.IASWD +local CarbonMonoxideCluster = clusters.CarbonMonoxide +local PowerConfiguration = clusters.PowerConfiguration +local TemperatureMeasurement = clusters.TemperatureMeasurement +local capabilities = require "st.capabilities" +local alarm = capabilities.alarm +local smokeDetector = capabilities.smokeDetector +local carbonMonoxideDetector = capabilities.carbonMonoxideDetector +local carbonMonoxideMeasurement = capabilities.carbonMonoxideMeasurement +local tamperAlert = capabilities.tamperAlert +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local data_types = require "st.zigbee.data_types" +local SinglePrecisionFloat = require "st.zigbee.data_types.SinglePrecisionFloat" +local device_management = require "st.zigbee.device_management" +local default_response = require "st.zigbee.zcl.global_commands.default_response" +local messages = require "st.zigbee.messages" +local zb_const = require "st.zigbee.constants" +local zcl_messages = require "st.zigbee.zcl" +local Status = require "st.zigbee.generated.types.ZclStatus" + +local SMOKE_ENDPOINT = 0x23 +local CO_ENDPOINT = 0x2E +local TEMPERATURE_ENDPOINT = 0x26 +local ALARM_COMMAND = "alarmCommand" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("frient-smoke-co-temperature-battery.yml"), + fingerprinted_endpoint_id = SMOKE_ENDPOINT, + zigbee_endpoints = { + [SMOKE_ENDPOINT] = { + id = SMOKE_ENDPOINT, + manufacturer = "frient A/S", + model = "SCAZB-143", + server_clusters = { PowerConfiguration.ID, IASZone.ID, IASWD.ID } + }, + [CO_ENDPOINT] = { + id = CO_ENDPOINT, + server_clusters = { IASZone.ID, CarbonMonoxideCluster.ID } + }, + [TEMPERATURE_ENDPOINT] = { + id = TEMPERATURE_ENDPOINT, + server_clusters = { TemperatureMeasurement.ID } + } + } + } +) + +local function build_default_response_msg(cluster, command, status, endpoint) + local addr_header = messages.AddressHeader( + mock_device:get_short_address(), + endpoint or SMOKE_ENDPOINT, + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + zb_const.HA_PROFILE_ID, + cluster + ) + local default_response_body = default_response.DefaultResponse(command, status) + local zcl_header = zcl_messages.ZclHeader({ + cmd = data_types.ZCLCommandId(default_response_body.ID) + }) + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_header, + zcl_body = default_response_body + }) + return messages.ZigbeeMessageRx({ + address_header = addr_header, + body = message_body + }) +end + +local function expect_bind_and_config(config, endpoint) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.build_bind_request(mock_device, config.cluster, zigbee_test_utils.mock_hub_eui, endpoint):to_endpoint(endpoint) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.attr_config(mock_device, config):to_endpoint(endpoint) + }) +end + +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( + "added lifecycle should set default states", + function() + 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", smokeDetector.smoke.clear()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideDetector.carbonMonoxide.clear()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideMeasurement.carbonMonoxideLevel({ value = 0, unit = "ppm" })) + ) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "init and doConfigure should bind, configure, and refresh", + function() + local battery_config = { + cluster = PowerConfiguration.ID, + attribute = PowerConfiguration.attributes.BatteryVoltage.ID, + minimum_interval = 30, + maximum_interval = 21600, + data_type = data_types.Uint8, + reportable_change = 1 + } + local ias_zone_config = { + cluster = IASZone.ID, + attribute = IASZone.attributes.ZoneStatus.ID, + minimum_interval = 0, + maximum_interval = 180, + data_type = IASZone.attributes.ZoneStatus.base_type + } + local co_config = { + cluster = CarbonMonoxideCluster.ID, + attribute = CarbonMonoxideCluster.attributes.MeasuredValue.ID, + minimum_interval = 30, + maximum_interval = 600, + data_type = data_types.SinglePrecisionFloat, + reportable_change = SinglePrecisionFloat(0, -20, 0.048576) + } + local temp_config = { + cluster = TemperatureMeasurement.ID, + attribute = TemperatureMeasurement.attributes.MeasuredValue.ID, + minimum_interval = 30, + maximum_interval = 600, + data_type = data_types.Int16, + reportable_change = data_types.Int16(100) + } + + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__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, "doConfigure" }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.attr_refresh(mock_device, PowerConfiguration.ID, PowerConfiguration.attributes.BatteryVoltage.ID):to_endpoint(SMOKE_ENDPOINT) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.attr_refresh(mock_device, IASZone.ID, IASZone.attributes.ZoneStatus.ID):to_endpoint(SMOKE_ENDPOINT) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.attr_refresh(mock_device, IASZone.ID, IASZone.attributes.ZoneStatus.ID):to_endpoint(CO_ENDPOINT) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.attr_refresh(mock_device, CarbonMonoxideCluster.ID, CarbonMonoxideCluster.attributes.MeasuredValue.ID):to_endpoint(CO_ENDPOINT) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.attr_refresh(mock_device, TemperatureMeasurement.ID, TemperatureMeasurement.attributes.MeasuredValue.ID):to_endpoint(TEMPERATURE_ENDPOINT) + }) + + expect_bind_and_config(battery_config, SMOKE_ENDPOINT) + expect_bind_and_config(ias_zone_config, SMOKE_ENDPOINT) + expect_bind_and_config(ias_zone_config, CO_ENDPOINT) + expect_bind_and_config(co_config, CO_ENDPOINT) + expect_bind_and_config(temp_config, TEMPERATURE_ENDPOINT) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.IASCIEAddress:write(mock_device, zigbee_test_utils.mock_hub_eui) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.server.commands.ZoneEnrollResponse(mock_device, 0x00, 0x00) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "IAS Zone smoke detected should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0001):from_endpoint(SMOKE_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", smokeDetector.smoke.detected()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + end +) + +test.register_coroutine_test( + "IAS Zone smoke tested should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0100):from_endpoint(SMOKE_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", smokeDetector.smoke.tested()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + end +) + +test.register_coroutine_test( + "IAS Zone smoke clear should be delayed", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(6, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000):from_endpoint(SMOKE_ENDPOINT) + }) + + test.mock_time.advance_time(6) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", smokeDetector.smoke.clear()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "IAS Zone carbon monoxide detected should be handled", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0001):from_endpoint(CO_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideDetector.carbonMonoxide.detected()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + end +) + +test.register_coroutine_test( + "IAS Zone carbon monoxide tested should be handled", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0100):from_endpoint(CO_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideDetector.carbonMonoxide.tested()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + end +) + +test.register_coroutine_test( + "IAS Zone carbon monoxide clear should be delayed", + function() + test.timer.__create_and_queue_test_time_advance_timer(6, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000):from_endpoint(CO_ENDPOINT) + }) + + test.mock_time.advance_time(6) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideDetector.carbonMonoxide.clear()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Tamper detected should be handled", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0004):from_endpoint(SMOKE_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.detected()) + ) + end +) + +test.register_coroutine_test( + "Tamper clear should be handled", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000):from_endpoint(SMOKE_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + end +) + +test.register_coroutine_test( + "Carbon monoxide measurement should scale values <= 1", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + CarbonMonoxideCluster.attributes.MeasuredValue:build_test_attr_report( + mock_device, + SinglePrecisionFloat(0, -20, 0.048576) + ):from_endpoint(CO_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideMeasurement.carbonMonoxideLevel({ value = 0.99999999747524, unit = "ppm" })) + ) + end +) + +test.register_coroutine_test( + "Carbon monoxide measurement should pass through values > 1", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + CarbonMonoxideCluster.attributes.MeasuredValue:build_test_attr_report( + mock_device, + SinglePrecisionFloat(0, -15, 0.572864) + ):from_endpoint(CO_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideMeasurement.carbonMonoxideLevel({ value = 47.999998059822, unit = "ppm" })) + ) + end +) + +test.register_coroutine_test( + "infoChanged should update maxWarningDuration and temperatureSensitivity", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + local updates = { + preferences = { + maxWarningDuration = 120, + temperatureSensitivity = 1.3 + } + } + + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.attributes.MaxDuration:write(mock_device, 120) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting( + mock_device, + 30, + 600, + 130 + ):to_endpoint(TEMPERATURE_ENDPOINT) + }) + end +) + +test.register_coroutine_test( + "Alarm siren command should send StartWarning and auto-off", + function() + mock_device.preferences.maxWarningDuration = 5 + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + + local expected_configuration = IASWD.types.SirenConfiguration(0x00) + expected_configuration:set_warning_mode(0x01) + expected_configuration:set_siren_level(0x01) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning( + mock_device, + expected_configuration, + data_types.Uint16(5), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) + + test.wait_for_events() + test.mock_time.advance_time(5) + + local expected_off_configuration = IASWD.types.SirenConfiguration(0x00) + expected_off_configuration:set_warning_mode(0x00) + expected_off_configuration:set_siren_level(0x00) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning( + mock_device, + expected_off_configuration, + data_types.Uint16(5), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) + end +) + +test.register_coroutine_test( + "Alarm off command should send StartWarning stop", + function() + mock_device.preferences.maxWarningDuration = 5 + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "off", args = {} } + }) + + local expected_configuration = IASWD.types.SirenConfiguration(0x00) + expected_configuration:set_warning_mode(0x00) + expected_configuration:set_siren_level(0x00) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning( + mock_device, + expected_configuration, + data_types.Uint16(5), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) + end +) + +test.register_coroutine_test( + "Default response to StartWarning should emit alarm events", + function() + mock_device.preferences.maxWarningDuration = 2 + mock_device:set_field(ALARM_COMMAND, 1, { persist = true }) + + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_default_response_msg(IASWD.ID, IASWD.server.commands.StartWarning.ID, Status.SUCCESS, SMOKE_ENDPOINT) + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", alarm.alarm.siren()) + ) + + test.wait_for_events() + test.mock_time.advance_time(2) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", alarm.alarm.off()) + ) + test.wait_for_events() + end +) + +test.run_registered_tests() + From 53cde2536da045cc3d64a00276d178237bdcba64 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:42:29 -0600 Subject: [PATCH 143/538] Matter Switch: Subscribe to power topology attributes rather than read them (#2792) --- .../SmartThings/matter-switch/src/init.lua | 12 +--- .../switch_handlers/attribute_handlers.lua | 8 +++ .../matter-switch/src/switch_utils/utils.lua | 66 ++++++++++++------- .../src/test/test_electrical_sensor_set.lua | 7 +- .../src/test/test_electrical_sensor_tree.lua | 4 +- 5 files changed, 53 insertions(+), 44 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index cac42e4483..c2f8f1c5ee 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -16,7 +16,6 @@ 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 @@ -37,8 +36,6 @@ 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 @@ -58,7 +55,6 @@ 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 @@ -106,15 +102,9 @@ function SwitchLifecycleHandlers.device_init(driver, device) if #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) == 0 then device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, {persist = true}) end + switch_utils.handle_electrical_sensor_info(device) device:extend_device("subscribe", switch_utils.subscribe) device:subscribe() - - -- device energy reporting must be handled cumulatively, periodically, or by both simultaneously. - -- 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_SUPPORTED, true, {persist = false}) - end end end diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index ae76709be8..b16610cf25 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -316,6 +316,10 @@ end --- 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) + if device:get_field(fields.profiling_data.POWER_TOPOLOGY) ~= nil then + device.log.warn("Received an AvailableEndpoints response after power topology has already been determined. Ignoring this response.") + return + end 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 @@ -341,6 +345,10 @@ end -- [[ DESCRIPTOR CLUSTER ATTRIBUTES ]] -- function AttributeHandlers.parts_list_handler(driver, device, ib, response) + if device:get_field(fields.profiling_data.POWER_TOPOLOGY) ~= nil then + device.log.warn("Received a PartsList response after power topology has already been determined. Ignoring this response.") + return + end 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 0592d9a342..9fd4c77c84 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -279,7 +279,8 @@ function utils.find_cluster_on_ep(ep, cluster_id, opts) 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 type(ep) ~= "table" then return nil end + for _, cluster in ipairs(ep.clusters or {}) 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") @@ -398,32 +399,32 @@ function utils.handle_electrical_sensor_info(device) 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)) + -- energy reporting must be handled by a cumulative report, a periodic report, or both attributes simultaneously. + -- To ensure a single source of truth, we only handle a device's periodic reporting if cumulative reporting is not supported. + for _, ep_info in ipairs(electrical_sensor_eps) do + if utils.find_cluster_on_ep(ep_info, clusters.ElectricalEnergyMeasurement.ID, + {feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY}) then + device:set_field(fields.CUMULATIVE_REPORTS_SUPPORTED, true) + break 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 + + -- check the feature map for the first (or only) Electrical Sensor EP if the device profiling has not been completed + if device:get_field(fields.profiling_data.POWER_TOPOLOGY) == nil then + 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) or + clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.TREE_TOPOLOGY, endpoint_power_topology_feature_map) then + -- 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 + device:set_field(fields.ELECTRICAL_SENSOR_EPS, electrical_sensor_eps) + 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 end - return end end @@ -510,6 +511,21 @@ function utils.subscribe(device) subscribe_request:with_info_block(ib) end + -- If the power topology of the device has not yet been determined, add the AvailableEndpoints (for SET topology) + -- or PartsList (for TREE topology) attributes to the list of subscribed attributes in order to map the device's electrical endpoints + -- to the proper device card(s). + if device:get_field(fields.profiling_data.POWER_TOPOLOGY) == nil then + local electrical_sensor_eps = utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.ELECTRICAL_SENSOR, { with_info = true }) or {} + local endpoint_power_topology_cluster = utils.find_cluster_on_ep(electrical_sensor_eps[1], clusters.PowerTopology.ID) or {} + if clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.SET_TOPOLOGY, endpoint_power_topology_cluster.feature_map or 0) then + local ib = im.InteractionInfoBlock(nil, clusters.PowerTopology.ID, clusters.PowerTopology.attributes.AvailableEndpoints.ID) + subscribe_request:with_info_block(ib) + elseif clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.TREE_TOPOLOGY, endpoint_power_topology_cluster.feature_map or 0) then + local ib = im.InteractionInfoBlock(nil, clusters.Descriptor.ID, clusters.Descriptor.attributes.PartsList.ID) + subscribe_request:with_info_block(ib) + end + end + if #subscribe_request.info_blocks > 0 then device:send(subscribe_request) end 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 index 8e3ad613da..8cabc4fe09 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -111,6 +111,7 @@ local subscribed_attributes_periodic = { clusters.OnOff.attributes.OnOff, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + clusters.PowerTopology.attributes.AvailableEndpoints, } local subscribed_attributes = { clusters.OnOff.attributes.OnOff, @@ -120,6 +121,7 @@ local subscribed_attributes = { clusters.ElectricalPowerMeasurement.attributes.ActivePower, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + clusters.PowerTopology.attributes.AvailableEndpoints, } local cumulative_report_val_19 = { @@ -180,9 +182,6 @@ local function test_init() 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 @@ -197,8 +196,6 @@ local function test_init_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 }) diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua index b548bb819b..5e6644ecac 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua @@ -87,6 +87,7 @@ local subscribed_attributes = { clusters.ElectricalPowerMeasurement.attributes.ActivePower, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + clusters.Descriptor.attributes.PartsList, } local cumulative_report_val_19 = { @@ -128,9 +129,6 @@ local function test_init() 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 }) end From 1f63cb0fe70800532b7ee1756fa5e92917cc7c13 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 5 Mar 2026 12:11:42 -0800 Subject: [PATCH 144/538] Revert "Merge pull request #2823 from SmartThingsCommunity/new_device/WWSTCERT-10528" This reverts commit 4351e1b98b21b5011192c1caba8845b47b0fa219, reversing changes made to a94358e748b1c22d02c5207f56db57a260859e31. --- drivers/SmartThings/matter-switch/fingerprints.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 01bf9ba425..1c8adddff4 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -753,14 +753,6 @@ matterManufacturer: vendorId: 0x1387 productId: 0x6094 deviceProfileName: light-color-level - -# Haojai - - id: "5530/4098" - deviceLabel: HAOJAI Smart Switch - vendorId: 0x159A - productId: 0x1002 - deviceProfileName: 6-button-motion - # Hue - id: "4107/2049" deviceLabel: Hue W 1600 A21 E26 1P NAM From 9ff09dac719ce5c6022935860409d6b2743bd50f Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 3 Mar 2026 11:53:07 -0600 Subject: [PATCH 145/538] CHAD-17683: Tagging passing tests with min_api_version=19 - Updated copyright on missing files and fixed missing newlines at EOF --- .../src/test/test_cook_top.lua | 14 +- .../src/test/test_dishwasher.lua | 24 + .../src/test/test_extractor_hood.lua | 39 +- .../src/test/test_laundry_dryer.lua | 27 + .../src/test/test_laundry_washer.lua | 6 + .../src/test/test_matter_appliance_rpc_5.lua | 60 +- .../src/test/test_microwave_oven.lua | 18 +- .../matter-appliance/src/test/test_oven.lua | 38 +- .../src/test/test_refrigerator.lua | 15 + .../src/test/skip_test_latching_switch.lua | 18 +- .../src/test/test_matter_button.lua | 33 + .../test/test_matter_button_parent_child.lua | 27 + .../src/test/test_matter_multi_button.lua | 72 +- .../src/test/test_battery_storage.lua | 23 +- .../matter-energy/src/test/test_evse.lua | 38 +- .../src/test/test_evse_energy_meas.lua | 8 +- .../src/test/test_solar_power.lua | 13 +- .../test_thread_border_router_network.lua | 25 +- .../src/test/test_aqara_matter_lock.lua | 32 +- .../src/test/test_bridged_matter_lock.lua | 10 +- .../matter-lock/src/test/test_matter_lock.lua | 35 +- .../src/test/test_matter_lock_battery.lua | 15 +- .../test/test_matter_lock_batteryLevel.lua | 3 + .../src/test/test_matter_lock_codes.lua | 78 +- .../src/test/test_matter_lock_cota.lua | 65 +- .../src/test/test_matter_lock_modular.lua | 45 +- .../src/test/test_matter_lock_unlatch.lua | 37 +- .../src/test/test_new_matter_lock.lua | 220 +- .../src/test/test_new_matter_lock_battery.lua | 60 +- .../src/test/test_matter_media_speaker.lua | 15 + .../test/test_matter_media_video_player.lua | 31 +- .../matter-rvc/src/test/test_matter_rvc.lua | 87 +- .../test/test_matter_air_quality_sensor.lua | 49 +- ...test_matter_air_quality_sensor_modular.lua | 10 +- .../test/test_matter_bosch_button_contact.lua | 21 + .../src/test/test_matter_flow_sensor.lua | 9 + .../test/test_matter_freeze_leak_sensor.lua | 24 +- .../src/test/test_matter_pressure_sensor.lua | 9 + .../src/test/test_matter_rain_sensor.lua | 6 + .../src/test/test_matter_sensor.lua | 29 +- .../src/test/test_matter_sensor_battery.lua | 15 +- .../test/test_matter_sensor_featuremap.lua | 15 +- .../src/test/test_matter_sensor_rpc.lua | 6 +- .../src/test/test_matter_smoke_co_alarm.lua | 33 + .../test_matter_smoke_co_alarm_battery.lua | 15 +- .../test/test_aqara_climate_sensor_w100.lua | 70 +- .../src/test/test_aqara_cube.lua | 10 +- .../src/test/test_aqara_light_switch_h2.lua | 10 +- .../src/test/test_electrical_sensor_set.lua | 79 +- .../src/test/test_electrical_sensor_tree.lua | 54 +- .../src/test/test_eve_energy.lua | 58 +- .../src/test/test_ikea_scroll.lua | 30 + .../test/test_light_illuminance_motion.lua | 48 +- .../src/test/test_matter_bridge.lua | 5 +- .../src/test/test_matter_button.lua | 52 +- .../src/test/test_matter_camera.lua | 200 +- .../src/test/test_matter_light_fan.lua | 20 +- .../src/test/test_matter_multi_button.lua | 94 +- .../test/test_matter_multi_button_motion.lua | 74 +- .../test_matter_multi_button_switch_mcd.lua | 44 +- .../test_matter_sensor_offset_preferences.lua | 15 +- .../src/test/test_matter_switch.lua | 99 +- .../test/test_matter_switch_device_types.lua | 68 +- .../src/test/test_matter_water_valve.lua | 24 + .../src/test/test_multi_switch_mcd.lua | 18 +- .../test_multi_switch_parent_child_lights.lua | 40 +- .../test_multi_switch_parent_child_plugs.lua | 35 +- .../src/test/test_stateless_step.lua | 6 + .../src/test/test_third_reality_mk1.lua | 5 +- .../src/test/test_matter_air_purifier.lua | 49 +- .../test/test_matter_air_purifier_api9.lua | 49 +- .../test/test_matter_air_purifier_modular.lua | 10 +- .../src/test/test_matter_fan.lua | 10 +- .../src/test/test_matter_heat_pump.lua | 43 +- .../src/test/test_matter_room_ac.lua | 28 +- .../src/test/test_matter_room_ac_modular.lua | 10 +- .../src/test/test_matter_thermo_battery.lua | 15 +- .../test/test_matter_thermo_featuremap.lua | 20 +- ...st_matter_thermo_multiple_device_types.lua | 15 +- .../test_matter_thermo_setpoint_limits.lua | 44 +- ...test_matter_thermo_setpoint_limits_rpc.lua | 12 +- .../src/test/test_matter_thermostat.lua | 81 +- ...est_matter_thermostat_composed_bridged.lua | 66 +- .../test/test_matter_thermostat_modular.lua | 5 +- .../src/test/test_matter_thermostat_rpc5.lua | 3 + .../src/test/test_matter_water_heater.lua | 21 +- .../src/test/test_matter_window_covering.lua | 172 +- .../src/test/test_virtual_switch.lua | 26 +- .../test_MultiIR_air_quality_detector.lua | 106 +- .../src/test/test_shus_mattress.lua | 2068 +++++++++-------- .../src/test/test_SLED_button.lua | 15 +- .../src/test/test_aduro_button.lua | 15 +- .../src/test/test_aqara_button.lua | 49 +- .../src/test/test_centralite_button.lua | 25 +- .../src/test/test_dimming_remote.lua | 30 +- .../src/test/test_ewelink_button.lua | 25 +- .../src/test/test_ezviz_button.lua | 22 +- .../src/test/test_frient_button.lua | 49 +- .../src/test/test_heiman_button.lua | 40 +- .../src/test/test_ikea_on_off.lua | 25 +- .../src/test/test_ikea_open_close.lua | 25 +- .../src/test/test_ikea_remote_control.lua | 23 +- .../src/test/test_iris_button.lua | 45 +- .../test/test_linxura_aura_smart_button.lua | 20 +- ...est_linxura_smart_controller_4x_button.lua | 20 +- .../src/test/test_push_only_button.lua | 22 +- .../src/test/test_robb_4x_button.lua | 29 +- .../src/test/test_robb_8x_button.lua | 29 +- .../src/test/test_samjin_button.lua | 10 +- .../src/test/test_shinasystem_button.lua | 25 +- .../src/test/test_somfy_situo_1_button.lua | 25 +- .../src/test/test_somfy_situo_4_button.lua | 20 +- .../src/test/test_thirdreality_button.lua | 20 +- .../src/test/test_vimar_button.lua | 30 +- .../src/test/test_wallhero_button.lua | 10 +- .../src/test/test_zigbee_button.lua | 40 +- .../src/test/test_zigbee_ecosmart_button.lua | 33 +- .../src/test/test_zunzunbee_8_button.lua | 20 +- ...test_climax_technology_carbon_monoxide.lua | 3 +- .../src/test/test_zigbee_carbon_monoxide.lua | 20 +- .../src/test/test_aqara_contact_sensor.lua | 45 +- .../src/test/test_aurora_contact_sensor.lua | 10 +- .../src/test/test_centralite_multi_sensor.lua | 40 +- .../test/test_contact_temperature_sensor.lua | 16 +- .../src/test/test_ecolink_contact.lua | 16 +- .../src/test/test_ewelink_heiman_sensor.lua | 10 +- .../src/test/test_frient_contact_sensor.lua | 31 +- .../test/test_frient_contact_sensor_2_pro.lua | 39 +- .../test/test_frient_contact_sensor_pro.lua | 39 +- .../src/test/test_frient_vibration_sensor.lua | 43 +- .../src/test/test_orvibo_contact_sensor.lua | 10 +- .../src/test/test_samjin_multi_sensor.lua | 20 +- .../src/test/test_sengled_contact_sensor.lua | 10 +- .../src/test/test_smartsense_multi.lua | 122 +- .../test/test_smartthings_multi_sensor.lua | 57 +- .../src/test/test_third_reality_contact.lua | 10 +- .../test/test_thirdreality_multi_sensor.lua | 18 +- .../src/test/test_zigbee_contact.lua | 31 +- .../src/test/test_zigbee_contact_battery.lua | 11 +- .../src/test/test_zigbee_contact_tyco.lua | 15 +- .../src/test/test_zigbee_accessory_dimmer.lua | 77 +- .../test_zigbee_battery_accessory_dimmer.lua | Bin 28081 -> 29143 bytes .../zigbee-fan/src/test/test_fan_light.lua | 63 + .../src/test/test_aqara_sensor.lua | 36 +- .../src/test/test_centralite_sensor.lua | 11 +- .../src/test/test_ewelink_sensor.lua | 8 +- .../test/test_frient_air_quality_sensor.lua | 33 +- .../src/test/test_frient_sensor.lua | 25 +- .../src/test/test_heiman_sensor.lua | 8 +- .../src/test/test_humidity_battery_sensor.lua | 14 +- .../src/test/test_humidity_plaid_systems.lua | 33 +- .../src/test/test_humidity_temperature.lua | 19 +- .../test_humidity_temperature_battery.lua | 19 +- .../test/test_humidity_temperature_sensor.lua | 14 +- .../src/test/test_illuminance_sensor.lua | 11 +- .../test/test_illuminance_sensor_aqara.lua | 26 +- .../zigbee-lock/src/test/test_c2o_lock.lua | 40 +- .../src/test/test_generic_lock_migration.lua | 5 +- ..._yale_fingerprint_bad_battery_reporter.lua | 3 + .../zigbee-lock/src/test/test_zigbee_lock.lua | 112 +- .../test/test_zigbee_lock_code_migration.lua | 25 +- .../src/test/test_zigbee_lock_v10.lua | 98 +- .../src/test/test_zigbee_samsungsds.lua | 184 +- .../test_zigbee_yale-bad-battery-reporter.lua | 3 + .../test_zigbee_yale-fingerprint-lock.lua | 3 + .../zigbee-lock/src/test/test_zigbee_yale.lua | 53 +- .../test_all_capabilities_zigbee_motion.lua | 42 +- .../src/test/test_aqara_high_precision.lua | 45 +- .../test/test_aqara_motion_illuminance.lua | 30 +- .../src/test/test_aurora_motion.lua | 20 +- .../src/test/test_battery_voltage_motion.lua | 6 +- .../src/test/test_centralite_motion.lua | 10 +- .../src/test/test_compacta_motion.lua | 10 +- .../src/test/test_frient_motion_sensor.lua | 24 +- .../test/test_frient_motion_sensor2_pet.lua | 29 +- .../test/test_frient_motion_sensor_pro.lua | 41 +- .../src/test/test_gator_motion.lua | 45 +- .../src/test/test_ikea_motion.lua | 33 +- .../src/test/test_samjin_sensor.lua | 10 +- .../src/test/test_sengled_motion.lua | 10 +- .../test/test_smartsense_motion_sensor.lua | 40 +- .../src/test/test_smartthings_motion.lua | 5 +- .../src/test/test_thirdreality_sensor.lua | 23 +- .../src/test/test_zigbee_motion_iris.lua | 8 +- .../src/test/test_zigbee_motion_nyce.lua | 12 +- .../src/test/test_zigbee_motion_orvibo.lua | 22 +- .../test/test_zigbee_plugin_motion_sensor.lua | 14 +- .../src/test/test_zigbee_power_meter.lua | 23 +- .../src/test/test_zigbee_power_meter_1p.lua | 46 +- .../src/test/test_zigbee_power_meter_2p.lua | 34 +- .../src/test/test_zigbee_power_meter_3p.lua | 43 +- ...e_power_meter_consumption_report_sihas.lua | 26 +- .../src/test/test_zigbee_power_meter_ezex.lua | 14 +- .../test/test_zigbee_power_meter_frient.lua | 10 +- .../test/test_aqara_presence_sensor_fp1.lua | 53 +- .../src/test/test_st_arrival_sensor_v1.lua | 35 +- .../src/test/test_zigbee_presence_sensor.lua | 54 +- .../test_frient_zigbee_range_extender.lua | 31 +- .../src/test/test_zigbee_extend.lua | 5 +- .../src/test/test_zigbee_sensor.lua | 126 +- .../src/test/test_frient_siren.lua | 108 +- .../src/test/test_frient_siren_tamper.lua | 97 +- .../zigbee-siren/src/test/test_ozom_siren.lua | 11 +- .../src/test/test_zigbee_siren.lua | 54 +- .../src/test/test_aqara_gas_detector.lua | 85 +- .../src/test/test_aqara_smoke_detector.lua | 53 +- .../src/test/test_frient_heat_detector.lua | 72 +- .../src/test/test_frient_smoke_detector.lua | 78 +- .../src/test/test_zigbee_smoke_detector.lua | 23 +- .../src/test/test_zigbee_sound_sensor.lua | 26 +- .../test/test_all_capability_zigbee_bulb.lua | 56 +- .../src/test/test_aqara_led_bulb.lua | 20 +- .../src/test/test_aqara_light.lua | 38 +- .../src/test/test_aqara_smart_plug.lua | 70 +- .../src/test/test_aqara_smart_plug_t1.lua | 75 +- .../src/test/test_aqara_switch_module.lua | 45 +- .../test_aqara_switch_module_no_power.lua | 40 +- .../src/test/test_aqara_switch_no_power.lua | 80 +- .../src/test/test_aqara_switch_power.lua | 85 +- .../src/test/test_aqara_wall_switch.lua | 55 +- .../src/test/test_aurora_relay.lua | 16 +- .../src/test/test_bad_data_type.lua | 3 + .../src/test/test_bad_device_kind.lua | 7 +- .../zigbee-switch/src/test/test_cree_bulb.lua | 20 +- .../test/test_duragreen_color_temp_bulb.lua | 18 +- .../test/test_enbrighten_metering_dimmer.lua | 23 +- .../src/test/test_frient_IO_module.lua | 25 +- .../src/test/test_frient_switch.lua | 43 +- .../src/test/test_ge_link_bulb.lua | 35 +- .../src/test/test_hanssem_switch.lua | 80 +- .../src/test/test_inovelli_vzm30_sn.lua | 50 +- .../src/test/test_inovelli_vzm30_sn_child.lua | 28 +- .../test_inovelli_vzm30_sn_preferences.lua | 40 +- .../src/test/test_inovelli_vzm31_sn.lua | 41 +- .../src/test/test_inovelli_vzm31_sn_child.lua | 28 +- .../test_inovelli_vzm31_sn_preferences.lua | 40 +- .../src/test/test_inovelli_vzm32_sn.lua | 56 +- .../src/test/test_inovelli_vzm32_sn_child.lua | 28 +- .../test_inovelli_vzm32_sn_preferences.lua | 33 +- .../src/test/test_jasco_switch.lua | 17 +- .../src/test/test_laisiao_bath_heather.lua | 128 +- .../src/test/test_multi_switch.lua | 15 + .../src/test/test_multi_switch_no_master.lua | 44 +- .../src/test/test_multi_switch_power.lua | 52 +- .../src/test/test_on_off_zigbee_bulb.lua | 17 +- .../src/test/test_osram_iqbr30_light.lua | 16 +- .../src/test/test_osram_light.lua | 13 +- .../zigbee-switch/src/test/test_rgb_bulb.lua | 23 +- .../zigbee-switch/src/test/test_rgbw_bulb.lua | 33 +- .../test/test_robb_smarrt_2-wire_dimmer.lua | 15 + .../src/test/test_robb_smarrt_knob_dimmer.lua | 12 + .../src/test/test_sengled_color_temp_bulb.lua | 13 +- ...sengled_dimmer_bulb_with_motion_sensor.lua | 36 +- .../src/test/test_sinope_dimmer.lua | 51 +- .../src/test/test_sinope_switch.lua | 18 +- .../src/test/test_switch_power.lua | 18 +- .../src/test/test_tuya_multi.lua | 167 +- .../src/test/test_tuya_multi_switch.lua | 15 + .../src/test/test_wallhero_switch.lua | 88 +- .../src/test/test_white_color_temp_bulb.lua | 13 +- .../src/test/test_yanmi_switch.lua | 36 + .../src/test/test_zigbee_ezex_switch.lua | 15 + ...metering_plug_power_consumption_report.lua | 11 +- .../test_zigbee_metering_plug_rexense.lua | 6 + .../src/test/test_zll_color_temp_bulb.lua | 28 +- .../src/test/test_zll_dimmer.lua | 23 +- .../src/test/test_zll_dimmer_bulb.lua | 33 +- .../src/test/test_zll_rgb_bulb.lua | 80 +- .../src/test/test_zll_rgbw_bulb.lua | 48 +- .../src/test/test_aqara_thermostat.lua | 65 +- .../src/test/test_centralite_thermostat.lua | 19 +- .../src/test/test_danfoss_thermostat.lua | 26 +- .../src/test/test_fidure_thermostat.lua | 8 +- .../src/test/test_leviton_rc.lua | 75 +- .../src/test/test_popp_thermostat.lua | 69 +- .../src/test/test_resideo_dt300st_m000.lua | 300 ++- .../test/test_sinope_th1300_thermostat.lua | 16 +- .../test/test_sinope_th1400_thermostat.lua | 16 +- .../src/test/test_sinope_thermostat.lua | 21 +- .../test_stelpro_ki_zigbee_thermostat.lua | 73 +- .../src/test/test_stelpro_thermostat.lua | 81 +- .../src/test/test_vimar_thermostat.lua | 81 +- .../src/test/test_zenwithin_thermostat.lua | 47 +- .../src/test/test_zigbee_thermostat.lua | 100 +- .../zigbee-valve/src/test/test_ezex_valve.lua | 41 +- .../src/test/test_sinope_valve.lua | 30 +- .../src/test/test_zigbee_valve.lua | 38 +- .../zigbee-vent/src/test/test_zigbee_vent.lua | 43 +- .../src/test/test_aqara_water_leak_sensor.lua | 14 +- .../test_centralite_water_leak_sensor.lua | 31 +- .../test/test_frient_water_leak_sensor.lua | 31 +- .../src/test/test_leaksmart_water.lua | 35 +- .../test/test_samjin_water_leak_sensor.lua | 25 +- .../test/test_sengled_water_leak_sensor.lua | 10 +- .../src/test/test_sinope_zigbee_water.lua | 29 +- .../test_smartthings_water_leak_sensor.lua | 24 +- .../test_thirdreality_water_leak_sensor.lua | 30 +- .../src/test/test_zigbee_water.lua | 29 +- .../src/test/test_zigbee_water_freeze.lua | 17 +- .../test/test_thirdreality_watering_kit.lua | 65 +- .../test_zigbee_window_shade_battery_ikea.lua | 50 +- ...est_zigbee_window_shade_battery_yoolax.lua | 55 +- ...est_zigbee_window_shade_only_HOPOsmart.lua | 39 +- .../src/test/test_zigbee_window_treatment.lua | 35 +- ..._zigbee_window_treatment_VWSDSTUST120H.lua | 79 +- .../test_zigbee_window_treatment_aqara.lua | 100 +- ...ndow_treatment_aqara_curtain_driver_e1.lua | 73 +- ...ow_treatment_aqara_roller_shade_rotate.lua | 80 +- .../test_zigbee_window_treatment_axis.lua | 85 +- .../test_zigbee_window_treatment_feibit.lua | 60 +- .../test_zigbee_window_treatment_hanssem.lua | 35 +- .../test_zigbee_window_treatment_rooms.lua | 66 +- ...ee_window_treatment_screen_innovations.lua | 70 +- .../test_zigbee_window_treatment_somfy.lua | 81 +- .../test_zigbee_window_treatment_vimar.lua | 51 +- .../src/test/test_aeon_multiwhite_bulb.lua | 66 +- .../src/test/test_aeotec_led_bulb_6.lua | 12 +- .../src/test/test_fibaro_rgbw_controller.lua | 43 +- .../zwave-bulb/src/test/test_zwave_bulb.lua | 33 +- .../src/test/test_zwave_aeotec_minimote.lua | 34 +- .../test/test_zwave_aeotec_nanomote_one.lua | 5 +- .../src/test/test_zwave_button.lua | 22 +- .../src/test/test_zwave_fibaro_button.lua | 5 +- .../src/test/test_zwave_multi_button.lua | 84 +- .../src/test/test_aeon_meter.lua | 14 +- .../src/test/test_aeotec_gen5_meter.lua | 14 +- .../src/test/test_qubino_3_phase_meter.lua | 16 +- .../src/test/test_qubino_smart_meter.lua | 14 +- .../src/test/test_zwave_electric_meter.lua | 9 +- .../src/test/test_zwave_fan_3_speed.lua | 17 +- .../src/test/test_zwave_fan_4_speed.lua | 17 +- .../test_ecolink_garage_door_operator.lua | 46 +- .../src/test/test_mimolite_garage_door.lua | 42 +- .../test/test_zwave_garage_door_opener.lua | 21 +- .../zwave-lock/src/test/test_keywe_lock.lua | 13 +- .../zwave-lock/src/test/test_lock_battery.lua | 21 +- .../zwave-lock/src/test/test_samsung_lock.lua | 25 +- .../zwave-lock/src/test/test_schlage_lock.lua | 40 +- .../zwave-lock/src/test/test_zwave_lock.lua | 102 +- .../test/test_zwave_lock_code_migration.lua | 25 +- .../src/test/test_zwave_mouse_trap.lua | 38 +- .../src/test/test_aeon_multisensor.lua | 8 +- .../src/test/test_aeotec_multisensor_6.lua | 45 +- .../src/test/test_aeotec_multisensor_7.lua | 24 +- .../src/test/test_aeotec_multisensor_gen5.lua | 5 +- .../src/test/test_aeotec_water_sensor.lua | 38 +- .../src/test/test_aeotec_water_sensor_7.lua | 20 +- .../src/test/test_enerwave_motion_sensor.lua | 15 +- .../src/test/test_everpsring_sp817.lua | 10 +- .../src/test/test_everspring_PIR_sensor.lua | 22 +- .../src/test/test_everspring_ST814.lua | 5 +- .../test_everspring_illuminance_sensor.lua | 5 +- .../test_everspring_motion_light_sensor.lua | 3 +- .../test_ezmultipli_multipurpose_sensor.lua | 23 +- .../test/test_fibaro_door_window_sensor.lua | 23 +- .../test/test_fibaro_door_window_sensor_1.lua | 35 +- .../test/test_fibaro_door_window_sensor_2.lua | 34 +- ...ro_door_window_sensor_with_temperature.lua | 34 +- .../src/test/test_fibaro_flood_sensor.lua | 46 +- .../src/test/test_fibaro_flood_sensor_zw5.lua | 5 +- .../src/test/test_fibaro_motion_sensor.lua | 41 +- .../test/test_fibaro_motion_sensor_zw5.lua | 10 +- .../src/test/test_firmware_version.lua | 14 +- .../src/test/test_generic_sensor.lua | 182 +- .../test_glentronics_water_leak_sensor.lua | 21 + .../src/test/test_homeseer_multi_sensor.lua | 19 +- .../src/test/test_no_wakeup_poll.lua | 6 +- .../src/test/test_sensative_strip.lua | 15 +- .../test_smartthings_water_leak_sensor.lua | 38 +- .../src/test/test_v1_contact_event.lua | 11 +- .../src/test/test_vision_motion_detector.lua | 20 +- .../src/test/test_zooz_4_in_1_sensor.lua | 32 +- .../test/test_zwave_motion_light_sensor.lua | 35 +- .../test_zwave_motion_temp_light_sensor.lua | 29 +- .../src/test/test_zwave_sensor.lua | 81 +- .../src/test/test_zwave_water_sensor.lua | 38 +- .../zwave-siren/src/test/test_aeon_siren.lua | 46 +- .../src/test/test_aeotec_doorbell_siren.lua | 368 ++- .../src/test/test_ecolink_wireless_siren.lua | 63 +- .../src/test/test_fortrezz_siren.lua | 20 +- .../src/test/test_philio_sound_siren.lua | 76 +- .../src/test/test_utilitech_siren.lua | 8 +- .../zwave-siren/src/test/test_yale_siren.lua | 41 +- .../src/test/test_zipato_siren.lua | 21 +- .../test/test_zwave_multifunctional-siren.lua | 17 +- .../test/test_zwave_notification_siren.lua | 12 + .../zwave-siren/src/test/test_zwave_siren.lua | 54 +- .../src/test/test_zwave_sound_sensor.lua | 9 + .../src/test/test_fibaro_co_sensor_zw5.lua | 46 +- .../src/test/test_fibaro_smoke_sensor.lua | 15 +- .../src/test/test_zwave_alarm_v1.lua | 21 + .../src/test/test_zwave_co_detector.lua | 21 + .../src/test/test_zwave_smoke_detector.lua | 45 +- .../src/test/test_aeon_smart_strip.lua | 62 +- .../src/test/test_aeotec_dimmer_switch.lua | 41 +- ..._aeotec_dual_nano_switch_configuration.lua | 5 +- .../test/test_aeotec_heavy_duty_switch.lua | 84 +- ...t_aeotec_metering_switch_configuration.lua | 5 +- .../src/test/test_aeotec_nano_dimmer.lua | 41 +- .../test_aeotec_nano_dimmer_preferences.lua | 6 +- .../src/test/test_aeotec_smart_switch.lua | 10 +- .../test/test_aeotec_smart_switch_7_eu.lua | 27 +- .../test/test_aeotec_smart_switch_7_us.lua | 26 +- .../test/test_aeotec_smart_switch_gen5.lua | 5 +- .../src/test/test_dawon_smart_plug.lua | 6 + .../src/test/test_dawon_wall_smart_switch.lua | 32 +- .../src/test/test_eaton_5_scene_keypad.lua | 65 +- .../src/test/test_eaton_accessory_dimmer.lua | 31 +- .../src/test/test_eaton_anyplace_switch.lua | 22 +- .../src/test/test_eaton_rf_dimmer.lua | 5 +- .../src/test/test_ecolink_switch.lua | 27 +- .../src/test/test_fibaro_double_switch.lua | 42 +- .../src/test/test_fibaro_single_switch.lua | 69 +- .../src/test/test_fibaro_wall_plug_eu.lua | 5 +- ...test_fibaro_wall_plug_uk_configuration.lua | 5 +- .../src/test/test_fibaro_wall_plug_us.lua | 28 +- .../test_fibaro_walli_dimmer_preferences.lua | 42 +- .../test/test_fibaro_walli_double_switch.lua | 59 +- ...fibaro_walli_double_switch_preferences.lua | 30 +- .../src/test/test_generic_zwave_device1.lua | 30 +- ...go_control_plug_in_switch_configuraton.lua | 5 +- .../src/test/test_honeywell_dimmer.lua | 5 +- .../test_inovelli_2_channel_smart_plug.lua | 54 + .../src/test/test_inovelli_button.lua | 14 +- .../src/test/test_inovelli_dimmer.lua | 26 +- .../src/test/test_inovelli_dimmer_led.lua | 10 +- .../test_inovelli_dimmer_power_energy.lua | 37 +- .../test/test_inovelli_dimmer_preferences.lua | 30 +- .../src/test/test_inovelli_dimmer_scenes.lua | 33 + .../src/test/test_inovelli_vzw32_sn.lua | 29 +- .../src/test/test_inovelli_vzw32_sn_child.lua | 28 +- .../test_inovelli_vzw32_sn_preferences.lua | 33 +- .../src/test/test_multi_metering_switch.lua | 75 +- .../src/test/test_multichannel_device.lua | 185 +- .../test_popp_outdoor_plug_configuration.lua | 5 +- .../src/test/test_qubino_din_dimmer.lua | 46 +- .../test_qubino_din_dimmer_preferences.lua | 42 +- .../test_qubino_flush_1_relay_preferences.lua | 30 +- ...test_qubino_flush_1d_relay_preferences.lua | 18 +- .../src/test/test_qubino_flush_2_relay.lua | 67 +- .../test_qubino_flush_2_relay_preferences.lua | 30 +- .../src/test/test_qubino_flush_dimmer.lua | 46 +- ..._qubino_flush_dimmer_0_10V_preferences.lua | 42 +- .../test_qubino_flush_dimmer_preferences.lua | 54 +- .../test_qubino_mini_dimmer_preferences.lua | 48 +- ...t_qubino_temperature_sensor_with_power.lua | 35 +- ...ubino_temperature_sensor_without_power.lua | 24 +- .../test_shelly_multi_metering_switch.lua | 70 +- .../zwave-switch/src/test/test_wyfy_touch.lua | 43 +- .../test/test_wyfy_touch_configuration.lua | 5 +- .../src/test/test_zooz_double_plug.lua | 51 +- .../src/test/test_zooz_power_strip.lua | 101 +- .../test/test_zooz_zen_30_dimmer_relay.lua | 144 +- ...t_zooz_zen_30_dimmer_relay_preferences.lua | 6 +- .../test/test_zwave_dimmer_power_energy.lua | 33 +- .../src/test/test_zwave_dual_switch.lua | 66 +- .../test/test_zwave_dual_switch_migration.lua | 10 +- .../src/test/test_zwave_switch.lua | 38 +- .../src/test/test_zwave_switch_battery.lua | 9 +- .../test/test_zwave_switch_electric_meter.lua | 12 +- .../test/test_zwave_switch_energy_meter.lua | 17 +- .../src/test/test_zwave_switch_level.lua | 6 +- .../test/test_zwave_switch_power_meter.lua | 12 +- .../test/test_aeotec_radiator_thermostat.lua | 28 +- .../src/test/test_ct100_thermostat.lua | 44 +- .../src/test/test_fibaro_heat_controller.lua | 39 +- .../test/test_popp_radiator_thermostat.lua | 22 +- .../src/test/test_qubino_flush_thermostat.lua | 55 +- .../src/test/test_stelpro_ki_thermostat.lua | 34 +- .../test/test_thermostat_heating_battery.lua | 62 +- .../src/test/test_zwave_thermostat.lua | 84 +- .../src/test/test_inverse.valve.lua | 22 +- .../zwave-valve/src/test/test_zwave_valve.lua | 32 +- .../test_zwave_virtual_momentary_switch.lua | 27 +- .../src/test/test_fibaro_roller_shutter.lua | 151 +- .../src/test/test_qubino_flush_shutter.lua | 122 +- .../test/test_zwave_aeotec_nano_shutter.lua | 58 +- .../test_zwave_iblinds_window_treatment.lua | 75 +- .../test_zwave_springs_window_treatment.lua | 5 +- .../src/test/test_zwave_window_treatment.lua | 78 +- 480 files changed, 16578 insertions(+), 3556 deletions(-) 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 4a65f8e882..99d56a5869 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua @@ -89,7 +89,10 @@ test.register_coroutine_test( local component_to_endpoint_map = mock_device:get_field("__component_to_endpoint_map") assert(component_to_endpoint_map["cookSurfaceOne"] == COOK_SURFACE_ONE_ENDPOINT, "Cook Surface One Endpoint must be 2") assert(component_to_endpoint_map["cookSurfaceTwo"] == COOK_SURFACE_TWO_ENDPOINT, "Cook Surface Two Endpoint must be 3") - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -112,6 +115,9 @@ test.register_message_test( clusters.OnOff.server.commands.Off(mock_device, COOK_TOP_ENDPOINT) } } + }, + { + min_api_version = 19 } ) @@ -149,6 +155,9 @@ test.register_message_test( clusters.TemperatureControl.server.commands.SetTemperature(mock_device, COOK_SURFACE_TWO_ENDPOINT, nil, 0) --0 is the index where Level1 is stored. } }, + }, + { + min_api_version = 19 } ) @@ -181,6 +190,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("cookSurfaceTwo", capabilities.temperatureMeasurement.temperature({ value = 20.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua b/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua index f446d1149b..0cfeb19f07 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua @@ -101,6 +101,9 @@ test.register_message_test( clusters.OnOff.server.commands.Off(mock_device, APPLICATION_ENDPOINT) } } + }, + { + min_api_version = 19 } ) @@ -166,6 +169,9 @@ test.register_message_test( })) } }, -- on receiving NO ERROR we don't do anything. + }, + { + min_api_version = 19 } ) @@ -231,6 +237,9 @@ test.register_message_test( })) } }, -- on receiving NO ERROR we don't do anything. + }, + { + min_api_version = 19 } ) @@ -296,6 +305,9 @@ test.register_message_test( })) } }, -- on receiving NO ERROR we don't do anything. + }, + { + min_api_version = 19 } ) @@ -372,6 +384,9 @@ test.register_message_test( })) } }, -- on receiving NO ERROR we don't do anything. + }, + { + min_api_version = 19 } ) @@ -433,6 +448,9 @@ test.register_message_test( clusters.DishwasherMode.server.commands.ChangeToMode(mock_device, APPLICATION_ENDPOINT, 1) --1 is the index where Super Dry is stored. } } + }, + { + min_api_version = 19 } ) @@ -470,6 +488,9 @@ test.register_message_test( clusters.TemperatureControl.server.commands.SetTemperature(mock_device, APPLICATION_ENDPOINT, nil, 0) --0 is the index where Level1 is stored. } }, + }, + { + min_api_version = 19 } ) @@ -526,6 +547,9 @@ test.register_message_test( clusters.TemperatureControl.commands.SetTemperature(mock_device, APPLICATION_ENDPOINT, 40 * 100, nil) } }, + }, + { + min_api_version = 19 } ) 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 b8d18a3ab4..ca85e49214 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_extractor_hood.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_extractor_hood.lua @@ -174,6 +174,9 @@ test.register_message_test( clusters.FanControl.attributes.PercentSetting:write(mock_device, 1, 50) } } + }, + { + min_api_version = 19 } ) @@ -245,6 +248,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.fanMode.fanMode("auto")) } + }, + { + min_api_version = 19 } ) @@ -331,6 +337,9 @@ test.register_message_test( clusters.FanControl.attributes.FanMode:write(mock_device, 1, clusters.FanControl.types.FanModeEnum.AUTO) } } + }, + { + min_api_version = 19 } ) test.register_message_test( @@ -441,6 +450,9 @@ test.register_message_test( capabilities.fanMode.fanMode.high.NAME }, {visibility={displayed=false}})) } + }, + { + min_api_version = 19 } ) @@ -476,6 +488,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.windMode.windMode.naturalWind()) } + }, + { + min_api_version = 19 } ) @@ -514,6 +529,9 @@ test.register_message_test( clusters.FanControl.attributes.WindSetting:write(mock_device, 1, clusters.FanControl.types.WindSettingMask.NATURAL_WIND) } } + }, + { + min_api_version = 19 } ) @@ -572,6 +590,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("hepaFilter", capabilities.filterStatus.filterStatus.replace()) }, + }, + { + min_api_version = 19 } ) @@ -630,6 +651,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("activatedCarbonFilter", capabilities.filterStatus.filterStatus.replace()) }, + }, + { + min_api_version = 19 } ) @@ -640,7 +664,10 @@ test.register_coroutine_test( mock_device_onoff:expect_metadata_update({ profile = "extractor-hood-wind-light" }) mock_device_onoff:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, - { test_init = test_init_onoff } + { + test_init = test_init_onoff, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -663,7 +690,10 @@ test.register_coroutine_test( clusters.OnOff.server.commands.Off(mock_device_onoff, 2) }) end, - { test_init = test_init_onoff } + { + test_init = test_init_onoff, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -684,7 +714,10 @@ test.register_coroutine_test( mock_device_onoff:generate_test_message("light", capabilities.switch.switch.off()) ) end, - { test_init = test_init_onoff } + { + test_init = test_init_onoff, + min_api_version = 19 + } ) test.run_registered_tests() 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 9049b5f64e..5c01970d7b 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua @@ -100,6 +100,9 @@ test.register_message_test( clusters.OnOff.server.commands.Off(mock_device, APPLICATION_ENDPOINT) } } + }, + { + min_api_version = 19 } ) @@ -165,6 +168,9 @@ test.register_message_test( })) } }, -- on receiving NO ERROR we don't do anything. + }, + { + min_api_version = 19 } ) @@ -230,6 +236,9 @@ test.register_message_test( })) } }, -- on receiving NO ERROR we don't do anything. + }, + { + min_api_version = 19 } ) @@ -295,6 +304,9 @@ test.register_message_test( })) } }, -- on receiving NO ERROR we don't do anything. + }, + { + min_api_version = 19 } ) @@ -371,6 +383,9 @@ test.register_message_test( })) } }, -- on receiving NO ERROR we don't do anything. + }, + { + min_api_version = 19 } ) @@ -432,6 +447,9 @@ test.register_message_test( clusters.LaundryWasherMode.server.commands.ChangeToMode(mock_device, APPLICATION_ENDPOINT, 1) --1 is the index where Super Dry is stored. } } + }, + { + min_api_version = 19 } ) @@ -469,6 +487,9 @@ test.register_message_test( clusters.TemperatureControl.server.commands.SetTemperature(mock_device, APPLICATION_ENDPOINT, nil, 0) --0 is the index where Level1 is stored. } }, + }, + { + min_api_version = 19 } ) @@ -525,6 +546,9 @@ test.register_message_test( clusters.TemperatureControl.commands.SetTemperature(mock_device, APPLICATION_ENDPOINT, 40 * 100, nil) } }, + }, + { + min_api_version = 19 } ) @@ -565,6 +589,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureSetpoint.temperatureSetpoint({value = 50.0, unit = "C"})) } + }, + { + min_api_version = 19 } ) 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 6f7ef3ee5c..a0df2e229b 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_laundry_washer.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_laundry_washer.lua @@ -133,6 +133,9 @@ test.register_message_test( clusters.TemperatureControl.commands.SetTemperature(mock_device_washer, APPLICATION_ENDPOINT, 25 * 100, nil) } }, + }, + { + min_api_version = 19 } ) @@ -173,6 +176,9 @@ test.register_message_test( direction = "send", message = mock_device_washer:generate_test_message("main", capabilities.temperatureSetpoint.temperatureSetpoint({value = 30.0, unit = "C"})) } + }, + { + min_api_version = 19 } ) 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 d8faa1e5ee..4df43c94fc 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 @@ -456,7 +456,10 @@ test.register_coroutine_test( { mock_device_dishwasher.id, clusters.TemperatureControl.commands.SetTemperature(mock_device_dishwasher, dishwasher_ep, 40 * 100, nil) } ) end, - { test_init = test_init_dishwasher } + { + test_init = test_init_dishwasher, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -495,7 +498,10 @@ test.register_coroutine_test( { mock_device_dishwasher.id, clusters.TemperatureControl.commands.SetTemperature(mock_device_dishwasher, dishwasher_ep, 50 * 100, nil) } ) end, - { test_init = test_init_dishwasher } + { + test_init = test_init_dishwasher, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -534,7 +540,10 @@ test.register_coroutine_test( { mock_device_washer.id, clusters.TemperatureControl.commands.SetTemperature(mock_device_washer, washer_ep, 28 * 100, nil) } ) end, - { test_init = test_init_washer } + { + test_init = test_init_washer, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -573,7 +582,10 @@ test.register_coroutine_test( { mock_device_washer.id, clusters.TemperatureControl.commands.SetTemperature(mock_device_washer, washer_ep, 50 * 100, nil) } ) end, - { test_init = test_init_washer } + { + test_init = test_init_washer, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -612,7 +624,10 @@ test.register_coroutine_test( { mock_device_dryer.id, clusters.TemperatureControl.commands.SetTemperature(mock_device_dryer, dryer_ep, 40 * 100, nil) } ) end, - { test_init = test_init_dryer } + { + test_init = test_init_dryer, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -651,7 +666,10 @@ test.register_coroutine_test( { mock_device_dryer.id, clusters.TemperatureControl.commands.SetTemperature(mock_device_dryer, dryer_ep, 40 * 100, nil) } ) end, - { test_init = test_init_dryer } + { + test_init = test_init_dryer, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -690,7 +708,10 @@ test.register_coroutine_test( { mock_device_oven.id, clusters.TemperatureControl.commands.SetTemperature(mock_device_oven, oven_tcc_one_ep, 140 * 100, nil) } ) end, - { test_init = test_init_oven } + { + test_init = test_init_oven, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -729,7 +750,10 @@ test.register_coroutine_test( { mock_device_oven.id, clusters.TemperatureControl.commands.SetTemperature(mock_device_oven, oven_tcc_one_ep, 140 * 100, nil) } ) end, - { test_init = test_init_oven } + { + test_init = test_init_oven, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -768,7 +792,10 @@ test.register_coroutine_test( { mock_device_refrigerator.id, clusters.TemperatureControl.commands.SetTemperature(mock_device_refrigerator, refrigerator_ep, 4 * 100, nil) } ) end, - { test_init = test_init_refrigerator } + { + test_init = test_init_refrigerator, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -807,7 +834,10 @@ test.register_coroutine_test( { mock_device_refrigerator.id, clusters.TemperatureControl.commands.SetTemperature(mock_device_refrigerator, refrigerator_ep, 10 * 100, nil) } ) end, - { test_init = test_init_refrigerator } + { + test_init = test_init_refrigerator, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -846,7 +876,10 @@ test.register_coroutine_test( { mock_device_refrigerator.id, clusters.TemperatureControl.commands.SetTemperature(mock_device_refrigerator, freezer_ep, -15 * 100, nil) } ) end, - { test_init = test_init_refrigerator } + { + test_init = test_init_refrigerator, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -885,7 +918,10 @@ test.register_coroutine_test( { mock_device_refrigerator.id, clusters.TemperatureControl.commands.SetTemperature(mock_device_refrigerator, freezer_ep, -20 * 100, nil) } ) end, - { test_init = test_init_refrigerator } + { + test_init = test_init_refrigerator, + min_api_version = 19 + } ) test.run_registered_tests() 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 624f1751f6..14402c5157 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua @@ -172,6 +172,9 @@ test.register_message_test( })) } }, -- on receiving NO ERROR we don't do anything. + }, + { + min_api_version = 19 } ) @@ -254,7 +257,8 @@ test.register_message_test( test_init = function() test_init() init_supported_microwave_oven_modes() - end + end, + min_api_version = 19 } ) @@ -332,6 +336,9 @@ test.register_message_test( })) } }, -- on receiving NO ERROR we don't do anything. + }, + { + min_api_version = 19 } ) @@ -444,6 +451,9 @@ test.register_message_test( })) } }, -- on receiving NO ERROR we don't do anything. + }, + { + min_api_version = 19 } ) @@ -467,6 +477,9 @@ test.register_message_test( maximum = 900 },{visibility={displayed=false}})) }, + }, + { + min_api_version = 19 } ) @@ -557,6 +570,9 @@ test.register_message_test( 300) } }, + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_oven.lua b/drivers/SmartThings/matter-appliance/src/test/test_oven.lua index 76a23a0f44..8c146f2bd5 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_oven.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_oven.lua @@ -141,7 +141,10 @@ test.register_coroutine_test( "Cook Surface One Endpoint must be 5") assert(component_to_endpoint_map["cookSurfaceTwo"] == COOK_SURFACE_TWO_ENDPOINT, "Cook Surface Two Endpoint must be 6") - end + end, + { + min_api_version = 19 + } ) @@ -201,6 +204,9 @@ test.register_message_test( clusters.OvenMode.commands.ChangeToMode(mock_device, OVEN_TCC_ONE_ENDPOINT, 0) --Index where Grill is stored) } } + }, + { + min_api_version = 19 } ) @@ -220,6 +226,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("tccOne", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -276,6 +285,9 @@ test.register_message_test( clusters.TemperatureControl.commands.SetTemperature(mock_device, OVEN_TCC_ONE_ENDPOINT, 130 * 100, nil) } }, + }, + { + min_api_version = 19 } ) @@ -335,6 +347,9 @@ test.register_message_test( clusters.OvenMode.commands.ChangeToMode(mock_device, OVEN_TCC_TWO_ENDPOINT, 1) --Index where Pre Heat is stored } } + }, + { + min_api_version = 19 } ) @@ -354,6 +369,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("tccTwo", capabilities.temperatureMeasurement.temperature({ value = 50.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -391,6 +409,9 @@ test.register_message_test( clusters.TemperatureControl.server.commands.SetTemperature(mock_device, OVEN_TCC_TWO_ENDPOINT, nil, 0) --0 is the index where Level1 is stored. } }, + }, + { + min_api_version = 19 } ) @@ -414,6 +435,9 @@ test.register_message_test( clusters.OnOff.server.commands.Off(mock_device, COOK_TOP_ENDPOINT) } } + }, + { + min_api_version = 19 } ) @@ -449,6 +473,9 @@ test.register_message_test( clusters.TemperatureControl.server.commands.SetTemperature(mock_device, COOK_SURFACE_ONE_ENDPOINT, nil, 2) -- 2 is the index where Level 5 is stored. } }, + }, + { + min_api_version = 19 } ) @@ -484,6 +511,9 @@ test.register_message_test( clusters.TemperatureControl.server.commands.SetTemperature(mock_device, COOK_SURFACE_TWO_ENDPOINT, nil, 1) -- 1 is the index where Level 4 is stored. } }, + }, + { + min_api_version = 19 } ) @@ -503,6 +533,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("cookSurfaceOne", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -522,6 +555,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("cookSurfaceTwo", capabilities.temperatureMeasurement.temperature({ value = 20.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua b/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua index 0ac94856e3..3158f69298 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua @@ -145,6 +145,9 @@ test.register_message_test( clusters.RefrigeratorAndTemperatureControlledCabinetMode.server.commands.ChangeToMode(mock_device, refrigerator_ep, 1) --1 is the index where Super Dry is stored. } } + }, + { + min_api_version = 19 } ) @@ -201,6 +204,9 @@ test.register_message_test( clusters.TemperatureControl.commands.SetTemperature(mock_device, refrigerator_ep, 4 * 100, nil) } }, + }, + { + min_api_version = 19 } ) @@ -241,6 +247,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("refrigerator", capabilities.temperatureSetpoint.temperatureSetpoint({value = 7.0, unit = "C"})) } + }, + { + min_api_version = 19 } ) @@ -297,6 +306,9 @@ test.register_message_test( clusters.TemperatureControl.commands.SetTemperature(mock_device, freezer_ep, -15 * 100, nil) } }, + }, + { + min_api_version = 19 } ) @@ -337,6 +349,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("freezer", capabilities.temperatureSetpoint.temperatureSetpoint({value = -15.0, unit = "C"})) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/matter-button/src/test/skip_test_latching_switch.lua b/drivers/SmartThings/matter-button/src/test/skip_test_latching_switch.lua index 58475ae0cc..084dc9048a 100644 --- a/drivers/SmartThings/matter-button/src/test/skip_test_latching_switch.lua +++ b/drivers/SmartThings/matter-button/src/test/skip_test_latching_switch.lua @@ -1,3 +1,6 @@ +-- Copyright 2026 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" @@ -81,7 +84,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -116,7 +122,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -135,7 +144,10 @@ test.register_coroutine_test( ) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) -- run the tests diff --git a/drivers/SmartThings/matter-button/src/test/test_matter_button.lua b/drivers/SmartThings/matter-button/src/test/test_matter_button.lua index c18daeab98..0c703c642d 100644 --- a/drivers/SmartThings/matter-button/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-button/src/test/test_matter_button.lua @@ -1,3 +1,6 @@ +-- Copyright 2026 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" @@ -66,6 +69,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) --should send initial press } +}, +{ + min_api_version = 19 } ) @@ -101,6 +107,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.held({state_change = true})) } +}, +{ + min_api_version = 19 } ) @@ -137,6 +146,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -182,6 +194,9 @@ test.register_message_test( ) } }, + }, + { + min_api_version = 19 } ) @@ -203,6 +218,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -224,6 +242,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double", "pushed_3x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -245,6 +266,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double", "pushed_3x", "pushed_4x", "pushed_5x", "pushed_6x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -282,6 +306,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", button_attr.double({state_change = true})) }, +}, +{ + min_api_version = 19 } ) @@ -319,6 +346,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", button_attr.pushed_4x({state_change = true})) }, +}, +{ + min_api_version = 19 } ) @@ -341,6 +371,9 @@ test.register_message_test( "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) ), }, + }, + { + min_api_version = 19 } ) -- run the tests diff --git a/drivers/SmartThings/matter-button/src/test/test_matter_button_parent_child.lua b/drivers/SmartThings/matter-button/src/test/test_matter_button_parent_child.lua index a9d2629803..64396e46f4 100644 --- a/drivers/SmartThings/matter-button/src/test/test_matter_button_parent_child.lua +++ b/drivers/SmartThings/matter-button/src/test/test_matter_button_parent_child.lua @@ -1,3 +1,6 @@ +-- Copyright 2026 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" @@ -131,6 +134,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) --should send initial press } +}, +{ + min_api_version = 19 } ) @@ -162,6 +168,9 @@ test.register_message_test( direction = "send", message = mock_children[3]:generate_test_message("main", button_attr.pushed({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -202,6 +211,9 @@ test.register_message_test( ) } }, + }, + { + min_api_version = 19 } ) @@ -223,6 +235,9 @@ test.register_message_test( message = mock_children[4]:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -244,6 +259,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double", "pushed_3x", "pushed_4x", "pushed_5x", "pushed_6x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -275,6 +293,9 @@ test.register_message_test( message = mock_children[4]:generate_test_message("main", button_attr.double({state_change = true})) }, +}, +{ + min_api_version = 19 } ) @@ -306,6 +327,9 @@ test.register_message_test( message = mock_children[4]:generate_test_message("main", button_attr.pushed_4x({state_change = true})) }, +}, +{ + min_api_version = 19 } ) @@ -328,6 +352,9 @@ test.register_message_test( "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) ), }, + }, + { + min_api_version = 19 } ) -- run the tests diff --git a/drivers/SmartThings/matter-button/src/test/test_matter_multi_button.lua b/drivers/SmartThings/matter-button/src/test/test_matter_multi_button.lua index 2ecf083ba7..2549698c61 100644 --- a/drivers/SmartThings/matter-button/src/test/test_matter_multi_button.lua +++ b/drivers/SmartThings/matter-button/src/test/test_matter_multi_button.lua @@ -1,3 +1,6 @@ +-- Copyright 2026 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" @@ -131,6 +134,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) --should send initial press } +}, +{ + min_api_version = 19 } ) @@ -161,6 +167,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("button2", button_attr.pushed({state_change = true})) --should send initial press } +}, +{ + min_api_version = 19 } ) @@ -183,7 +192,10 @@ test.register_coroutine_test( ) }) test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.held({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -205,7 +217,10 @@ test.register_coroutine_test( ) }) test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -226,7 +241,10 @@ test.register_coroutine_test( mock_device, 50, {previous_position = 0} ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -263,7 +281,10 @@ test.register_coroutine_test( ) }) test.socket.capability:__expect_send(mock_device:generate_test_message("button4", button_attr.double({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -288,7 +309,10 @@ test.register_coroutine_test( mock_device, 30, {previous_position = 0} ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -313,7 +337,10 @@ test.register_coroutine_test( mock_device, 50, {previous_position = 0} ) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -348,6 +375,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.held({state_change = true})) } +}, +{ + min_api_version = 19 } ) @@ -384,6 +414,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -429,6 +462,9 @@ test.register_message_test( ) } }, + }, + { + min_api_version = 19 } ) @@ -450,6 +486,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -471,6 +510,9 @@ test.register_message_test( message = mock_device:generate_test_message("button4", capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -492,6 +534,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double", "pushed_3x", "pushed_4x", "pushed_5x", "pushed_6x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -529,6 +574,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", button_attr.double({state_change = true})) }, +}, +{ + min_api_version = 19 } ) @@ -566,6 +614,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", button_attr.pushed_4x({state_change = true})) }, +}, +{ + min_api_version = 19 } ) @@ -588,6 +639,9 @@ test.register_message_test( "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) ), }, + }, + { + min_api_version = 19 } ) @@ -630,6 +684,9 @@ test.register_message_test( } } -- no double event +}, +{ + min_api_version = 19 } ) @@ -685,6 +742,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("button4", button_attr.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) -- run the tests 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 5b5734acf5..a9c652f661 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua @@ -102,7 +102,10 @@ test.register_coroutine_test( "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Battery charge state must reported properly", @@ -157,7 +160,10 @@ test.register_coroutine_test( mock_device:generate_test_message( "main", capabilities.chargingState.chargingState.error()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -171,7 +177,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 30.0, unit = "W" }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -185,7 +194,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 30.0, unit = "W" }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -288,7 +300,8 @@ test.register_coroutine_test( { test_init = function() test_init() - end + end, + min_api_version = 19 } ) diff --git a/drivers/SmartThings/matter-energy/src/test/test_evse.lua b/drivers/SmartThings/matter-energy/src/test/test_evse.lua index 6add71bde9..0aeed44209 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_evse.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_evse.lua @@ -118,7 +118,10 @@ test.register_coroutine_test( assert(component_to_endpoint_map["electricalSensor"] == ELECTRICAL_SENSOR_EP, "Electrical Sensor Endpoint must be 2") assert(component_to_endpoint_map["deviceEnergyManagement"] == DEVICE_ENERGY_MANAGEMENT_DEVICE_EP, "Device Energy Management Endpoint must be 3") - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -145,6 +148,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.evseChargingSession.chargingState.charging({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -193,6 +199,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.evseChargingSession.chargingState.charging({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -214,6 +223,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.evseState.faultState.groundFault()) } + }, + { + min_api_version = 19 } ) @@ -234,6 +246,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.evseChargingSession.targetEndTime("2024-08-23T07:47:22Z")) } + }, + { + min_api_version = 19 } ) @@ -254,6 +269,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.evseChargingSession.minCurrent(0)) } + }, + { + min_api_version = 19 } ) @@ -274,6 +292,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.evseChargingSession.maxCurrent(10000)) } + }, + { + min_api_version = 19 } ) @@ -294,6 +315,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.evseChargingSession.sessionTime(9000)) } + }, + { + min_api_version = 19 } ) @@ -314,6 +338,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.evseChargingSession.energyDelivered(900000)) } + }, + { + min_api_version = 19 } ) @@ -335,6 +362,9 @@ test.register_message_test( message = mock_device:generate_test_message("electricalSensor", capabilities.powerSource.powerSource.mains()) } + }, + { + min_api_version = 19 } ) @@ -406,6 +436,9 @@ test.register_message_test( clusters.EnergyEvseMode.commands.ChangeToMode(mock_device, EVSE_EP, 0) --Index is Auto-Scheduled } } + }, + { + min_api_version = 19 } ) @@ -506,6 +539,9 @@ test.register_message_test( clusters.DeviceEnergyManagementMode.commands.ChangeToMode(mock_device, DEVICE_ENERGY_MANAGEMENT_DEVICE_EP, 0) --Index is Grid Energy Management } } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua b/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua index 610c443625..ba5d6d6101 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua @@ -116,7 +116,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) mock_device:expect_metadata_update({ profile = "evse-energy-meas" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -148,7 +151,8 @@ test.register_coroutine_test( { test_init = function() test_init() - end + end, + min_api_version = 19 } ) 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 c7446af44b..0e248624d9 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua @@ -122,7 +122,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 35.0, unit = "W" }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -168,7 +171,8 @@ test.register_coroutine_test( { test_init = function() test_init() - end + end, + min_api_version = 19 } ) @@ -191,7 +195,10 @@ test.register_coroutine_test( .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, apparent_energy = 0, reactive_energy = 0 })) }) --100Wh - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-hrap/src/test/test_thread_border_router_network.lua b/drivers/SmartThings/matter-hrap/src/test/test_thread_border_router_network.lua index 0467e19de3..f4d2450c58 100644 --- a/drivers/SmartThings/matter-hrap/src/test/test_thread_border_router_network.lua +++ b/drivers/SmartThings/matter-hrap/src/test/test_thread_border_router_network.lua @@ -106,7 +106,10 @@ test.register_coroutine_test( mock_device, 1, 6 ) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -138,6 +141,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.threadBorderRouter.threadInterfaceState("disabled")) } + }, + { + min_api_version = 19 } ) @@ -183,6 +189,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.threadBorderRouter.borderRouterName({ value = "john foo no suffix"})) }, + }, + { + min_api_version = 19 } ) @@ -202,6 +211,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.wifiInformation.ssid({ value = "test name for ssid!" })) } + }, + { + min_api_version = 19 } ) @@ -216,6 +228,9 @@ test.register_message_test( clusters.WifiNetworkMangement.attributes.Ssid:build_test_report_data(mock_device, 1, string.char(data_types.Null.ID)) } } + }, + { + min_api_version = 19 } ) @@ -230,6 +245,9 @@ test.register_message_test( clusters.WifiNetworkMangement.attributes.Ssid:build_test_report_data(mock_device, 1, string.char(0xC0)) -- 0xC0 never appears in utf8 } } + }, + { + min_api_version = 19 } ) @@ -336,7 +354,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.threadNetwork.panId({ value = 55672 })) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua index c5da1b600e..e8a8c3ce24 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua @@ -90,6 +90,9 @@ test.register_message_test( direction = "send", message = {mock_device.id, clusters.DoorLock.server.commands.LockDoor(mock_device, 1)}, }, + }, + { + min_api_version = 19 } ) @@ -111,6 +114,9 @@ test.register_message_test( clusters.DoorLock.server.commands.UnlockDoor(mock_device, 1), }, }, + }, + { + min_api_version = 19 } ) @@ -130,7 +136,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.lock.locked()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -149,7 +158,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.lock.unlocked()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -168,7 +180,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.lock.not_fully_locked()) ) - end + end, + { + min_api_version = 19 + } ) local function refresh_commands(dev) @@ -191,6 +206,9 @@ test.register_message_test( direction = "send", message = {mock_device.id, refresh_commands(mock_device)}, }, + }, + { + min_api_version = 19 } ) @@ -287,6 +305,9 @@ test.register_message_test( capabilities.lockAlarm.alarm.forcedOpeningAttempt({state_change = true}) ), }, + }, + { + min_api_version = 19 } ) @@ -301,7 +322,10 @@ test.register_coroutine_test( capabilities.lockAlarm.alarm.clear({state_change = true}) ) ) -end +end, +{ + min_api_version = 19 +} ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua index eb8ec9fa98..12872290e0 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua @@ -91,7 +91,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) mock_device:expect_metadata_update({ profile = "base-lock-nobattery" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -99,7 +102,10 @@ test.register_coroutine_test( function() test.socket.device_lifecycle:__queue_receive({ mock_device_level.id, "doConfigure" }) mock_device_level:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua index 8fd0cc0574..da6a13ecad 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua @@ -69,6 +69,9 @@ test.register_message_test( direction = "send", message = {mock_device.id, clusters.DoorLock.server.commands.LockDoor(mock_device, 10)}, }, + }, + { + min_api_version = 19 } ) @@ -90,6 +93,9 @@ test.register_message_test( clusters.DoorLock.server.commands.UnlockDoor(mock_device, 10), }, }, + }, + { + min_api_version = 19 } ) @@ -110,6 +116,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked()), }, + }, + { + min_api_version = 19 } ) @@ -130,6 +139,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked()), }, + }, + { + min_api_version = 19 } ) @@ -150,6 +162,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.not_fully_locked()), }, + }, + { + min_api_version = 19 } ) @@ -170,6 +185,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked()), }, + }, + { + min_api_version = 19 } ) @@ -192,6 +210,9 @@ test.register_message_test( "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) ), }, + }, + { + min_api_version = 19 } ) @@ -216,6 +237,9 @@ test.register_message_test( direction = "send", message = {mock_device.id, refresh_commands(mock_device)}, }, + }, + { + min_api_version = 19 } ) @@ -282,6 +306,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()), }, + }, + { + min_api_version = 19 } ) @@ -319,6 +346,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()), }, + }, + { + min_api_version = 19 } ) @@ -331,7 +361,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) ) -end +end, +{ + min_api_version = 19 +} ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua index a8f7506798..135cb84c37 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua @@ -119,7 +119,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ profile = "base-lock" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -144,13 +147,19 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ profile = "base-lock-batteryLevel" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Test that profile changes to base-lock-no-battery when battery feature is not available", function() end, - { test_init = test_init_no_battery } + { + test_init = test_init_no_battery, + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua index 990bff8b60..16787f10e5 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua @@ -92,6 +92,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.batteryLevel.battery.normal()), }, + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua index a3e05c9634..6bfc5e5bfb 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua @@ -223,7 +223,10 @@ test.register_coroutine_test( req:merge(DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device, 10)) test.socket.matter:__expect_send({mock_device.id, req}) expect_reload_all_codes_messages(mock_device) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -236,7 +239,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -257,6 +263,9 @@ test.register_message_test( capabilities.lockCodes.minCodeLength(4, {visibility = {displayed = false}}) ), }, + }, + { + min_api_version = 19 } ) @@ -278,6 +287,9 @@ test.register_message_test( capabilities.lockCodes.maxCodeLength(4, {visibility = {displayed = false}}) ), }, + }, + { + min_api_version = 19 } ) @@ -299,6 +311,9 @@ test.register_message_test( capabilities.lockCodes.maxCodes(16, {visibility = {displayed = false}}) ), }, + }, + { + min_api_version = 19 } ) @@ -318,6 +333,9 @@ test.register_message_test( ), }, } + }, + { + min_api_version = 19 } ) @@ -340,6 +358,9 @@ test.register_message_test( ), }, } + }, + { + min_api_version = 19 } ) @@ -352,7 +373,10 @@ test.register_coroutine_test( } ) expect_reload_all_codes_messages(mock_device) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -394,7 +418,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -429,7 +456,10 @@ test.register_coroutine_test( .codeChanged("1 unset", {data = {codeName = "Code 1"}, state_change = true}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -532,7 +562,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -578,7 +611,10 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({}), {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -635,7 +671,10 @@ test.register_coroutine_test( .lockCodes(json.encode({["1"] = "test"}), {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -667,7 +706,10 @@ test.register_coroutine_test( .lockCodes(json.encode({["1"] = "foo"}), {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Setting a user code name via setCode should be handled", function() @@ -698,7 +740,10 @@ test.register_coroutine_test( .lockCodes(json.encode({["1"] = "foo"}), {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -737,6 +782,9 @@ test.register_message_test( ) ), }, + }, + { + min_api_version = 19 } ) @@ -777,7 +825,10 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({}), {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -853,7 +904,10 @@ test.register_coroutine_test( ) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua index c41d911881..143c04763e 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua @@ -143,7 +143,10 @@ test.register_coroutine_test( "Added should kick off cota cred process", function() test.socket.matter:__set_channel_ordering("relaxed") expect_kick_off_cota_process(mock_device) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -157,7 +160,10 @@ test.register_coroutine_test( mock_device.id, clusters.DoorLock.server.commands.UnlockDoor(mock_device, 10, "1111"), }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -171,7 +177,10 @@ test.register_coroutine_test( mock_device.id, clusters.DoorLock.server.commands.LockDoor(mock_device, 10, "1111"), }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -203,7 +212,10 @@ test.register_coroutine_test( DoorLock.types.DlUserType.REMOTE_ONLY_USER -- user_type ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -224,7 +236,10 @@ test.register_coroutine_test( profile = "nonfunctional-lock", provisioning_state = "NONFUNCTIONAL" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -323,7 +338,10 @@ test.register_coroutine_test( ) local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read() test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -418,7 +436,10 @@ test.register_coroutine_test( ) local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read() test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -506,7 +527,10 @@ test.register_coroutine_test( ) local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read() test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -563,7 +587,10 @@ test.register_coroutine_test( DoorLock.types.DlUserType.REMOTE_ONLY_USER -- user_type ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -600,7 +627,10 @@ test.register_coroutine_test( ) }) test.mock_time.advance_time(2) - end + end, + { + min_api_version = 19 + } ) @@ -678,7 +708,10 @@ test.register_coroutine_test( DoorLock.types.DlUserType.REMOTE_ONLY_USER -- user_type ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -710,12 +743,18 @@ test.register_coroutine_test( DoorLock.types.DlUserType.REMOTE_ONLY_USER -- user_type ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Delay setting COTA cred if another cred is already being set.", function() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index c4c226fbbe..c5600c069b 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -349,7 +349,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) ) mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {}}} }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -380,7 +383,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) ) mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {"batteryLevel"}}} }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -412,7 +418,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) ) mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {"battery"}}} }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -443,7 +452,10 @@ test.register_coroutine_test( ) mock_device_unlatch:expect_metadata_update({ profile = "lock-modular-embedded-unlatch", optional_component_capabilities = {{"main", {}}} }) end, - { test_init = test_init_unlatch } + { + test_init = test_init_unlatch, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -475,7 +487,10 @@ test.register_coroutine_test( ) mock_device_unlatch:expect_metadata_update({ profile = "lock-modular-embedded-unlatch", optional_component_capabilities = {{"main", {"batteryLevel"}}} }) end, - { test_init = test_init_unlatch } + { + test_init = test_init_unlatch, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -508,7 +523,10 @@ test.register_coroutine_test( ) mock_device_unlatch:expect_metadata_update({ profile = "lock-modular-embedded-unlatch", optional_component_capabilities = {{"main", {"battery"}}} }) end, - { test_init = test_init_unlatch } + { + test_init = test_init_unlatch, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -541,7 +559,10 @@ test.register_coroutine_test( ) mock_device_user_pin:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {"lockUsers", "lockCredentials", "battery"}}} }) end, - { test_init = test_init_user_pin } + { + test_init = test_init_user_pin, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -575,7 +596,10 @@ test.register_coroutine_test( mock_device_user_pin_schedule_unlatch:expect_metadata_update({ profile = "lock-modular-embedded-unlatch", optional_component_capabilities = {{"main", {"lockUsers", "lockCredentials", "lockSchedules", "battery"}}} }) end, - { test_init = test_init_user_pin_schedule_unlatch } + { + test_init = test_init_user_pin_schedule_unlatch, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -638,7 +662,10 @@ test.register_coroutine_test( mock_device_modular:generate_test_message("main", capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) ) end, - { test_init = test_init_modular } + { + test_init = test_init_modular, + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua index 642ca3bf7a..d8d2b9f137 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua @@ -101,7 +101,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -149,7 +152,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({}, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -167,6 +173,9 @@ test.register_message_test( direction = "send", message = {mock_device.id, DoorLock.server.commands.LockDoor(mock_device, 1)}, }, + }, + { + min_api_version = 19 } ) @@ -188,6 +197,9 @@ test.register_message_test( DoorLock.server.commands.UnboltDoor(mock_device, 1), }, }, + }, + { + min_api_version = 19 } ) @@ -209,6 +221,9 @@ test.register_message_test( DoorLock.server.commands.UnlockDoor(mock_device, 1), }, }, + }, + { + min_api_version = 19 } ) @@ -228,7 +243,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.lock.locked()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -247,7 +265,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.lock.unlocked()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -266,7 +287,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.lock.unlatched()) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -352,6 +376,9 @@ test.register_message_test( ) ), } + }, + { + min_api_version = 19 } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 738248fd8e..4388c61a4a 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -110,7 +110,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -158,7 +161,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({}, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -175,7 +181,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockUsers.totalUsersSupported(10, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -192,7 +201,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.pinUsersSupported(10, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -209,7 +221,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.minPinCodeLen(6, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -226,7 +241,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.maxPinCodeLen(8, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -243,7 +261,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockSchedules.weekDaySchedulesPerUser(5, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -260,7 +281,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockSchedules.yearDaySchedulesPerUser(5, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -328,7 +352,10 @@ test.register_coroutine_test( ), } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -406,7 +433,10 @@ function() ) test.mock_time.advance_time(1) test.wait_for_events() -end +end, +{ + min_api_version = 19 +} ) test.register_coroutine_test( @@ -420,7 +450,10 @@ test.register_coroutine_test( ), } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -437,7 +470,10 @@ test.register_coroutine_test( mock_device.id, clusters.DoorLock.server.commands.UnlockDoor(mock_device, 1, "654123"), }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -453,7 +489,10 @@ test.register_coroutine_test( mock_device.id, clusters.DoorLock.server.commands.LockDoor(mock_device, 1, "654123"), }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -471,6 +510,9 @@ test.register_message_test( direction = "send", message = {mock_device.id, DoorLock.server.commands.LockDoor(mock_device, 1)}, }, + }, + { + min_api_version = 19 } ) @@ -492,6 +534,9 @@ test.register_message_test( DoorLock.server.commands.UnlockDoor(mock_device, 1), }, }, + }, + { + min_api_version = 19 } ) @@ -511,7 +556,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.lock.locked()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -530,7 +578,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.lock.unlocked()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -549,7 +600,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.lock.not_fully_locked()) ) - end + end, + { + min_api_version = 19 + } ) local function refresh_commands(dev) @@ -572,6 +626,9 @@ test.register_message_test( direction = "send", message = {mock_device.id, refresh_commands(mock_device)}, }, + }, + { + min_api_version = 19 } ) @@ -668,6 +725,9 @@ test.register_message_test( capabilities.lockAlarm.alarm.forcedOpeningAttempt({state_change = true}) ), }, + }, + { + min_api_version = 19 } ) @@ -892,6 +952,9 @@ test.register_message_test( ) ), } + }, + { + min_api_version = 19 } ) @@ -906,7 +969,10 @@ test.register_coroutine_test( capabilities.lockAlarm.alarm.clear({state_change = true}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -972,7 +1038,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -996,7 +1065,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1037,7 +1109,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1083,7 +1158,10 @@ test.register_coroutine_test( ) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1107,7 +1185,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1170,7 +1251,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1194,7 +1278,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1257,7 +1344,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1281,7 +1371,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1343,7 +1436,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1369,7 +1465,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1422,7 +1521,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1482,7 +1584,10 @@ test.register_coroutine_test( ), } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1535,7 +1640,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1588,7 +1696,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1614,7 +1725,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1665,7 +1779,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1691,7 +1808,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1760,7 +1880,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1786,7 +1909,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1854,7 +1980,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1880,7 +2009,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1927,7 +2059,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1956,7 +2091,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua index 03a51a3150..373c4184ed 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua @@ -291,7 +291,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) ) mock_device:expect_metadata_update({ profile = "lock" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -319,7 +322,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) ) mock_device:expect_metadata_update({ profile = "lock-batteryLevel" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -348,7 +354,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) ) mock_device:expect_metadata_update({ profile = "lock-battery" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -376,7 +385,10 @@ test.register_coroutine_test( ) mock_device_unlatch:expect_metadata_update({ profile = "lock-unlatch" }) end, - { test_init = test_init_unlatch } + { + test_init = test_init_unlatch, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -405,7 +417,10 @@ test.register_coroutine_test( ) mock_device_unlatch:expect_metadata_update({ profile = "lock-unlatch-batteryLevel" }) end, - { test_init = test_init_unlatch } + { + test_init = test_init_unlatch, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -435,7 +450,10 @@ test.register_coroutine_test( ) mock_device_unlatch:expect_metadata_update({ profile = "lock-unlatch-battery" }) end, - { test_init = test_init_unlatch } + { + test_init = test_init_unlatch, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -463,7 +481,10 @@ test.register_coroutine_test( ) mock_device_user_pin:expect_metadata_update({ profile = "lock-user-pin" }) end, - { test_init = test_init_user_pin } + { + test_init = test_init_user_pin, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -492,7 +513,10 @@ test.register_coroutine_test( ) mock_device_user_pin:expect_metadata_update({ profile = "lock-user-pin-batteryLevel" }) end, - { test_init = test_init_user_pin } + { + test_init = test_init_user_pin, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -522,7 +546,10 @@ test.register_coroutine_test( ) mock_device_user_pin:expect_metadata_update({ profile = "lock-user-pin-battery" }) end, - { test_init = test_init_user_pin } + { + test_init = test_init_user_pin, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -550,7 +577,10 @@ test.register_coroutine_test( ) mock_device_user_pin_schedule_unlatch:expect_metadata_update({ profile = "lock-user-pin-schedule-unlatch" }) end, - { test_init = test_init_user_pin_schedule_unlatch } + { + test_init = test_init_user_pin_schedule_unlatch, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -579,7 +609,10 @@ test.register_coroutine_test( ) mock_device_user_pin_schedule_unlatch:expect_metadata_update({ profile = "lock-user-pin-schedule-unlatch-batteryLevel" }) end, - { test_init = test_init_user_pin_schedule_unlatch } + { + test_init = test_init_user_pin_schedule_unlatch, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -609,7 +642,10 @@ test.register_coroutine_test( ) mock_device_user_pin_schedule_unlatch:expect_metadata_update({ profile = "lock-user-pin-schedule-unlatch-battery" }) end, - { test_init = test_init_user_pin_schedule_unlatch } + { + test_init = test_init_user_pin_schedule_unlatch, + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-media/src/test/test_matter_media_speaker.lua b/drivers/SmartThings/matter-media/src/test/test_matter_media_speaker.lua index cf5bc44648..80b0c33ff8 100644 --- a/drivers/SmartThings/matter-media/src/test/test_matter_media_speaker.lua +++ b/drivers/SmartThings/matter-media/src/test/test_matter_media_speaker.lua @@ -124,6 +124,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.audioMute.mute.muted()) } + }, + { + min_api_version = 19 } ) @@ -162,6 +165,9 @@ test.register_message_test( clusters.OnOff.server.commands.On(mock_device, 10) } } + }, + { + min_api_version = 19 } ) @@ -205,6 +211,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.audioVolume.volume(20)) } + }, + { + min_api_version = 19 } ) @@ -324,6 +333,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.audioVolume.volume(20)) }, + }, + { + min_api_version = 19 } ) @@ -352,6 +364,9 @@ test.register_message_test( refresh_commands(mock_device) } }, + }, + { + min_api_version = 19 } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua b/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua index ebae93ca53..296d203b41 100644 --- a/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua +++ b/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua @@ -238,6 +238,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) }, + }, + { + min_api_version = 19 } ) @@ -273,6 +276,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.mediaPlayback.playbackStatus.playing()) }, + }, + { + min_api_version = 19 } ) @@ -308,6 +314,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.mediaPlayback.playbackStatus.paused()) }, + }, + { + min_api_version = 19 } ) @@ -343,6 +352,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.mediaPlayback.playbackStatus.stopped()) }, + }, + { + min_api_version = 19 } ) @@ -381,6 +393,9 @@ test.register_message_test( clusters.MediaPlayback.server.commands.FastForward(mock_device, 10) } }, + }, + { + min_api_version = 19 } ) @@ -419,6 +434,9 @@ test.register_message_test( clusters.MediaPlayback.server.commands.Next(mock_device, 10) } }, + }, + { + min_api_version = 19 } ) @@ -585,6 +603,9 @@ test.register_message_test( clusters.KeypadInput.server.commands.SendKey(mock_device, 10, clusters.KeypadInput.types.CecKeyCode.ROOT_MENU) } } + }, + { + min_api_version = 19 } ) @@ -615,7 +636,10 @@ test.register_coroutine_test( ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -645,7 +669,10 @@ test.register_coroutine_test( ) mock_device_variable_speed:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua b/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua index e8d91fd1ca..431a6e1c8e 100644 --- a/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua +++ b/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua @@ -228,7 +228,10 @@ test.register_coroutine_test( mock_device.id, clusters.RvcOperationalState.attributes.AcceptedCommandList:read() }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -256,7 +259,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -281,7 +287,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -306,7 +315,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -330,7 +342,10 @@ test.register_coroutine_test( ) ) end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -348,7 +363,10 @@ test.register_coroutine_test( clusters.RvcCleanMode.server.commands.ChangeToMode(mock_device, APPLICATION_ENDPOINT, cleanMode.mode) }) end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -386,7 +404,10 @@ test.register_coroutine_test( mock_device.id, clusters.RvcRunMode.server.commands.ChangeToMode(mock_device, APPLICATION_ENDPOINT, CLEANING_MODE.mode) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -421,7 +442,10 @@ test.register_coroutine_test( mock_device.id, clusters.RvcOperationalState.commands.GoHome(mock_device, APPLICATION_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -482,7 +506,10 @@ test.register_coroutine_test( mock_device.id, clusters.RvcOperationalState.commands.Pause(mock_device, APPLICATION_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -533,7 +560,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -587,7 +617,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -641,7 +674,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -692,7 +728,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -743,7 +782,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -975,7 +1017,10 @@ test.register_coroutine_test( capabilities.robotCleanerOperatingState.operatingState.mopCleaningPadMissing() ) ) - end + end, + { + min_api_version = 19 + } ) local locationDescriptorStruct = require "Global.types.LocationDescriptorStruct" @@ -1008,6 +1053,9 @@ test.register_message_test( {["areaId"] = 1, ["areaName"] = "0F Balcony" }, }, { visibility = { displayed = false } })) }, + }, + { + min_api_version = 19 } ) @@ -1031,6 +1079,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.serviceArea.selectedAreas({ 1,2,5 }, { visibility = { displayed = false } })) }, + }, + { + min_api_version = 19 } ) @@ -1054,6 +1105,9 @@ test.register_message_test( clusters.ServiceArea.server.commands.SelectAreas(mock_device, APPLICATION_ENDPOINT, {uint32_dt(1),uint32_dt(2)}) } }, + }, + { + min_api_version = 19 } ) @@ -1136,6 +1190,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.serviceArea.selectedAreas({ 1,2,5 },{ state_change=true})) }, + }, + { + min_api_version = 19 } ) 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 1988563bc8..734aaad66b 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 @@ -497,7 +497,10 @@ test.register_coroutine_test( "Configure should read units from device and profile change as needed", function() test_aqs_device_type_do_configure(mock_device, "aqs-temp-humidity-all-level-all-meas") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -505,7 +508,10 @@ test.register_coroutine_test( function() test_aqs_device_type_do_configure(mock_device_common, "aqs-temp-humidity-co2-pm25-tvoc-meas") end, - { test_init = test_init_common } + { + test_init = test_init_common, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -513,7 +519,10 @@ test.register_coroutine_test( function() test_aqs_device_type_do_configure(mock_device_level, "aqs-temp-humidity-all-level") end, - { test_init = test_init_level } + { + test_init = test_init_level, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -522,7 +531,10 @@ test.register_coroutine_test( 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 } + { + test_init = test_init_co_co2, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -530,7 +542,10 @@ test.register_coroutine_test( function() test_aqs_device_type_do_configure(mock_device_tvoc, "aqs-temp-humidity-tvoc-meas") end, - { test_init = test_init_tvoc } + { + test_init = test_init_tvoc, + min_api_version = 19 + } ) @@ -551,6 +566,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -570,6 +588,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 40 })) } + }, + { + min_api_version = 19 } ) @@ -602,6 +623,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.airQualityHealthConcern.hazardous()) }, + }, + { + min_api_version = 19 } ) @@ -669,7 +693,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.tvocMeasurement.tvocLevel({value = 750, unit = "ppb"})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -691,7 +718,10 @@ test.register_coroutine_test( mock_device_common:generate_test_message("main", capabilities.fineDustSensor.fineDustLevel({value = 18, unit = "μg/m^3"})) ) end, - { test_init = test_init_common } + { + test_init = test_init_common, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -796,7 +826,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.tvocHealthConcern.tvocHealthConcern.hazardous()) ) - end + end, + { + min_api_version = 19 + } ) -- run tests 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 5b6b43f2f5..69e87e0e31 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 @@ -319,7 +319,10 @@ test.register_coroutine_test( 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_init = test_init_all, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -346,7 +349,10 @@ test.register_coroutine_test( 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 } + { + test_init = test_init_common, + min_api_version = 19 + } ) -- run tests 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 1ca2add2ff..b999ac44f9 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 @@ -80,6 +80,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) --should send initial press } + }, + { + min_api_version = 19 } ) @@ -115,6 +118,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.button.button.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -160,6 +166,9 @@ test.register_message_test( ) } }, + }, + { + min_api_version = 19 } ) @@ -181,6 +190,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -218,6 +230,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.button.double({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -240,6 +255,9 @@ test.register_message_test( "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) ), }, + }, + { + min_api_version = 19 } ) @@ -272,6 +290,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) 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 6b1301cb96..220ac3c51e 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 @@ -69,6 +69,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.flowMeasurement.flow({ value = 20.0, unit = "m^3/h" })) } + }, + { + min_api_version = 19 } ) @@ -96,6 +99,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.flowMeasurement.flowRange({ value = { minimum = 2.0, maximum = 500.0 }, unit = "m^3/h" })) } + }, + { + min_api_version = 19 } ) @@ -125,6 +131,9 @@ test.register_message_test( refresh_commands(mock_device) } }, + }, + { + min_api_version = 19 } ) 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 44a984b2a7..0c05271917 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 @@ -97,6 +97,9 @@ test.register_message_test( direction = "send", message = mock_device_freeze_leak:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.freeze()) } + }, + { + min_api_version = 19 } ) @@ -130,6 +133,9 @@ test.register_message_test( direction = "send", message = mock_device_freeze_leak:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -162,6 +168,9 @@ test.register_message_test( direction = "send", message = mock_device_freeze_leak:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) } + }, + { + min_api_version = 19 } ) @@ -180,7 +189,10 @@ test.register_coroutine_test( mock_device_freeze_leak.id, clusters.BooleanStateConfiguration.attributes.CurrentSensitivityLevel:write(mock_device_freeze_leak, 2, 0) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -197,7 +209,10 @@ test.register_coroutine_test( mock_device_freeze_leak.id, clusters.BooleanStateConfiguration.attributes.CurrentSensitivityLevel:write(mock_device_freeze_leak, 2, mock_device_freeze_leak:get_field("freezeMax") - 1) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -227,7 +242,10 @@ test.register_coroutine_test( mock_device_freeze_leak.id, clusters.BooleanStateConfiguration.attributes.CurrentSensitivityLevel:write(mock_device_freeze_leak, 2, 0) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 f7b3d7afe0..24659a2426 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 @@ -92,6 +92,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.atmosphericPressureMeasurement.atmosphericPressure({ value = 0, unit = "kPa" })) } + }, + { + min_api_version = 19 } ) @@ -111,6 +114,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5))) } + }, + { + min_api_version = 19 } ) @@ -140,6 +146,9 @@ test.register_message_test( refresh_commands(mock_device) } }, + }, + { + min_api_version = 19 } ) test.run_registered_tests() 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 21f8a0fa51..b25ca9ff96 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 @@ -94,6 +94,9 @@ test.register_message_test( direction = "send", message = mock_device_rain:generate_test_message("main", capabilities.rainSensor.rain.detected()) } + }, + { + min_api_version = 19 } ) @@ -126,6 +129,9 @@ test.register_message_test( direction = "send", message = mock_device_rain:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) } + }, + { + min_api_version = 19 } ) 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 a66e575310..06f7f40fb8 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor.lua @@ -161,6 +161,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 41 })) } + }, + { + min_api_version = 19 } ) @@ -180,6 +183,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -199,6 +205,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 137 })) } + }, + { + min_api_version = 19 } ) @@ -231,6 +240,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -250,6 +262,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5))) } + }, + { + min_api_version = 19 } ) @@ -282,6 +297,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -316,6 +334,9 @@ test.register_message_test( refresh_commands(mock_device) } }, + }, + { + min_api_version = 19 } ) @@ -343,6 +364,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 5.00, maximum = 40.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -355,7 +379,10 @@ test.register_coroutine_test( }) mock_device_presence_sensor:expect_metadata_update({ profile = "presence-illuminance-temperature-humidity-battery" }) end, - { test_init = test_init_presence_sensor } + { + test_init = test_init_presence_sensor, + min_api_version = 19 + } ) test.run_registered_tests() 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 8d3962de66..46b3819baf 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 @@ -86,7 +86,10 @@ test.register_coroutine_test( } ) mock_device_humidity_battery:expect_metadata_update({ profile = "humidity-battery" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -111,7 +114,10 @@ test.register_coroutine_test( } ) mock_device_humidity_battery:expect_metadata_update({ profile = "humidity-batteryLevel" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -134,7 +140,10 @@ test.register_coroutine_test( }) } ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 b1e5d39248..b617e317a0 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 @@ -169,21 +169,30 @@ test.register_coroutine_test( "Test profile change on init for humidity sensor with battery", function() end, - { test_init = test_init_humidity_battery } + { + test_init = test_init_humidity_battery, + min_api_version = 19 + } ) test.register_coroutine_test( "Test profile change on init for humidity sensor without battery", function() end, - { test_init = test_init_humidity_no_battery } + { + test_init = test_init_humidity_no_battery, + min_api_version = 19 + } ) test.register_coroutine_test( "Test profile change on init for temperature-humidity sensor", function() end, - { test_init = test_init_temp_humidity } + { + test_init = test_init_temp_humidity, + min_api_version = 19 + } ) test.run_registered_tests() 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 88af036e19..cfc6455813 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 @@ -69,7 +69,11 @@ test.register_message_test( clusters.TemperatureMeasurement.attributes.MaxMeasuredValue:build_test_report_data(mock_device, 1, 4000) } } + }, + { + min_api_version = 19 } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + 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 c7cfe26c2a..634c9750d7 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 @@ -122,6 +122,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -167,6 +170,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.detected()) } + }, + { + min_api_version = 19 } ) @@ -212,6 +218,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) }, + }, + { + min_api_version = 19 } ) @@ -270,6 +279,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.detected()) }, + }, + { + min_api_version = 19 } ) @@ -328,6 +340,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.detected()) }, + }, + { + min_api_version = 19 } ) @@ -373,6 +388,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.batteryLevel.battery.critical()), } + }, + { + min_api_version = 19 } ) @@ -415,6 +433,9 @@ test.register_message_test( direction = "send", message = {mock_device.id, clusters.SmokeCoAlarm.attributes.COState:read(mock_device)} } + }, + { + min_api_version = 19 } ) @@ -447,6 +468,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) } + }, + { + min_api_version = 19 } ) @@ -466,6 +490,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -498,6 +525,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 41 })) } + }, + { + min_api_version = 19 } ) @@ -546,6 +576,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.carbonMonoxideMeasurement.carbonMonoxideLevel({value = 10, unit = "ppm"})) } + }, + { + min_api_version = 19 } ) 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 816552935e..b5a3fd9f39 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 @@ -90,7 +90,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ profile = "smoke-co-temp-humidity-comeas-battery" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -102,7 +105,10 @@ test.register_coroutine_test( clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, {uint32(0)}) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -119,7 +125,10 @@ test.register_coroutine_test( "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 d47c666166..357c1171dd 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 @@ -162,7 +162,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( aqara_mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -184,7 +187,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( aqara_mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 5.00, maximum = 40.00 }, unit = "C" })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -209,7 +215,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( aqara_mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 41 })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -225,7 +234,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( aqara_mock_device:generate_test_message("main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5))) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -247,7 +259,10 @@ test.register_coroutine_test( clusters.Switch.events.ShortRelease:build_test_event_report(aqara_mock_device, 3, {previous_position = 0}) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -269,7 +284,10 @@ test.register_coroutine_test( clusters.Switch.events.ShortRelease:build_test_event_report(aqara_mock_device, 3, {previous_position = 0}) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -307,7 +325,10 @@ test.register_coroutine_test( } ) test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button2", capabilities.button.button.double({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -333,7 +354,10 @@ test.register_coroutine_test( clusters.Switch.events.LongRelease:build_test_event_report(aqara_mock_device, 3, {previous_position = 0}) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -359,7 +383,10 @@ test.register_coroutine_test( clusters.Switch.events.LongRelease:build_test_event_report(aqara_mock_device, 5, {previous_position = 0}) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -375,7 +402,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( aqara_mock_device:generate_test_message("button1", capabilities.button.button.double({state_change = true})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -391,7 +421,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( aqara_mock_device:generate_test_message("button1", capabilities.button.supportedButtonValues({"pushed", "double", "held"}, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -413,7 +446,10 @@ test.register_coroutine_test( clusters.Switch.events.ShortRelease:build_test_event_report(aqara_mock_device, 4, {previous_position = 0}) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -433,7 +469,10 @@ test.register_coroutine_test( "button3", capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x"}, {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -453,7 +492,10 @@ test.register_coroutine_test( "button1", capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x", "pushed_4x", "pushed_5x", "pushed_6x"}, {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 3c377255ab..5a5eb23b0f 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_aqara_cube.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_aqara_cube.lua @@ -233,7 +233,10 @@ test.register_coroutine_test( "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -265,7 +268,10 @@ test.register_coroutine_test( mock_device_exhausted:generate_test_message("main", cubeFace.cubeFace({value = "face1Up"})) ) end, - { test_init = test_init_exhausted } + { + test_init = test_init_exhausted, + min_api_version = 19 + } ) -- run the tests 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 35f4cd9ce7..9f21039519 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 @@ -267,7 +267,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( aqara_mock_children[aqara_child2_ep]:generate_test_message("main", capabilities.switch.switch.on()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -344,7 +347,10 @@ test.register_coroutine_test( energy = 39.0 })) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 index 8e3ad613da..29981c9b39 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -208,14 +208,6 @@ 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", @@ -224,6 +216,14 @@ test.register_message_test( { capability = "switch", component = "main", command = "on", args = { } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "on" } + } + }, { channel = "matter", direction = "send", @@ -232,19 +232,14 @@ test.register_message_test( clusters.OnOff.server.commands.On(mock_device, 2) } } + }, + { + min_api_version = 19 } ) 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", @@ -254,6 +249,14 @@ test.register_message_test( { capability = "switch", component = "main", command = "off", args = { } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "off" } + } + }, { channel = "matter", direction = "send", @@ -262,6 +265,9 @@ test.register_message_test( clusters.OnOff.server.commands.Off(mock_device, 2) } } + }, + { + min_api_version = 19 } ) @@ -289,6 +295,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -354,7 +363,10 @@ test.register_coroutine_test( energy = 39.0 })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -376,7 +388,10 @@ test.register_coroutine_test( ) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -435,7 +450,10 @@ test.register_coroutine_test( })) ) end, - { test_init = test_init_periodic } + { + test_init = test_init_periodic, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -457,7 +475,10 @@ test.register_coroutine_test( parent_assigned_child_key = string.format("%d", 4) }) end, - { test_init = test_init } + { + test_init = test_init, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -469,7 +490,10 @@ test.register_coroutine_test( 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_init = test_init_periodic, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -584,7 +608,10 @@ test.register_coroutine_test( ) -- 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_init = test_init, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -652,7 +679,10 @@ test.register_coroutine_test( })) ) end, - { test_init = test_init_periodic } + { + test_init = test_init_periodic, + min_api_version = 19 + } ) test.register_message_test( @@ -732,6 +762,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua index b548bb819b..ec9b27749d 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua @@ -139,14 +139,6 @@ test.set_test_init_function(test_init) 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", @@ -155,6 +147,14 @@ test.register_message_test( { capability = "switch", component = "main", command = "on", args = { } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "on" } + } + }, { channel = "matter", direction = "send", @@ -163,19 +163,14 @@ test.register_message_test( clusters.OnOff.server.commands.On(mock_device, 2) } } + }, + { + min_api_version = 19 } ) 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", @@ -185,6 +180,14 @@ test.register_message_test( { capability = "switch", component = "main", command = "off", args = { } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "off" } + } + }, { channel = "matter", direction = "send", @@ -193,6 +196,9 @@ test.register_message_test( clusters.OnOff.server.commands.Off(mock_device, 2) } } + }, + { + min_api_version = 19 } ) @@ -220,6 +226,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -285,7 +294,10 @@ test.register_coroutine_test( energy = 39.0 })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -308,7 +320,10 @@ test.register_coroutine_test( parent_assigned_child_key = string.format("%d", 4) }) end, - { test_init = test_init } + { + test_init = test_init, + min_api_version = 19 + } ) test.register_message_test( @@ -388,6 +403,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) 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 a189b1e5fc..2f40904cd8 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -180,6 +180,9 @@ test.register_message_test( clusters.OnOff.server.commands.On(mock_device, 1) } } + }, + { + min_api_version = 19 } ) @@ -210,6 +213,9 @@ test.register_message_test( clusters.OnOff.server.commands.Off(mock_device, 1) } } + }, + { + min_api_version = 19 } ) @@ -227,7 +233,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -239,7 +248,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "removed" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -270,7 +282,8 @@ test.register_coroutine_test( test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") - end + end, + min_api_version = 19 } ) @@ -287,7 +300,10 @@ test.register_coroutine_test( refresh_response:merge(cluster_base.read(mock_device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_WATT_ACCUMULATED, nil)) test.socket.matter:__expect_send({ mock_device.id, refresh_response}) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -311,7 +327,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -335,7 +354,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -371,7 +393,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -396,7 +421,10 @@ test.register_coroutine_test( cluster_base.write(mock_device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_ACCUMULATED_CONTROL_POINT, nil, data) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -420,7 +448,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -454,7 +485,8 @@ test.register_coroutine_test( test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") - end + end, + min_api_version = 19 } ) @@ -545,7 +577,11 @@ test.register_coroutine_test( energy = 39.0 })) ) - end, { test_init = test_init_electrical_sensor } + end, + { + test_init = test_init_electrical_sensor, + min_api_version = 19 + } ) 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 index 4629d69a5f..6e1e0206bc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua @@ -1,3 +1,6 @@ +-- Copyright 2026 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" @@ -232,6 +235,9 @@ test.register_message_test( message = mock_ikea_scroll:generate_test_message("group3", capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -353,6 +359,9 @@ test.register_message_test( ) }, } + }, + { + min_api_version = 19 } ) @@ -474,6 +483,9 @@ test.register_message_test( ) }, } + }, + { + min_api_version = 19 } ) @@ -527,6 +539,9 @@ test.register_message_test( message = mock_ikea_scroll:generate_test_message("group2", capabilities.knob.rotateAmount(18, {state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -580,6 +595,9 @@ test.register_message_test( message = mock_ikea_scroll:generate_test_message("group2", capabilities.knob.rotateAmount(-18, {state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -633,6 +651,9 @@ test.register_message_test( message = mock_ikea_scroll:generate_test_message("group3", capabilities.knob.rotateAmount(18, {state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -686,6 +707,9 @@ test.register_message_test( message = mock_ikea_scroll:generate_test_message("group3", capabilities.knob.rotateAmount(-18, {state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -717,6 +741,9 @@ test.register_message_test( message = mock_ikea_scroll:generate_test_message("main", capabilities.button.button.held({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -748,6 +775,9 @@ test.register_message_test( message = mock_ikea_scroll:generate_test_message("group2", capabilities.button.button.pushed({state_change = true})) }, + }, + { + min_api_version = 19 } ) 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 abc98591fc..36624197b1 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 @@ -152,6 +152,9 @@ test.register_message_test( clusters.OnOff.server.commands.On(mock_device, 1) } } + }, + { + min_api_version = 19 } ) @@ -182,6 +185,9 @@ test.register_message_test( clusters.OnOff.server.commands.Off(mock_device, 1) } } + }, + { + min_api_version = 19 } ) @@ -262,6 +268,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -289,6 +298,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -372,6 +384,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "saturation" } } }, + }, + { + min_api_version = 19 } ) @@ -394,6 +409,9 @@ test.register_message_test( clusters.ColorControl.server.commands.MoveToHue(mock_device, 1, hue, 0, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, + }, + { + min_api_version = 19 } ) @@ -416,6 +434,9 @@ test.register_message_test( clusters.ColorControl.server.commands.MoveToSaturation(mock_device, 1, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, + }, + { + min_api_version = 19 } ) @@ -459,6 +480,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) }, + }, + { + min_api_version = 19 } ) @@ -488,7 +512,10 @@ test.register_coroutine_test( ) ) end, - { test_init = test_init_x_y_color_mode } + { + test_init = test_init_x_y_color_mode, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -517,7 +544,10 @@ test.register_coroutine_test( ) ) end, - { test_init = test_init_x_y_color_mode } + { + test_init = test_init_x_y_color_mode, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -546,7 +576,10 @@ test.register_coroutine_test( ) ) end, - { test_init = test_init_x_y_color_mode } + { + test_init = test_init_x_y_color_mode, + min_api_version = 19 + } ) test.register_message_test( @@ -560,6 +593,9 @@ test.register_message_test( clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 0) } } + }, + { + min_api_version = 19 } ) @@ -579,6 +615,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 137 })) } + }, + { + min_api_version = 19 } ) @@ -611,6 +650,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) 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 20e2545349..75830d55a7 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua @@ -71,7 +71,10 @@ test.register_coroutine_test( "Profile should not change for devices with aggregator device type (bridges)", function() end, - { test_init = test_init_mock_bridge } + { + test_init = test_init_mock_bridge, + min_api_version = 19 + } ) test.run_registered_tests() 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 f84c29c4c5..7de587d93f 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua @@ -170,7 +170,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device_battery.id, updated_subscribe_request}) expect_configure_buttons(mock_device_battery) end, - { test_init = test_init_battery } + { + test_init = test_init_battery, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -191,7 +194,10 @@ test.register_coroutine_test( ) ) end, - { test_init = test_init_battery } + { + test_init = test_init_battery, + min_api_version = 19 + } ) test.register_message_test( @@ -211,6 +217,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) --should send initial press } + }, + { + min_api_version = 19 } ) @@ -246,6 +255,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -282,6 +294,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -327,6 +342,9 @@ test.register_message_test( ) } }, + }, + { + min_api_version = 19 } ) @@ -348,6 +366,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -369,6 +390,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double", "pushed_3x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -390,6 +414,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double", "pushed_3x", "pushed_4x", "pushed_5x", "pushed_6x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -426,6 +453,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.double({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -462,6 +492,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed_4x({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -481,7 +514,10 @@ test.register_coroutine_test( clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, {uint32(10)}) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -499,7 +535,10 @@ test.register_coroutine_test( ) expect_configure_buttons(mock_device) mock_device:expect_metadata_update({ profile = "button-batteryLevel" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -517,7 +556,10 @@ test.register_coroutine_test( ) expect_configure_buttons(mock_device) mock_device:expect_metadata_update({ profile = "button-battery" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index facc6ea984..81a366624c 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -392,7 +392,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", v.capability("disabled")) ) end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -432,7 +435,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", v.capability("auto")) ) end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -458,7 +464,10 @@ test.register_coroutine_test( first_value = false end end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -499,7 +508,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.webrtc.talkback(false)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -527,7 +539,10 @@ test.register_coroutine_test( mock_device:generate_test_message(v.component, capabilities.audioMute.mute("unmuted")) ) end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -574,7 +589,10 @@ test.register_coroutine_test( mock_device:generate_test_message(v.component, capabilities.audioVolume.volume(32)) ) end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -596,7 +614,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("statusLed", capabilities.switch.switch.off()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -646,7 +667,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("statusLed", capabilities.mode.mode("auto")) ) - end + end, + { + min_api_version = 19 + } ) local function receive_rate_distortion_trade_off_points() @@ -767,7 +791,10 @@ test.register_coroutine_test( receive_video_sensor_params() emit_video_sensor_parameters() emit_supported_resolutions() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -782,7 +809,10 @@ test.register_coroutine_test( emit_video_sensor_parameters() receive_max_encoded_pixel_rate() emit_supported_resolutions() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -797,7 +827,10 @@ test.register_coroutine_test( emit_video_sensor_parameters() receive_rate_distortion_trade_off_points() emit_supported_resolutions() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -849,7 +882,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.zoom(30)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -870,7 +906,10 @@ test.register_coroutine_test( { id = 2, label = "Preset 2", pan = -55, tilt = 80, zoom = 60} })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -885,7 +924,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.maxPresets(10)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -900,7 +942,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.zoneManagement.maxZones(10)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -945,7 +990,10 @@ test.register_coroutine_test( } })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -980,7 +1028,10 @@ test.register_coroutine_test( } })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1003,7 +1054,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.zoneManagement.sensitivity(5, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1029,7 +1083,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) -- Event Handler UTs @@ -1069,7 +1126,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.zoneManagement.triggeredZones({{zoneId = 3}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1092,7 +1152,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("doorbell", capabilities.button.button.double({state_change = true})) ) - end + end, + { + min_api_version = 19 + } ) -- Capability Handler UTs @@ -1129,7 +1192,10 @@ test.register_coroutine_test( mock_device.id, attr:write(mock_device, CAMERA_EP, clusters.CameraAvStreamManagement.types.TriStateAutoEnum.AUTO) }) end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1162,7 +1228,10 @@ test.register_coroutine_test( mock_device.id, v.attr:write(mock_device, CAMERA_EP, false) }) end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1184,7 +1253,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send({ mock_device.id, clusters.CameraAvStreamManagement.attributes.ImageRotation:write(mock_device, CAMERA_EP, 257) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1248,7 +1320,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send({ mock_device.id, clusters.CameraAvStreamManagement.attributes.MicrophoneMuted:write(mock_device, CAMERA_EP, false) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1353,7 +1428,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("microphone", capabilities.audioVolume.volume(99)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1376,7 +1454,10 @@ test.register_coroutine_test( mock_device.id, clusters.CameraAvStreamManagement.attributes.StatusLightBrightness:write(mock_device, CAMERA_EP, v) }) end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1398,7 +1479,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send({ mock_device.id, clusters.CameraAvStreamManagement.attributes.StatusLightEnabled:write(mock_device, CAMERA_EP, false) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1427,7 +1511,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send({ mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, 0, 0, 80) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1505,7 +1592,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.zoom(5)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1534,7 +1624,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send({ mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZMoveToPreset(mock_device, CAMERA_EP, 2) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1556,7 +1649,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send({ mock_device.id, clusters.Chime.server.commands.PlayChimeSound(mock_device, CAMERA_EP) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1627,7 +1723,10 @@ test.register_coroutine_test( mock_device.id, clusters.ZoneManagement.server.commands.RemoveZone(mock_device, CAMERA_EP, i) }) end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1697,7 +1796,10 @@ test.register_coroutine_test( mock_device.id, clusters.ZoneManagement.server.commands.RemoveZone(mock_device, CAMERA_EP, i) }) end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1770,7 +1872,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send({ mock_device.id, clusters.ZoneManagement.server.commands.RemoveTrigger(mock_device, CAMERA_EP, 1) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1859,7 +1964,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.zoneManagement.zones({value = {}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1884,7 +1992,10 @@ test.register_coroutine_test( 3, true, false ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1949,7 +2060,10 @@ test.register_coroutine_test( 1, false, true ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1964,7 +2078,10 @@ test.register_coroutine_test( uint32(clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) }) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2003,7 +2120,10 @@ test.register_coroutine_test( mock_device:expect_metadata_update(updated_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 + end, + { + min_api_version = 19 + } ) -- run the 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 e1af3ad52b..cc435a7f95 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 @@ -159,7 +159,10 @@ test.register_coroutine_test( "main", capabilities.switch.switch.on() ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -202,6 +205,9 @@ test.register_message_test( direction = "send", message = mock_child:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) }, + }, + { + min_api_version = 19 } ) @@ -248,6 +254,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.fanMode.fanMode("medium")) } + }, + { + min_api_version = 19 } ) @@ -281,6 +290,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.fanMode.supportedFanModes({"off", "low", "medium", "high", "auto"}, {visibility={displayed=false}})) }, + }, + { + min_api_version = 19 } ) @@ -303,6 +315,9 @@ test.register_message_test( clusters.FanControl.attributes.FanMode:write(mock_device, mock_device_ep2, FanMode.LOW) } } + }, + { + min_api_version = 19 } ) @@ -325,6 +340,9 @@ test.register_message_test( clusters.FanControl.attributes.PercentSetting:write(mock_device, mock_device_ep2, 64) } } + }, + { + min_api_version = 19 } ) 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 33002a8203..f929b93745 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 @@ -292,7 +292,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device_battery.id, updated_subscribe_request}) expect_configure_buttons(mock_device_battery) end, - { test_init = test_init_battery } + { + test_init = test_init_battery, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -313,7 +316,10 @@ test.register_coroutine_test( ) ) end, - { test_init = test_init_battery } + { + test_init = test_init_battery, + min_api_version = 19 + } ) test.register_message_test( @@ -333,6 +339,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) --should send initial press } + }, + { + min_api_version = 19 } ) @@ -363,6 +372,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("button2", button_attr.pushed({state_change = true})) --should send initial press } + }, + { + min_api_version = 19 } ) @@ -385,7 +397,10 @@ test.register_coroutine_test( ) }) test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.held({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -407,7 +422,10 @@ test.register_coroutine_test( ) }) test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -428,7 +446,10 @@ test.register_coroutine_test( mock_device, 50, {previous_position = 0} ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -465,7 +486,10 @@ test.register_coroutine_test( ) }) test.socket.capability:__expect_send(mock_device:generate_test_message("button5", button_attr.double({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -490,7 +514,10 @@ test.register_coroutine_test( mock_device, 30, {previous_position = 0} ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -515,7 +542,10 @@ test.register_coroutine_test( mock_device, 60, {previous_position = 0} ) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -550,6 +580,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -586,6 +619,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -631,6 +667,9 @@ test.register_message_test( ) } }, + }, + { + min_api_version = 19 } ) @@ -652,6 +691,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -673,6 +715,9 @@ test.register_message_test( message = mock_device:generate_test_message("button5", capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -694,6 +739,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double", "pushed_3x", "pushed_4x", "pushed_5x", "pushed_6x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -730,6 +778,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.double({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -766,6 +817,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed_4x({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -787,6 +841,9 @@ test.register_message_test( message = mock_device:generate_test_message("button4", capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -828,6 +885,9 @@ test.register_message_test( } } -- no double event + }, + { + min_api_version = 19 } ) @@ -883,6 +943,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("button5", button_attr.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -902,7 +965,10 @@ test.register_coroutine_test( clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 10, {uint32(10)}) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -920,7 +986,10 @@ test.register_coroutine_test( ) expect_configure_buttons(mock_device) mock_device:expect_metadata_update({ profile = "5-button-batteryLevel" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -938,7 +1007,10 @@ test.register_coroutine_test( ) expect_configure_buttons(mock_device) mock_device:expect_metadata_update({ profile = "5-button-battery" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 index dca5f20645..071c897a35 100644 --- 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 @@ -195,6 +195,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) --should send initial press } +}, +{ + min_api_version = 19 } ) @@ -225,6 +228,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("button2", button_attr.pushed({state_change = true})) --should send initial press } +}, +{ + min_api_version = 19 } ) @@ -247,7 +253,10 @@ test.register_coroutine_test( ) }) test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.held({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -269,7 +278,10 @@ test.register_coroutine_test( ) }) test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -290,7 +302,10 @@ test.register_coroutine_test( mock_device, 50, {previous_position = 0} ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -327,7 +342,10 @@ test.register_coroutine_test( ) }) test.socket.capability:__expect_send(mock_device:generate_test_message("button6", button_attr.double({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -352,7 +370,10 @@ test.register_coroutine_test( mock_device, 40, {previous_position = 0} ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -368,7 +389,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -393,7 +417,10 @@ test.register_coroutine_test( mock_device, 60, {previous_position = 0} ) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -428,6 +455,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.held({state_change = true})) } +}, +{ + min_api_version = 19 } ) @@ -464,6 +494,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) }, + }, + { + min_api_version = 19 } ) @@ -509,6 +542,9 @@ test.register_message_test( ) } }, + }, + { + min_api_version = 19 } ) @@ -530,6 +566,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -551,6 +590,9 @@ test.register_message_test( message = mock_device:generate_test_message("button6", capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -572,6 +614,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "double", "pushed_3x", "pushed_4x", "pushed_5x", "pushed_6x"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -609,6 +654,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", button_attr.double({state_change = true})) }, +}, +{ + min_api_version = 19 } ) @@ -646,6 +694,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", button_attr.pushed_4x({state_change = true})) }, +}, +{ + min_api_version = 19 } ) @@ -667,6 +718,9 @@ test.register_message_test( message = mock_device:generate_test_message("button5", capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) }, + }, + { + min_api_version = 19 } ) @@ -708,6 +762,9 @@ test.register_message_test( } } -- no double event +}, +{ + min_api_version = 19 } ) @@ -763,6 +820,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("button6", button_attr.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) -- run the 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 db1ae04bfb..2cf394a356 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 @@ -280,6 +280,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -300,6 +303,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("button1", button_attr.pushed({state_change = true})) --should send initial press } +}, +{ + min_api_version = 19 } ) @@ -330,6 +336,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("button2", button_attr.pushed({state_change = true})) } +}, +{ + min_api_version = 19 } ) @@ -352,7 +361,10 @@ test.register_coroutine_test( ) }) test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -378,7 +390,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -440,7 +455,10 @@ test.register_coroutine_test( 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 } + { + test_init = test_init_mcd_unsupported_switch_device_type, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -457,7 +475,10 @@ test.register_coroutine_test( mock_child:expect_metadata_update({ profile = "light-color-level" }) mock_device:expect_metadata_update({ profile = "light-level-3-button" }) expect_configure_buttons() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -472,7 +493,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -483,7 +507,10 @@ test.register_coroutine_test( mock_child:expect_metadata_update({ profile = "light-color-level" }) mock_device:expect_metadata_update({ profile = "light-level-3-button" }) expect_configure_buttons() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -500,7 +527,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) -- run the tests diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua index de2afe3bd3..642610e88a 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua @@ -1,3 +1,6 @@ +-- Copyright 2026 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,7 +100,11 @@ test.register_coroutine_test("Read appropriate attribute values after tempOffset value = 20.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Read appropriate attribute values after humidityOffset preference change", function() local report = clusters.RelativeHumidityMeasurement.attributes.MeasuredValue:build_test_report_data(mock_device,2, 2000) @@ -116,7 +123,11 @@ test.register_coroutine_test("Read appropriate attribute values after humidityOf test.socket.capability:__expect_send(mock_device:generate_test_message("main",capabilities.relativeHumidityMeasurement.humidity({ value = 20 }))) -end) +end, +{ + min_api_version = 19 +} +) test.set_test_init_function(test_init) 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 6cdf530dbf..189d95d2e9 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -263,13 +263,19 @@ end test.register_message_test( "Test that Color Temperature Light device does not switch profiles", {}, - { test_init = test_init_color_temp } + { + test_init = test_init_color_temp, + min_api_version = 19 + } ) test.register_message_test( "Test that Extended Color Light device does not switch profiles", {}, - { test_init = test_init_extended_color } + { + test_init = test_init_extended_color, + min_api_version = 19 + } ) test.register_message_test( @@ -299,6 +305,9 @@ test.register_message_test( clusters.OnOff.server.commands.On(mock_device, 1) } } + }, + { + min_api_version = 19 } ) @@ -329,6 +338,9 @@ test.register_message_test( clusters.OnOff.server.commands.Off(mock_device, 1) } } + }, + { + min_api_version = 19 } ) @@ -410,6 +422,9 @@ test.register_message_test( } }, + }, + { + min_api_version = 19 } ) @@ -437,6 +452,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -483,7 +501,10 @@ test.register_coroutine_test( ) ) end, - { test_init = test_init_no_hue_sat } + { + test_init = test_init_no_hue_sat, + min_api_version = 19 + } ) local hue = math.floor((50 * 0xFE) / 100.0 + 0.5) @@ -566,6 +587,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "saturation" } } }, + }, + { + min_api_version = 19 } ) @@ -648,6 +672,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "saturation" } } }, + }, + { + min_api_version = 19 } ) @@ -672,6 +699,9 @@ test.register_message_test( clusters.ColorControl.server.commands.MoveToHue(mock_device, 1, hue, 0, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, + }, + { + min_api_version = 19 } ) @@ -694,6 +724,9 @@ test.register_message_test( clusters.ColorControl.server.commands.MoveToSaturation(mock_device, 1, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, + }, + { + min_api_version = 19 } ) @@ -737,6 +770,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) }, + }, + { + min_api_version = 19 } ) @@ -766,7 +802,10 @@ test.register_coroutine_test( ) ) end, - { test_init = test_init_x_y_color_mode } + { + test_init = test_init_x_y_color_mode, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -795,7 +834,10 @@ test.register_coroutine_test( ) ) end, - { test_init = test_init_x_y_color_mode } + { + test_init = test_init_x_y_color_mode, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -824,7 +866,10 @@ test.register_coroutine_test( ) ) end, - { test_init = test_init_x_y_color_mode } + { + test_init = test_init_x_y_color_mode, + min_api_version = 19 + } ) test.register_message_test( @@ -838,6 +883,9 @@ test.register_message_test( clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 0) } } + }, + { + min_api_version = 19 } ) @@ -865,6 +913,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 1800, maximum = 6500})) } + }, + { + min_api_version = 19 } ) @@ -892,6 +943,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 2800, maximum = 6000})) } + }, + { + min_api_version = 19 } ) @@ -945,6 +999,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(2800)) } + }, + { + min_api_version = 19 } ) @@ -1004,6 +1061,9 @@ test.register_message_test( clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 365, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } } + }, + { + min_api_version = 19 } ) @@ -1026,6 +1086,9 @@ test.register_message_test( clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 555) } } + }, + { + min_api_version = 19 } ) @@ -1048,6 +1111,9 @@ test.register_message_test( clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 1100) } } + }, + { + min_api_version = 19 } ) @@ -1075,6 +1141,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.levelRange({minimum = 2, maximum = 4})) } + }, + { + min_api_version = 19 } ) @@ -1097,6 +1166,9 @@ test.register_message_test( clusters.LevelControl.attributes.MaxLevel:build_test_report_data(mock_device, 1, 10) } } + }, + { + min_api_version = 19 } ) @@ -1152,7 +1224,10 @@ test.register_coroutine_test( } ) end, - { test_init = test_init_x_y_color_mode } + { + test_init = test_init_x_y_color_mode, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1194,7 +1269,10 @@ test.register_coroutine_test( "main", capabilities.colorControl.saturation(72) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1209,7 +1287,10 @@ test.register_coroutine_test( end test.socket.matter:__expect_send({mock_device.id, read_request}) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) 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 5513c915b7..b45219adca 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 @@ -674,91 +674,131 @@ test.register_coroutine_test( "Test profile change on init for onoff parent cluster as server", function() end, - { test_init = test_init_onoff } + { + test_init = test_init_onoff, + min_api_version = 19 + } ) test.register_coroutine_test( "Test profile change on init for dimmer parent cluster as server", function() end, - { test_init = test_init_dimmer } + { + test_init = test_init_dimmer, + min_api_version = 19 + } ) test.register_coroutine_test( "Test profile change on init for color dimmer parent cluster as server", function() end, - { test_init = test_init_color_dimmer } + { + test_init = test_init_color_dimmer, + min_api_version = 19 + } ) test.register_coroutine_test( "Test init for onoff parent cluster as client", function() end, - { test_init = test_init_onoff_client } + { + test_init = test_init_onoff_client, + min_api_version = 19 + } ) 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_init = test_init_switch_vendor_override, + min_api_version = 19 + } ) test.register_coroutine_test( "Test init for mounted onoff control parent cluster as server", function() end, - { test_init = test_init_mounted_on_off_control } + { + test_init = test_init_mounted_on_off_control, + min_api_version = 19 + } ) test.register_coroutine_test( "Test init for mounted dimmable load control parent cluster as server", function() end, - { test_init = test_init_mounted_dimmable_load_control } + { + test_init = test_init_mounted_dimmable_load_control, + min_api_version = 19 + } ) test.register_coroutine_test( "Test profile change on init for water valve parent cluster as server", function() end, - { test_init = test_init_water_valve } + { + test_init = test_init_water_valve, + min_api_version = 19 + } ) test.register_coroutine_test( "Test profile change on init for onoff parent cluster as client and onoff child as server", function() end, - { test_init = test_init_parent_client_child_server } + { + test_init = test_init_parent_client_child_server, + min_api_version = 19 + } ) test.register_coroutine_test( "Test profile change on init for onoff device when parent and child are both server", function() end, - { test_init = test_init_parent_child_switch_types } + { + test_init = test_init_parent_child_switch_types, + min_api_version = 19 + } ) test.register_coroutine_test( "Test child device attribute subscriptions when parent device has clusters that are not a superset of child device clusters", function() end, - { test_init = test_init_parent_child_different_types } + { + test_init = test_init_parent_child_different_types, + min_api_version = 19 + } ) test.register_coroutine_test( "Test child device attributes not subscribed to for unsupported device type for child device", function() end, - { test_init = test_init_parent_child_unsupported_device_type } + { + test_init = test_init_parent_child_unsupported_device_type, + min_api_version = 19 + } ) test.register_coroutine_test( "Test init for light with motion sensor", function() end, - { test_init = test_init_light_level_motion } + { + test_init = test_init_light_level_motion, + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + 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 da74443896..03cf8ee407 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 @@ -85,6 +85,9 @@ test.register_message_test( clusters.ValveConfigurationAndControl.server.commands.Open(mock_device, 1) } } + }, + { + min_api_version = 19 } ) @@ -107,6 +110,9 @@ test.register_message_test( clusters.ValveConfigurationAndControl.server.commands.Close(mock_device, 1) } } + }, + { + min_api_version = 19 } ) @@ -129,6 +135,9 @@ test.register_message_test( clusters.ValveConfigurationAndControl.server.commands.Open(mock_device, 1, nil, 25) } } + }, + { + min_api_version = 19 } ) @@ -151,6 +160,9 @@ test.register_message_test( clusters.ValveConfigurationAndControl.server.commands.Close(mock_device, 1) } } + }, + { + min_api_version = 19 } ) @@ -170,6 +182,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.closed()) }, + }, + { + min_api_version = 19 } ) @@ -189,6 +204,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.open()) }, + }, + { + min_api_version = 19 } ) @@ -208,6 +226,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.open()) }, + }, + { + min_api_version = 19 } ) @@ -227,6 +248,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.level.level(50)) }, + }, + { + min_api_version = 19 } ) 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 774e6bfa52..2fabc37408 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 @@ -204,7 +204,10 @@ test.register_message_test( } } }, - { test_init = test_init_mock_3switch } + { + test_init = test_init_mock_3switch, + min_api_version = 19 + } ) -- The custom "test_init" function also checks that the appropriate profile is switched on init @@ -228,7 +231,10 @@ test.register_message_test( } } }, - { test_init = test_init_mock_2switch } + { + test_init = test_init_mock_2switch, + min_api_version = 19 + } ) -- The custom "test_init" function also checks that the appropriate profile is switched on init @@ -252,7 +258,11 @@ test.register_message_test( } } }, - { test_init = test_init_mock_3switch_non_sequential } + { + test_init = test_init_mock_3switch_non_sequential, + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + 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 be01a7bf32..efbb7c4545 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 @@ -348,6 +348,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -399,6 +402,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -450,6 +456,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -477,6 +486,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -520,6 +532,9 @@ test.register_message_test( direction = "send", message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) }, + }, + { + min_api_version = 19 } ) @@ -552,6 +567,9 @@ test.register_message_test( direction = "send", message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorControl.saturation(72)) } + }, + { + min_api_version = 19 } ) @@ -608,6 +626,9 @@ test.register_message_test( direction = "send", message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorControl.saturation(72)) } + }, + { + min_api_version = 19 } ) @@ -656,6 +677,9 @@ test.register_message_test( direction = "send", message = mock_children[child2_ep]:generate_test_message("main", capabilities.switchLevel.levelRange({minimum = 50, maximum = 80})) } + }, + { + min_api_version = 19 } ) @@ -683,6 +707,9 @@ test.register_message_test( direction = "send", message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 1800, maximum = 6500})) } + }, + { + min_api_version = 19 } ) @@ -690,7 +717,10 @@ test.register_coroutine_test( "Test child devices are created in order of their endpoints", function() end, - { test_init = test_init_parent_child_endpoints_non_sequential } + { + test_init = test_init_parent_child_endpoints_non_sequential, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -700,7 +730,11 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + 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 2370984c97..edbc775df8 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 @@ -341,6 +341,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -392,6 +395,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -443,6 +449,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -470,6 +479,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -513,6 +525,9 @@ test.register_message_test( direction = "send", message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) }, + }, + { + min_api_version = 19 } ) @@ -545,6 +560,9 @@ test.register_message_test( direction = "send", message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorControl.saturation(72)) } + }, + { + min_api_version = 19 } ) @@ -601,6 +619,9 @@ test.register_message_test( direction = "send", message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorControl.saturation(72)) } + }, + { + min_api_version = 19 } ) @@ -649,6 +670,9 @@ test.register_message_test( direction = "send", message = mock_children[child2_ep]:generate_test_message("main", capabilities.switchLevel.levelRange({minimum = 50, maximum = 80})) } + }, + { + min_api_version = 19 } ) @@ -676,6 +700,9 @@ test.register_message_test( direction = "send", message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 1800, maximum = 6500})) } + }, + { + min_api_version = 19 } ) @@ -683,7 +710,11 @@ test.register_coroutine_test( "Test child devices are created in order of their endpoints", function() end, - { test_init = test_init_parent_child_endpoints_non_sequential } + { + test_init = test_init_parent_child_endpoints_non_sequential, + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + diff --git a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua index a8a8e83b4b..0c5f19f597 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua @@ -102,6 +102,9 @@ test.register_message_test( clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.UP, 467, fields.TRANSITION_TIME_FAST, fields.COLOR_TEMPERATURE_MIRED_MIN, fields.COLOR_TEMPERATURE_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) }, } + }, + { + min_api_version = 19 } ) @@ -157,6 +160,9 @@ test.register_message_test( clusters.LevelControl.server.commands.Step(mock_device_color_temp, 1, clusters.LevelControl.types.StepModeEnum.UP, 254, fields.TRANSITION_TIME_FAST, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) }, } + }, + { + min_api_version = 19 } ) 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 0a72a1316e..77c7d7843c 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 @@ -232,7 +232,10 @@ test.register_coroutine_test( mock_device:generate_test_message(key == 1 and "main" or "F" .. key, capabilities.button.button.pushed({state_change = true})) ) end - end + end, + { + min_api_version = 19 + } ) -- run the tests 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 ca7fddcdd9..8c0ddad122 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 @@ -430,7 +430,10 @@ test.register_coroutine_test( mock_device_ap_aqs:expect_metadata_update({ profile = "air-purifier-hepa-ac-aqs-co2-tvoc-meas-co2-radon-level" }) mock_device_ap_aqs:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, - { test_init = test_init_ap_aqs } + { + test_init = test_init_ap_aqs, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -443,7 +446,10 @@ test.register_coroutine_test( mock_device_ap_thermo_aqs:expect_metadata_update({ provisioning_state = "PROVISIONED" }) print(mock_device_ap_thermo_aqs.profile) end, - { test_init = test_init_ap_thermo_aqs } + { + test_init = test_init_ap_thermo_aqs, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -465,7 +471,10 @@ test.register_coroutine_test( mock_device_ap_thermo_aqs_preconfigured:generate_test_message("main", capabilities.formaldehydeMeasurement.formaldehydeLevel({value = 14, unit = "ppm"})) ) end, - { test_init = test_init_ap_thermo_aqs_preconfigured } + { + test_init = test_init_ap_thermo_aqs_preconfigured, + min_api_version = 19 + } ) test.register_message_test( @@ -519,6 +528,9 @@ test.register_message_test( clusters.FanControl.attributes.FanMode:write(mock_device, 1, clusters.FanControl.attributes.FanMode.AUTO) } } + }, + { + min_api_version = 19 } ) @@ -560,6 +572,9 @@ test.register_message_test( capabilities.airPurifierFanMode.airPurifierFanMode.high.NAME }, {visibility={displayed=false}})) }, + }, + { + min_api_version = 19 } ) @@ -595,6 +610,9 @@ test.register_message_test( clusters.FanControl.attributes.PercentSetting:write(mock_device, 1, 50) } } + }, + { + min_api_version = 19 } ) @@ -640,6 +658,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.airPurifierFanMode.airPurifierFanMode.high()) }, + }, + { + min_api_version = 19 } ) @@ -698,6 +719,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("activatedCarbonFilter", capabilities.filterStatus.filterStatus.replace()) }, + }, + { + min_api_version = 19 } ) @@ -751,6 +775,9 @@ test.register_message_test( clusters.FanControl.attributes.WindSetting:write(mock_device, 1, clusters.FanControl.types.WindSettingMask.NATURAL_WIND) } } + }, + { + min_api_version = 19 } ) @@ -770,6 +797,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.fanSpeedPercent.percent(100)) }, + }, + { + min_api_version = 19 } ) @@ -838,6 +868,9 @@ test.register_message_test( clusters.FanControl.attributes.RockSetting:write(mock_device_rock, 1, clusters.FanControl.types.RockBitmap.ROCK_UP_DOWN) } } + }, + { + min_api_version = 19 } ) @@ -853,7 +886,10 @@ test.register_coroutine_test( clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device_ap_thermo_aqs_preconfigured, 7, 2100) }) end, - { test_init = test_init_ap_thermo_aqs_preconfigured } + { + test_init = test_init_ap_thermo_aqs_preconfigured, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -876,7 +912,10 @@ test.register_coroutine_test( clusters.ActivatedCarbonFilterMonitoring.server.commands.ResetCondition(mock_device_ap_thermo_aqs_preconfigured, 1) }) end, - { test_init = test_init_ap_thermo_aqs_preconfigured } + { + test_init = test_init_ap_thermo_aqs_preconfigured, + min_api_version = 19 + } ) test.run_registered_tests() 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 fd68c958d9..d16833eda1 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 @@ -430,7 +430,10 @@ test.register_coroutine_test( mock_device_ap_aqs:expect_metadata_update({ profile = "air-purifier-hepa-ac-aqs-co2-tvoc-meas-co2-radon-level" }) mock_device_ap_aqs:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, - { test_init = test_init_ap_aqs } + { + test_init = test_init_ap_aqs, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -443,7 +446,10 @@ test.register_coroutine_test( mock_device_ap_thermo_aqs:expect_metadata_update({ provisioning_state = "PROVISIONED" }) print(mock_device_ap_thermo_aqs.profile) end, - { test_init = test_init_ap_thermo_aqs } + { + test_init = test_init_ap_thermo_aqs, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -465,7 +471,10 @@ test.register_coroutine_test( mock_device_ap_thermo_aqs_preconfigured:generate_test_message("main", capabilities.formaldehydeMeasurement.formaldehydeLevel({value = 14, unit = "ppm"})) ) end, - { test_init = test_init_ap_thermo_aqs_preconfigured } + { + test_init = test_init_ap_thermo_aqs_preconfigured, + min_api_version = 19 + } ) test.register_message_test( @@ -519,6 +528,9 @@ test.register_message_test( clusters.FanControl.attributes.FanMode:write(mock_device, 1, clusters.FanControl.attributes.FanMode.AUTO) } } + }, + { + min_api_version = 19 } ) @@ -560,6 +572,9 @@ test.register_message_test( capabilities.airPurifierFanMode.airPurifierFanMode.high.NAME }, {visibility={displayed=false}})) }, + }, + { + min_api_version = 19 } ) @@ -595,6 +610,9 @@ test.register_message_test( clusters.FanControl.attributes.PercentSetting:write(mock_device, 1, 50) } } + }, + { + min_api_version = 19 } ) @@ -640,6 +658,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.airPurifierFanMode.airPurifierFanMode.high()) } + }, + { + min_api_version = 19 } ) @@ -698,6 +719,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("activatedCarbonFilter", capabilities.filterStatus.filterStatus.replace()) } + }, + { + min_api_version = 19 } ) @@ -752,6 +776,9 @@ test.register_message_test( clusters.FanControl.attributes.WindSetting:write(mock_device, 1, clusters.FanControl.types.WindSettingMask.NATURAL_WIND) } } + }, + { + min_api_version = 19 } ) @@ -771,6 +798,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.fanSpeedPercent.percent(100)) } + }, + { + min_api_version = 19 } ) @@ -840,6 +870,9 @@ test.register_message_test( clusters.FanControl.attributes.RockSetting:write(mock_device_rock, 1, clusters.FanControl.types.RockBitmap.ROCK_UP_DOWN) } } + }, + { + min_api_version = 19 } ) @@ -855,7 +888,10 @@ test.register_coroutine_test( clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device_ap_thermo_aqs_preconfigured, 7, 2100) }) end, - { test_init = test_init_ap_thermo_aqs_preconfigured } + { + test_init = test_init_ap_thermo_aqs_preconfigured, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -878,7 +914,10 @@ test.register_coroutine_test( clusters.ActivatedCarbonFilterMonitoring.server.commands.ResetCondition(mock_device_ap_thermo_aqs_preconfigured, 1) }) end, - { test_init = test_init_ap_thermo_aqs_preconfigured } + { + test_init = test_init_ap_thermo_aqs_preconfigured, + min_api_version = 19 + } ) test.run_registered_tests() 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 745076cd04..7ee1a966d4 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 @@ -315,7 +315,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_device_basic:generate_info_changed({ profile = updated_device_profile })) test.socket.matter:__expect_send({mock_device_basic.id, subscribe_request}) end, - { test_init = test_init_basic } + { + test_init = test_init_basic, + min_api_version = 19 + } ) local expected_update_metadata= { @@ -389,7 +392,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_device_ap_thermo_aqs:generate_info_changed({ profile = updated_device_profile })) test.socket.matter:__expect_send({mock_device_ap_thermo_aqs.id, subscribe_request}) end, - { test_init = test_init_ap_thermo_aqs_preconfigured } + { + test_init = test_init_ap_thermo_aqs_preconfigured, + min_api_version = 19 + } ) test.run_registered_tests() 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 8eefceb23b..975a192d9d 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua @@ -111,7 +111,10 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "fan-rock-wind" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, - { test_init = test_init } + { + test_init = test_init, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -123,7 +126,10 @@ test.register_coroutine_test( mock_device_generic:expect_metadata_update({ profile = "fan-generic" }) mock_device_generic:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, - { test_init = test_init_generic } + { + test_init = test_init_generic, + min_api_version = 19 + } ) test.run_registered_tests() 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 e96bfb69fc..8bfd026c24 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 @@ -146,7 +146,10 @@ test.register_coroutine_test( local component_to_endpoint_map = mock_device:get_field("__component_to_endpoint_map") assert(component_to_endpoint_map["thermostatOne"] == THERMOSTAT_ONE_EP, string.format("Thermostat One Endpoint must be %d", THERMOSTAT_ONE_EP)) assert(component_to_endpoint_map["thermostatTwo"] == THERMOSTAT_TWO_EP, string.format("Thermostat Two Endpoint must be %d", THERMOSTAT_TWO_EP)) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -188,6 +191,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 23.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -230,6 +236,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 19.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -268,6 +277,9 @@ test.register_message_test( clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, THERMOSTAT_TWO_EP, 25*100) } } + }, + { + min_api_version = 19 } ) @@ -306,6 +318,9 @@ test.register_message_test( clusters.Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, THERMOSTAT_TWO_EP , 13*100) } } + }, + { + min_api_version = 19 } ) @@ -370,6 +385,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.thermostatMode.heat()) }, + }, + { + min_api_version = 19 } ) @@ -455,6 +473,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool"}, {visibility={displayed=false}})) }, + }, + { + min_api_version = 19 } ) @@ -523,6 +544,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.thermostatMode.emergency_heat()) }, + }, + { + min_api_version = 19 } ) @@ -587,7 +611,10 @@ test.register_message_test( message = mock_device_with_auto:generate_test_message("thermostatTwo", capabilities.thermostatMode.thermostatMode.emergency_heat()) }, }, - { test_init = test_init_auto } + { + test_init = test_init_auto, + min_api_version = 19 + } ) test.register_message_test( @@ -608,6 +635,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 15.0, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -630,6 +660,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 15, unit = "Wh" })) } + }, + { + min_api_version = 19 } ) @@ -676,7 +709,8 @@ test.register_coroutine_test( { test_init = function() test_init() - end + end, + min_api_version = 19 } ) @@ -728,7 +762,8 @@ test.register_coroutine_test( { test_init = function() test_init() - end + end, + min_api_version = 19 } ) 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 bb7b9e6bde..6dfe993c27 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 @@ -294,7 +294,10 @@ test.register_coroutine_test( ) mock_device_configure:expect_metadata_update({ profile = "room-air-conditioner" }) end, - { test_init = test_init_configure } + { + test_init = test_init_configure, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -312,7 +315,10 @@ test.register_coroutine_test( ) mock_device_nostate:expect_metadata_update({ profile = "room-air-conditioner-fan-heating-cooling-nostate" }) end, - { test_init = test_init_nostate } + { + test_init = test_init_nostate, + min_api_version = 19 + } ) test.register_message_test( @@ -347,6 +353,9 @@ test.register_message_test( clusters.FanControl.attributes.PercentSetting:write(mock_device, 1, 50) } } + }, + { + min_api_version = 19 } ) @@ -400,6 +409,9 @@ test.register_message_test( clusters.FanControl.attributes.WindSetting:write(mock_device, 1, clusters.FanControl.types.WindSettingMask.NATURAL_WIND) } } + }, + { + min_api_version = 19 } ) @@ -445,6 +457,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.airConditionerFanMode.fanMode("high")) } + }, + { + min_api_version = 19 } ) @@ -478,6 +493,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.airConditionerFanMode.supportedAcFanModes({"off", "low", "medium", "high", "auto"}, {visibility={displayed=false}})) } + }, + { + min_api_version = 19 } ) @@ -524,6 +542,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"cool"}, {visibility={displayed=false}})) }, + }, + { + min_api_version = 19 } ) @@ -561,6 +582,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.emergency_heat()) } + }, + { + min_api_version = 19 } ) 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 be240c95d0..6ecbe049aa 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 @@ -312,7 +312,10 @@ test.register_coroutine_test( function() test_room_ac_device_type_update_modular_profile(mock_device_basic, expected_metadata_basic, subscribe_request_basic, uint32(0x29)) end, - { test_init = test_init_basic } + { + test_init = test_init_basic, + min_api_version = 19 + } ) local expected_metadata_no_state = { @@ -337,6 +340,9 @@ test.register_coroutine_test( function() test_room_ac_device_type_update_modular_profile(mock_device_no_state, expected_metadata_no_state, subscribe_request_no_state, uint32(0)) end, - { test_init = test_init_no_state } + { + test_init = test_init_no_state, + min_api_version = 19 + } ) test.run_registered_tests() 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 a28882ca46..bb9c637714 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 @@ -105,7 +105,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ profile = "thermostat-cooling-only-nostate" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -124,7 +127,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ profile = "thermostat-cooling-only-nostate-batteryLevel" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -136,7 +142,10 @@ test.register_coroutine_test( clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, {uint32(10)}) } ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 2db6e20e59..328ea848cf 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 @@ -226,7 +226,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ profile = "thermostat-humidity-fan-heating-only" }) -end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -247,7 +250,10 @@ test.register_coroutine_test( } } test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) -end +end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -269,7 +275,10 @@ test.register_coroutine_test( } ) mock_device_simple:expect_metadata_update({ profile = "thermostat-cooling-only-nostate" }) -end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -283,7 +292,10 @@ test.register_coroutine_test( } ) mock_device_no_battery:expect_metadata_update({ profile = "thermostat-cooling-only-nostate-nobattery" }) -end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 90e529beb3..e9f92b6d2c 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 @@ -238,7 +238,10 @@ test.register_coroutine_test( function() test_thermostat_device_type_update_modular_profile(mock_device, expected_metadata, get_subscribe_request(mock_device, new_cluster_subscribe_list)) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -247,7 +250,10 @@ test.register_coroutine_test( test_thermostat_device_type_update_modular_profile(mock_device_disorder_endpoints, expected_metadata, get_subscribe_request(mock_device_disorder_endpoints, new_cluster_subscribe_list)) end, - { test_init = test_init_disorder_endpoints } + { + test_init = test_init_disorder_endpoints, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -278,7 +284,10 @@ test.register_coroutine_test( mock_device.id, clusters.FanControl.attributes.PercentSetting:write(mock_device, 2, 50) }) - end + end, + { + min_api_version = 19 + } ) 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 74e550b344..6f8f7d6412 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 @@ -148,7 +148,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", cached_heating_setpoint) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -174,7 +177,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", cached_cooling_setpoint) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -200,7 +206,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", cached_heating_setpoint) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -226,7 +235,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", cached_cooling_setpoint) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -245,7 +257,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", cached_heating_setpoint) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -264,7 +279,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", cached_cooling_setpoint) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -277,7 +295,10 @@ test.register_coroutine_test( test.wait_for_events() local min_setpoint_deadband_checked = mock_device:get_field("MIN_SETPOINT_DEADBAND_CHECKED") assert(min_setpoint_deadband_checked == true, "min_setpoint_deadband_checked is True") - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -304,6 +325,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpointRange({ value = { minimum = 10.00, maximum = 32.22, step = 0.1 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -331,6 +355,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpointRange({ value = { minimum = 10.00, maximum = 32.22, step = 0.1 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -358,6 +385,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 5.00, maximum = 39.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) 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 13eaa47bfe..3163303942 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 @@ -99,6 +99,9 @@ test.register_message_test( clusters.Thermostat.attributes.AbsMaxCoolSetpointLimit:build_test_report_data(mock_device, 1, 3222) } } + }, + { + min_api_version = 19 } ) @@ -121,6 +124,9 @@ test.register_message_test( clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit:build_test_report_data(mock_device, 1, 3222) } } + }, + { + min_api_version = 19 } ) @@ -143,7 +149,11 @@ test.register_message_test( clusters.TemperatureMeasurement.attributes.MaxMeasuredValue:build_test_report_data(mock_device, 1, 4000) } } + }, + { + min_api_version = 19 } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + 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 3eed9709a2..4125e20649 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat.lua @@ -167,6 +167,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 40 })) } + }, + { + min_api_version = 19 } ) @@ -186,6 +189,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -205,6 +211,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -229,6 +238,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -253,6 +265,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -272,6 +287,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState.cooling()) } + }, + { + min_api_version = 19 } ) @@ -291,6 +309,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState.heating()) } + }, + { + min_api_version = 19 } ) @@ -310,6 +331,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState.fan_only()) } + }, + { + min_api_version = 19 } ) @@ -329,6 +353,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState.idle()) } + }, + { + min_api_version = 19 } ) @@ -363,6 +390,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.cool()) }, + }, + { + min_api_version = 19 } ) @@ -409,6 +439,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"off", "cool"}, {visibility={displayed=false}})) }, + }, + { + min_api_version = 19 } ) @@ -455,7 +488,10 @@ test.register_message_test( message = mock_device_auto:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "auto"}, {visibility={displayed=false}})) }, }, - { test_init = test_init_auto } + { + test_init = test_init_auto, + min_api_version = 19 + } ) test.register_message_test( @@ -492,6 +528,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.emergency_heat()) } + }, + { + min_api_version = 19 } ) @@ -530,7 +569,10 @@ test.register_message_test( message = mock_device_auto:generate_test_message("main", capabilities.thermostatMode.thermostatMode.emergency_heat()) } }, - { test_init = test_init_auto } + { + test_init = test_init_auto, + min_api_version = 19 + } ) test.register_message_test( @@ -579,7 +621,10 @@ test.register_message_test( } }, }, - { test_init = test_init_auto } + { + test_init = test_init_auto, + min_api_version = 19 + } ) local FanMode = clusters.FanControl.attributes.FanMode @@ -633,6 +678,9 @@ test.register_message_test( FanMode:build_test_report_data(mock_device, 1, FanMode.OFF) } } + }, + { + min_api_version = 19 } ) @@ -666,6 +714,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatFanMode.supportedThermostatFanModes({"auto", "on"}, {visibility={displayed=false}})) }, + }, + { + min_api_version = 19 } ) @@ -688,6 +739,9 @@ test.register_message_test( clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 1, 15*100) } } + }, + { + min_api_version = 19 } ) @@ -710,6 +764,9 @@ test.register_message_test( clusters.Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, 1, 25*100) } } + }, + { + min_api_version = 19 } ) @@ -732,6 +789,9 @@ test.register_message_test( clusters.Thermostat.attributes.SystemMode:write(mock_device, 1, 3) } } + }, + { + min_api_version = 19 } ) @@ -770,6 +830,9 @@ test.register_message_test( FanMode:write(mock_device, 1, FanMode.ON) } }, + }, + { + min_api_version = 19 } ) @@ -792,6 +855,9 @@ test.register_message_test( clusters.FanControl.attributes.FanMode:write(mock_device, 1, 5) } } + }, + { + min_api_version = 19 } ) @@ -810,7 +876,11 @@ test.register_coroutine_test("Battery percent reports should generate correct me ) ) test.wait_for_events() -end) +end, +{ + min_api_version = 19 +} +) local refresh_request = nil local attribute_refresh_list = { @@ -861,6 +931,9 @@ test.register_message_test( refresh_request } } + }, + { + min_api_version = 19 } ) 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 ef85b147af..9e38f59b09 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 @@ -96,6 +96,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 40 })) } + }, + { + min_api_version = 19 } ) @@ -116,6 +119,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -136,6 +142,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -162,6 +171,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -188,6 +200,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 40.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -208,6 +223,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState.cooling()) } + }, + { + min_api_version = 19 } ) @@ -228,6 +246,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState.heating()) } + }, + { + min_api_version = 19 } ) @@ -248,6 +269,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState.fan_only()) } + }, + { + min_api_version = 19 } ) @@ -268,6 +292,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState.idle()) } + }, + { + min_api_version = 19 } ) @@ -300,6 +327,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.cool()) }, + }, + { + min_api_version = 19 } ) @@ -350,6 +380,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({ "off", "cool" }, {visibility={displayed=false}})) }, + }, + { + min_api_version = 19 } ) @@ -389,6 +422,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.emergency_heat()) }, + }, + { + min_api_version = 19 } ) @@ -436,6 +472,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.thermostatFanMode.thermostatFanMode.on()) }, + }, + { + min_api_version = 19 } ) @@ -471,6 +510,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.thermostatFanMode.supportedThermostatFanModes({ "auto", "on" }, {visibility={displayed=false}})) }, + }, + { + min_api_version = 19 } ) @@ -493,6 +535,9 @@ test.register_message_test( clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 3, 15 * 100) } } + }, + { + min_api_version = 19 } ) @@ -515,6 +560,9 @@ test.register_message_test( clusters.Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, 3, 25 * 100) } } + }, + { + min_api_version = 19 } ) @@ -537,6 +585,9 @@ test.register_message_test( clusters.Thermostat.attributes.SystemMode:write(mock_device, 3, 3) } } + }, + { + min_api_version = 19 } ) @@ -575,6 +626,9 @@ test.register_message_test( FanMode:write(mock_device, 3, FanMode.ON) } }, + }, + { + min_api_version = 19 } ) @@ -597,6 +651,9 @@ test.register_message_test( clusters.FanControl.attributes.FanMode:write(mock_device, 3, 5) } } + }, + { + min_api_version = 19 } ) @@ -615,7 +672,11 @@ test.register_coroutine_test("Battery percent reports should generate correct me ) ) test.wait_for_events() -end) +end, +{ + min_api_version = 19 +} +) local refresh_request = nil local attribute_refresh_list = { @@ -666,6 +727,9 @@ test.register_message_test( refresh_request } } + }, + { + min_api_version = 19 } ) 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 bf985671cf..6e034beba1 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 @@ -140,7 +140,10 @@ test.register_coroutine_test( function() test_thermostat_device_type_update_modular_profile(mock_device_basic, expected_metadata, subscribe_request_basic) end, - { test_init = test_init } + { + test_init = test_init, + min_api_version = 19 + } ) -- run tests 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 a9bbf59930..0ae634cbc4 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 @@ -135,6 +135,9 @@ test.register_message_test( clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 1, 35 * 100) } } + }, + { + min_api_version = 19 } ) 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 b39db4136b..552715c3bf 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 @@ -115,6 +115,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 70.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -137,6 +140,9 @@ test.register_message_test( clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, WATER_HEATER_EP, 80*100) } } + }, + { + min_api_version = 19 } ) @@ -196,6 +202,9 @@ test.register_message_test( clusters.WaterHeaterMode.commands.ChangeToMode(mock_device, WATER_HEATER_EP, 0) -- Index where Mode 1 is stored) } } + }, + { + min_api_version = 19 } ) @@ -225,6 +234,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -247,6 +259,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 15, unit = "Wh" })) } + }, + { + min_api_version = 19 } ) @@ -315,7 +330,8 @@ test.register_coroutine_test( { test_init = function() test_init() - end + end, + min_api_version = 19 } ) @@ -415,6 +431,9 @@ test.register_message_test( clusters.WaterHeaterMode.commands.ChangeToMode(mock_device, WATER_HEATER_EP, 0) -- Index is Water Heater Mode 1 } } + }, + { + min_api_version = 19 } ) 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 409ebfcb09..20f01b6f99 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 @@ -187,7 +187,10 @@ test.register_coroutine_test( WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -217,7 +220,10 @@ test.register_coroutine_test( WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -247,7 +253,10 @@ test.register_coroutine_test( "main", capabilities.windowShade.windowShade.closed() ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -277,7 +286,10 @@ test.register_coroutine_test( "main", capabilities.windowShade.windowShade.closed() ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -307,7 +319,10 @@ test.register_coroutine_test( WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -337,7 +352,10 @@ test.register_coroutine_test( WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -367,7 +385,10 @@ test.register_coroutine_test( ), } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -397,7 +418,10 @@ test.register_coroutine_test( ), } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -427,7 +451,10 @@ test.register_coroutine_test( WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -457,7 +484,10 @@ test.register_coroutine_test( WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -487,7 +517,10 @@ test.register_coroutine_test( "main", capabilities.windowShade.windowShade.partially_open() ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -517,7 +550,10 @@ test.register_coroutine_test( "main", capabilities.windowShade.windowShade.partially_open() ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test("WindowCovering OperationalStatus opening", function() @@ -551,7 +587,11 @@ test.register_coroutine_test("WindowCovering OperationalStatus opening", functio "main", capabilities.windowShade.windowShade.opening() ) ) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("WindowCovering OperationalStatus closing", function() test.socket.capability:__set_channel_ordering("relaxed") @@ -584,7 +624,11 @@ test.register_coroutine_test("WindowCovering OperationalStatus closing", functio "main", capabilities.windowShade.windowShade.closing() ) ) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("WindowCovering OperationalStatus unknown", function() test.socket.capability:__set_channel_ordering("relaxed") @@ -617,7 +661,11 @@ test.register_coroutine_test("WindowCovering OperationalStatus unknown", functio "main", capabilities.windowShade.windowShade.unknown() ) ) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test( "WindowShade open cmd handler", function() @@ -631,7 +679,10 @@ test.register_coroutine_test( {mock_device.id, WindowCovering.server.commands.UpOrOpen(mock_device, 10)} ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -646,7 +697,10 @@ test.register_coroutine_test( {mock_device.id, WindowCovering.server.commands.DownOrClose(mock_device, 10)} ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -661,7 +715,10 @@ test.register_coroutine_test( {mock_device.id, WindowCovering.server.commands.StopMotion(mock_device, 10)} ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -690,7 +747,10 @@ test.register_coroutine_test( end test.socket.matter:__expect_send({mock_device.id, read_request}) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test("WindowShade setShadeLevel cmd handler", function() @@ -703,7 +763,11 @@ test.register_coroutine_test("WindowShade setShadeLevel cmd handler", function() test.socket.matter:__expect_send( {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 8000)} ) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("WindowShade setShadeTiltLevel cmd handler", function() test.socket.capability:__queue_receive( @@ -715,7 +779,11 @@ test.register_coroutine_test("WindowShade setShadeTiltLevel cmd handler", functi test.socket.matter:__expect_send( {mock_device.id, WindowCovering.server.commands.GoToTiltPercentage(mock_device, 10, 4000)} ) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("LevelControl CurrentLevel handler", function() test.socket.matter:__queue_receive( @@ -729,7 +797,11 @@ test.register_coroutine_test("LevelControl CurrentLevel handler", function() "main", capabilities.windowShadeLevel.shadeLevel(math.floor((100 / 254.0 * 100) + .5)) ) ) -end) +end, +{ + min_api_version = 19 +} +) --test battery test.register_coroutine_test( @@ -747,7 +819,10 @@ test.register_coroutine_test( "main", capabilities.battery.battery(math.floor(150/2.0+0.5)) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test("OperationalStatus report contains current position report", function() @@ -767,7 +842,11 @@ test.register_coroutine_test("OperationalStatus report contains current position "main", capabilities.windowShade.windowShade.partially_open() ) ) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test( "Handle preset commands", @@ -789,7 +868,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send( {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, (100 - PRESET_LEVEL) * 100)} ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -802,7 +884,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ profile = "window-covering-tilt-battery" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -814,14 +899,20 @@ test.register_coroutine_test( clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 10, {uint32(10)}) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Test mains powered device does not switch to battery profile", function() end, - { test_init = test_init_mains_powered } + { + test_init = test_init_mains_powered, + min_api_version = 19 + } ) test.register_coroutine_test( @@ -833,7 +924,10 @@ test.register_coroutine_test( test.wait_for_events() test.socket.matter:__queue_receive({mock_device.id, clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 10, {uint32(0x0C)})}) mock_device:expect_metadata_update({profile = "window-covering-tilt-battery"}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -942,7 +1036,10 @@ test.register_coroutine_test( "main", capabilities.windowShade.windowShade.partially_open() ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1013,7 +1110,10 @@ test.register_coroutine_test( "main", capabilities.windowShade.windowShade.partially_open() ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1068,7 +1168,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send( {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 0)} ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1123,7 +1226,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send( {mock_device.id, WindowCovering.server.commands.GoToTiltPercentage(mock_device, 10, 10000)} ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua b/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua index 651668297b..00264417b1 100644 --- a/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua +++ b/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua @@ -1,3 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" local capabilities = require "st.capabilities" @@ -41,6 +44,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -57,6 +63,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("main", capabilities.switch.switch.on({state_change=true})) } + }, + { + min_api_version = 19 } ) @@ -73,6 +82,9 @@ test.register_message_test( direction = "send", message = mock_device_no_prefs:generate_test_message("main", capabilities.switch.switch.on({state_change=true})) } + }, + { + min_api_version = 19 } ) @@ -90,6 +102,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("main", capabilities.switch.switch.off({state_change=true})) } + }, + { + min_api_version = 19 } ) @@ -106,6 +121,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("main", capabilities.switch.switch.off({state_change=true})) } + }, + { + min_api_version = 19 } ) @@ -128,6 +146,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -146,7 +167,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_simple_device.id, { capability = "switch", component = "main", command = "on", args = {} } }) test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.switch.switch.on())) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua b/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua index 09f539b53b..73920bcd34 100755 --- a/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua +++ b/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua @@ -71,7 +71,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, read_tvoc_messge}) test.socket.zigbee:__expect_send({mock_device.id, read_carbonDioxide_messge}) test.socket.zigbee:__expect_send({mock_device.id, read_AQI_messge}) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -90,6 +93,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 40 })) } + }, + { + min_api_version = 19 } ) @@ -109,6 +115,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C"})) } + }, + { + min_api_version = 19 } ) @@ -126,7 +135,10 @@ test.register_coroutine_test( capabilities.carbonDioxideMeasurement.carbonDioxide({value = 1400, unit = "ppm"}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "good"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -143,7 +155,10 @@ test.register_coroutine_test( capabilities.fineDustSensor.fineDustLevel({value = 74 }))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fineDustHealthConcern.fineDustHealthConcern.good())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -160,7 +175,10 @@ test.register_coroutine_test( capabilities.veryFineDustSensor.veryFineDustLevel({value = 69 }))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern.good())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -177,7 +195,10 @@ test.register_coroutine_test( capabilities.dustSensor.dustLevel({value = 69 }))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.dustHealthConcern.dustHealthConcern.good())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -192,7 +213,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.formaldehydeMeasurement.formaldehydeLevel({value = 1000.0, unit = "mg/m^3"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -209,7 +233,10 @@ test.register_coroutine_test( capabilities.tvocMeasurement.tvocLevel({value = 1000.0, unit = "ug/m3"}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tvocHealthConcern.tvocHealthConcern({value = "unhealthy"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -224,7 +251,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "good"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -239,7 +269,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "moderate"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -254,7 +287,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "slightlyUnhealthy"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -269,7 +305,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "unhealthy"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -284,7 +323,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "veryUnhealthy"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -299,7 +341,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "hazardous"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -316,7 +361,10 @@ test.register_coroutine_test( capabilities.carbonDioxideMeasurement.carbonDioxide({value = 2000, unit = "ppm"}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "moderate"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -333,7 +381,10 @@ test.register_coroutine_test( capabilities.carbonDioxideMeasurement.carbonDioxide({value = 3000, unit = "ppm"}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "unhealthy"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -350,7 +401,10 @@ test.register_coroutine_test( capabilities.fineDustSensor.fineDustLevel({value = 90}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fineDustHealthConcern.fineDustHealthConcern({value = "moderate"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -367,7 +421,10 @@ test.register_coroutine_test( capabilities.fineDustSensor.fineDustLevel({value = 120}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fineDustHealthConcern.fineDustHealthConcern({value = "unhealthy"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -384,7 +441,10 @@ test.register_coroutine_test( capabilities.veryFineDustSensor.veryFineDustLevel({value = 150}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern({value = "unhealthy"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -401,7 +461,10 @@ test.register_coroutine_test( capabilities.dustSensor.dustLevel({value = 200}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.dustHealthConcern.dustHealthConcern({value = "unhealthy"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -418,7 +481,10 @@ test.register_coroutine_test( capabilities.tvocMeasurement.tvocLevel({value = 500.0, unit = "ug/m3"}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tvocHealthConcern.tvocHealthConcern({value = "good"}))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 a3227b04bc..bc19893cc6 100755 --- a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua +++ b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua @@ -1,950 +1,1118 @@ --- Copyright 2024 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - --- Mock out globals -local test = require "integration_test" -local cluster_base = require "st.zigbee.cluster_base" -local data_types = require "st.zigbee.data_types" -local t_utils = require "integration_test.utils" -local zigbee_test_utils = require "integration_test.zigbee_test_utils" -local custom_capabilities = require "shus-mattress/custom_capabilities" - -local shus_mattress_profile_def = t_utils.get_profile_definition("shus-smart-mattress.yml") -test.add_package_capability("aiMode.yaml") -test.add_package_capability("autoInflation.yaml") -test.add_package_capability("leftControl.yaml") -test.add_package_capability("rightControl.yaml") -test.add_package_capability("strongExpMode.yaml") -test.add_package_capability("yoga.yaml") -test.add_package_capability("mattressHardness.yaml") - -local PRIVATE_CLUSTER_ID = 0xFCC2 -local MFG_CODE = 0x1235 - -local mock_device = test.mock_device.build_test_zigbee_device( -{ - label = "Shus Smart Mattress", - profile = shus_mattress_profile_def, - zigbee_endpoints = { - [1] = { - id = 1, - manufacturer = "SHUS", - model = "SX-1", - server_clusters = { 0x0000,PRIVATE_CLUSTER_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( - "lifecycle - added 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"}, { 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) - local read_0x000a_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000a, MFG_CODE) - local read_0x0000_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE) - local read_0x0001_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE) - local read_0x0002_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE) - local read_0x0003_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0003, MFG_CODE) - local read_0x0004_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0004, MFG_CODE) - local read_0x0005_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0005, MFG_CODE) - local read_0x0008_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0008, MFG_CODE) - local read_0x000C_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000C, MFG_CODE) - local read_0x000D_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000D, MFG_CODE) - local read_0x000E_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000E, MFG_CODE) - local read_0x000F_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000F, MFG_CODE) - local read_0x0010_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0010, MFG_CODE) - local read_0x0011_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0011, MFG_CODE) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0006_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0007_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0009_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000a_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0000_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0001_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0002_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0003_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0004_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0005_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0008_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000C_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000D_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000E_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000F_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0010_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0011_messge}) - end -) - -test.register_coroutine_test( - "capability - refresh", - function() - test.socket.capability:__queue_receive({ mock_device.id, - { capability = "refresh", component = "main", command = "refresh", args = {} } }) - 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) - local read_0x000a_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000a, MFG_CODE) - local read_0x0000_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE) - local read_0x0001_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE) - local read_0x0002_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE) - local read_0x0003_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0003, MFG_CODE) - local read_0x0004_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0004, MFG_CODE) - local read_0x0005_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0005, MFG_CODE) - local read_0x0008_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0008, MFG_CODE) - local read_0x000C_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000C, MFG_CODE) - local read_0x000D_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000D, MFG_CODE) - local read_0x000E_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000E, MFG_CODE) - local read_0x000F_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000F, MFG_CODE) - local read_0x0010_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0010, MFG_CODE) - local read_0x0011_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0011, MFG_CODE) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0006_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0007_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0009_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000a_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0000_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0001_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0002_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0003_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0004_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0005_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0008_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000C_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000D_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000E_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000F_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0010_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0011_messge}) - end -) - -test.register_coroutine_test( - "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 } - } - 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", - 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({ visibility = { displayed = false }})", - 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", - 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({ visibility = { displayed = false }})", - function() - local attr_report_data = { - { 0x0001, 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", - 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({ visibility = { displayed = false }})", - function() - local attr_report_data = { - { 0x0001, 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", - 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({ visibility = { displayed = false }})", - function() - local attr_report_data = { - { 0x0002, 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", - 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({ visibility = { displayed = false }})", - function() - local attr_report_data = { - { 0x0002, 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", - 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({ visibility = { displayed = false }})", - function() - local attr_report_data = { - { 0x0003, 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", - 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({ visibility = { displayed = false }})", - function() - local attr_report_data = { - { 0x0003, 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", - 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({ visibility = { displayed = false }})", - function() - local attr_report_data = { - { 0x0004, 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", - 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({ visibility = { displayed = false }})", - function() - local attr_report_data = { - { 0x0004, 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", - 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({ visibility = { displayed = false }})", - function() - local attr_report_data = { - { 0x0005, 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", - 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({ visibility = { displayed = false }})", - function() - local attr_report_data = { - { 0x0005, 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", - custom_capabilities.right_control.righthip.idle({ visibility = { displayed = false }}))) - end -) - -test.register_coroutine_test( - "Device reported leftBackHardness 1 and driver emit custom_capabilities.mattressHardness.leftBackHardness(1)", - function() - local attr_report_data = { - { 0x000C, 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", - custom_capabilities.mattressHardness.leftBackHardness(1))) - end -) - -test.register_coroutine_test( - "Device reported leftWaistHardness 1 and driver emit custom_capabilities.mattressHardness.leftWaistHardness(1)", - function() - local attr_report_data = { - { 0x000D, 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", - custom_capabilities.mattressHardness.leftWaistHardness(1))) - end -) - -test.register_coroutine_test( - "Device reported leftHipHardness 1 and driver emit custom_capabilities.mattressHardness.leftHipHardness(1)", - function() - local attr_report_data = { - { 0x000E, 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", - custom_capabilities.mattressHardness.leftHipHardness(1))) - end -) - -test.register_coroutine_test( - "Device reported rightBackHardness 1 and driver emit custom_capabilities.mattressHardness.rightBackHardness(1)", - function() - local attr_report_data = { - { 0x000F, 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", - custom_capabilities.mattressHardness.rightBackHardness(1))) - end -) - -test.register_coroutine_test( - "Device reported rightWaistHardness 1 and driver emit custom_capabilities.mattressHardness.rightWaistHardness(1)", - function() - local attr_report_data = { - { 0x0010, 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", - custom_capabilities.mattressHardness.rightWaistHardness(1))) - end -) - -test.register_coroutine_test( - "Device reported rightHipHardness 1 and driver emit custom_capabilities.mattressHardness.rightHipHardness(1)", - function() - local attr_report_data = { - { 0x0011, 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", - custom_capabilities.mattressHardness.rightHipHardness(1))) - end -) - -test.register_coroutine_test( - "Device reported yoga 3 and driver emit custom_capabilities.yoga.state.both()", - function() - local attr_report_data = { - { 0x0008, 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", - custom_capabilities.yoga.state.both())) - end -) - -test.register_coroutine_test( - "Device reported yoga 2 and driver emit custom_capabilities.yoga.state.right()", - function() - local attr_report_data = { - { 0x0008, 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", - custom_capabilities.yoga.state.right())) - end -) - -test.register_coroutine_test( - "Device reported yoga 1 and driver emit custom_capabilities.yoga.state.left()", - function() - local attr_report_data = { - { 0x0008, 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", - custom_capabilities.yoga.state.left())) - end -) - -test.register_coroutine_test( - "Device reported yoga 0 and driver emit custom_capabilities.yoga.state.stop()", - function() - local attr_report_data = { - { 0x0008, 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", - custom_capabilities.yoga.state.stop())) - end -) - -test.register_coroutine_test( - "Device reported ai_mode left false and driver emit custom_capabilities.ai_mode.left.off()", - function() - local attr_report_data = { - { 0x0006, data_types.Boolean.ID, false } - } - 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", - custom_capabilities.ai_mode.left.off())) - end -) - -test.register_coroutine_test( - "Device reported ai_mode left true and driver emit custom_capabilities.ai_mode.left.on()", - function() - local attr_report_data = { - { 0x0006, data_types.Boolean.ID, true } - } - 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", - custom_capabilities.ai_mode.left.on())) - end -) - -test.register_coroutine_test( - "Device reported ai_mode right true and driver emit custom_capabilities.ai_mode.right.on()", - function() - local attr_report_data = { - { 0x0007, data_types.Boolean.ID, true } - } - 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", - custom_capabilities.ai_mode.right.on())) - end -) - -test.register_coroutine_test( - "Device reported ai_mode right false and driver emit custom_capabilities.ai_mode.right.off()", - function() - local attr_report_data = { - { 0x0007, data_types.Boolean.ID, false } - } - 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", - custom_capabilities.ai_mode.right.off())) - end -) - -test.register_coroutine_test( - "Device reported inflationState false and driver emit custom_capabilities.auto_inflation.inflationState.off()", - function() - local attr_report_data = { - { 0x0009, data_types.Boolean.ID, false } - } - 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", - custom_capabilities.auto_inflation.inflationState.off())) - end -) - -test.register_coroutine_test( - "Device reported inflationState true and driver emit custom_capabilities.auto_inflation.inflationState.on()", - function() - local attr_report_data = { - { 0x0009, data_types.Boolean.ID, true } - } - 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", - custom_capabilities.auto_inflation.inflationState.on())) - end -) - -test.register_coroutine_test( - "Device reported strong_exp_mode false and driver emit custom_capabilities.strong_exp_mode.expState.off()", - function() - local attr_report_data = { - { 0x000a, data_types.Boolean.ID, false } - } - 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", - custom_capabilities.strong_exp_mode.expState.off())) - end -) - -test.register_coroutine_test( - "Device reported strong_exp_mode true and driver emit custom_capabilities.strong_exp_mode.expState.on()", - function() - local attr_report_data = { - { 0x000a, data_types.Boolean.ID, true } - } - 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", - custom_capabilities.strong_exp_mode.expState.on())) - end -) - - -test.register_coroutine_test( - "capability leftControl on and driver send on ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.ai_mode.ID, component = "main", command ="leftControl" , args = {"on"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0006, MFG_CODE, data_types.Boolean, true) - }) - end -) - -test.register_coroutine_test( - "capability leftControl off and driver send off ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.ai_mode.ID, component = "main", command ="leftControl" , args = {"off"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0006, MFG_CODE, data_types.Boolean, false) - }) - end -) - -test.register_coroutine_test( - "capability rightControl on and driver send on ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.ai_mode.ID, component = "main", command ="rightControl" , args = {"on"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0007, MFG_CODE, data_types.Boolean, true) - }) - end -) - -test.register_coroutine_test( - "capability rightControl off and driver send off ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.ai_mode.ID, component = "main", command ="rightControl" , args = {"off"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0007, MFG_CODE, data_types.Boolean, false) - }) - end -) - -test.register_coroutine_test( - "capability auto_inflation stateControl on and driver send on ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.auto_inflation.ID, component = "main", command ="stateControl" , args = {"on"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0009, MFG_CODE, data_types.Boolean, true) - }) - end -) - -test.register_coroutine_test( - "capability auto_inflation stateControl off and driver send off ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.auto_inflation.ID, component = "main", command ="stateControl" , args = {"off"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0009, MFG_CODE, data_types.Boolean, false) - }) - end -) - -test.register_coroutine_test( - "capability strong_exp_mode stateControl on and driver send on ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.strong_exp_mode.ID, component = "main", command ="stateControl" , args = {"on"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x000a, MFG_CODE, data_types.Boolean, true) - }) - end -) - -test.register_coroutine_test( - "capability strong_exp_mode stateControl off and driver send off ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.strong_exp_mode.ID, component = "main", command ="stateControl" , args = {"off"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x000a, MFG_CODE, data_types.Boolean, false) - }) - end -) - -test.register_coroutine_test( - "capability left_control backControl soft and driver send soft ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="backControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0000, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftback.soft())) - end -) - -test.register_coroutine_test( - "capability waistControl backControl soft and driver send soft ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="waistControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0001, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftwaist.soft())) - end -) - -test.register_coroutine_test( - "capability left_control hipControl soft and driver send soft ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="hipControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0002, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.lefthip.soft())) - end -) - -test.register_coroutine_test( - "capability left_control backControl hard and driver send hard ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="backControl" , args = {"hard"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0000, MFG_CODE, data_types.Uint8, 1) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftback.hard())) - end -) - -test.register_coroutine_test( - "capability waistControl backControl hard and driver send hard ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="waistControl" , args = {"hard"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0001, MFG_CODE, data_types.Uint8, 1) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftwaist.hard())) - end -) - -test.register_coroutine_test( - "capability left_control hipControl hard and driver send hard ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="hipControl" , args = {"hard"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0002, MFG_CODE, data_types.Uint8, 1) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.lefthip.hard())) - end -) - -test.register_coroutine_test( - "capability right_control backControl soft and driver send soft ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.right_control.ID, component = "main", command ="backControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0003, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightback.soft())) - end -) - -test.register_coroutine_test( - "capability right_control waistControl soft and driver send soft ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.right_control.ID, component = "main", command ="waistControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0004, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightwaist.soft())) - end -) - -test.register_coroutine_test( - "capability right_control hipControl soft and driver send soft ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.right_control.ID, component = "main", command ="hipControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0005, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.righthip.soft())) - end -) - -test.register_coroutine_test( - "capability right_control backControl hard and driver send hard ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.right_control.ID, component = "main", command ="backControl" , args = {"hard"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0003, MFG_CODE, data_types.Uint8, 1) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightback.hard())) - end -) - -test.register_coroutine_test( - "capability right_control waistControl hard and driver send hard ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.right_control.ID, component = "main", command ="waistControl" , args = {"hard"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0004, MFG_CODE, data_types.Uint8, 1) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightwaist.hard())) - end -) - -test.register_coroutine_test( - "capability right_control hipControl hard and driver send hard ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.right_control.ID, component = "main", command ="hipControl" , args = {"hard"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0005, MFG_CODE, data_types.Uint8, 1) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.righthip.hard())) - end -) - -test.register_coroutine_test( - "capability yoga stateControl left and driver send left ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.yoga.ID, component = "main", command ="stateControl" , args = {"left"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0008, MFG_CODE, data_types.Uint8, 1) - }) - end -) - -test.register_coroutine_test( - "capability yoga stateControl right and driver send right ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.yoga.ID, component = "main", command ="stateControl" , args = {"right"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0008, MFG_CODE, data_types.Uint8, 2) - }) - end -) - -test.register_coroutine_test( - "capability yoga stateControl stop and driver send stop ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.yoga.ID, component = "main", command ="stateControl" , args = {"stop"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0008, MFG_CODE, data_types.Uint8, 0) - }) - end -) - -test.register_coroutine_test( - "capability left_control backControl soft emits idle event after delay", - function() - test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="backControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0000, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftback.soft())) - test.wait_for_events() - - test.mock_time.advance_time(1) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftback("idle", { visibility = { displayed = false }}))) - end -) - -test.run_registered_tests() +-- Copyright 2024 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Mock out globals +local test = require "integration_test" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local custom_capabilities = require "shus-mattress/custom_capabilities" + +local shus_mattress_profile_def = t_utils.get_profile_definition("shus-smart-mattress.yml") +test.add_package_capability("aiMode.yaml") +test.add_package_capability("autoInflation.yaml") +test.add_package_capability("leftControl.yaml") +test.add_package_capability("rightControl.yaml") +test.add_package_capability("strongExpMode.yaml") +test.add_package_capability("yoga.yaml") +test.add_package_capability("mattressHardness.yaml") + +local PRIVATE_CLUSTER_ID = 0xFCC2 +local MFG_CODE = 0x1235 + +local mock_device = test.mock_device.build_test_zigbee_device( +{ + label = "Shus Smart Mattress", + profile = shus_mattress_profile_def, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "SHUS", + model = "SX-1", + server_clusters = { 0x0000,PRIVATE_CLUSTER_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( + "lifecycle - added 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"}, { 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) + local read_0x000a_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000a, MFG_CODE) + local read_0x0000_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE) + local read_0x0001_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE) + local read_0x0002_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE) + local read_0x0003_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0003, MFG_CODE) + local read_0x0004_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0004, MFG_CODE) + local read_0x0005_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0005, MFG_CODE) + local read_0x0008_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0008, MFG_CODE) + local read_0x000C_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000C, MFG_CODE) + local read_0x000D_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000D, MFG_CODE) + local read_0x000E_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000E, MFG_CODE) + local read_0x000F_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000F, MFG_CODE) + local read_0x0010_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0010, MFG_CODE) + local read_0x0011_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0011, MFG_CODE) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0006_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0007_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0009_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000a_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0000_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0001_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0002_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0003_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0004_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0005_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0008_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000C_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000D_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000E_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000F_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0010_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0011_messge}) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability - refresh", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } }) + 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) + local read_0x000a_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000a, MFG_CODE) + local read_0x0000_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE) + local read_0x0001_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE) + local read_0x0002_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE) + local read_0x0003_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0003, MFG_CODE) + local read_0x0004_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0004, MFG_CODE) + local read_0x0005_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0005, MFG_CODE) + local read_0x0008_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0008, MFG_CODE) + local read_0x000C_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000C, MFG_CODE) + local read_0x000D_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000D, MFG_CODE) + local read_0x000E_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000E, MFG_CODE) + local read_0x000F_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000F, MFG_CODE) + local read_0x0010_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0010, MFG_CODE) + local read_0x0011_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0011, MFG_CODE) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0006_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0007_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0009_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000a_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0000_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0001_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0002_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0003_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0004_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0005_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0008_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000C_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000D_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000E_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000F_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0010_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0011_messge}) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "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 } + } + 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", + custom_capabilities.left_control.leftback.idle({ visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "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 } + } + 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", + custom_capabilities.left_control.leftback.idle({ visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "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 } + } + 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", + custom_capabilities.left_control.leftwaist.idle({ visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "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 } + } + 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", + custom_capabilities.left_control.leftwaist.idle({ visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "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 } + } + 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", + custom_capabilities.left_control.lefthip.idle({ visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "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 } + } + 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", + custom_capabilities.left_control.lefthip.idle({ visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "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 } + } + 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", + custom_capabilities.right_control.rightback.idle({ visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "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 } + } + 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", + custom_capabilities.right_control.rightback.idle({ visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "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 } + } + 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", + custom_capabilities.right_control.rightwaist.idle({ visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "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 } + } + 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", + custom_capabilities.right_control.rightwaist.idle({ visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "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 } + } + 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", + custom_capabilities.right_control.righthip.idle({ visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "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 } + } + 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", + custom_capabilities.right_control.righthip.idle({ visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported leftBackHardness 1 and driver emit custom_capabilities.mattressHardness.leftBackHardness(1)", + function() + local attr_report_data = { + { 0x000C, 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", + custom_capabilities.mattressHardness.leftBackHardness(1))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported leftWaistHardness 1 and driver emit custom_capabilities.mattressHardness.leftWaistHardness(1)", + function() + local attr_report_data = { + { 0x000D, 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", + custom_capabilities.mattressHardness.leftWaistHardness(1))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported leftHipHardness 1 and driver emit custom_capabilities.mattressHardness.leftHipHardness(1)", + function() + local attr_report_data = { + { 0x000E, 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", + custom_capabilities.mattressHardness.leftHipHardness(1))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported rightBackHardness 1 and driver emit custom_capabilities.mattressHardness.rightBackHardness(1)", + function() + local attr_report_data = { + { 0x000F, 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", + custom_capabilities.mattressHardness.rightBackHardness(1))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported rightWaistHardness 1 and driver emit custom_capabilities.mattressHardness.rightWaistHardness(1)", + function() + local attr_report_data = { + { 0x0010, 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", + custom_capabilities.mattressHardness.rightWaistHardness(1))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported rightHipHardness 1 and driver emit custom_capabilities.mattressHardness.rightHipHardness(1)", + function() + local attr_report_data = { + { 0x0011, 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", + custom_capabilities.mattressHardness.rightHipHardness(1))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported yoga 3 and driver emit custom_capabilities.yoga.state.both()", + function() + local attr_report_data = { + { 0x0008, 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", + custom_capabilities.yoga.state.both())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported yoga 2 and driver emit custom_capabilities.yoga.state.right()", + function() + local attr_report_data = { + { 0x0008, 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", + custom_capabilities.yoga.state.right())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported yoga 1 and driver emit custom_capabilities.yoga.state.left()", + function() + local attr_report_data = { + { 0x0008, 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", + custom_capabilities.yoga.state.left())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported yoga 0 and driver emit custom_capabilities.yoga.state.stop()", + function() + local attr_report_data = { + { 0x0008, 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", + custom_capabilities.yoga.state.stop())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported ai_mode left false and driver emit custom_capabilities.ai_mode.left.off()", + function() + local attr_report_data = { + { 0x0006, data_types.Boolean.ID, false } + } + 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", + custom_capabilities.ai_mode.left.off())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported ai_mode left true and driver emit custom_capabilities.ai_mode.left.on()", + function() + local attr_report_data = { + { 0x0006, data_types.Boolean.ID, true } + } + 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", + custom_capabilities.ai_mode.left.on())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported ai_mode right true and driver emit custom_capabilities.ai_mode.right.on()", + function() + local attr_report_data = { + { 0x0007, data_types.Boolean.ID, true } + } + 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", + custom_capabilities.ai_mode.right.on())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported ai_mode right false and driver emit custom_capabilities.ai_mode.right.off()", + function() + local attr_report_data = { + { 0x0007, data_types.Boolean.ID, false } + } + 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", + custom_capabilities.ai_mode.right.off())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported inflationState false and driver emit custom_capabilities.auto_inflation.inflationState.off()", + function() + local attr_report_data = { + { 0x0009, data_types.Boolean.ID, false } + } + 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", + custom_capabilities.auto_inflation.inflationState.off())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported inflationState true and driver emit custom_capabilities.auto_inflation.inflationState.on()", + function() + local attr_report_data = { + { 0x0009, data_types.Boolean.ID, true } + } + 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", + custom_capabilities.auto_inflation.inflationState.on())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported strong_exp_mode false and driver emit custom_capabilities.strong_exp_mode.expState.off()", + function() + local attr_report_data = { + { 0x000a, data_types.Boolean.ID, false } + } + 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", + custom_capabilities.strong_exp_mode.expState.off())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Device reported strong_exp_mode true and driver emit custom_capabilities.strong_exp_mode.expState.on()", + function() + local attr_report_data = { + { 0x000a, data_types.Boolean.ID, true } + } + 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", + custom_capabilities.strong_exp_mode.expState.on())) + end, + { + min_api_version = 19 + } +) + + +test.register_coroutine_test( + "capability leftControl on and driver send on ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.ai_mode.ID, component = "main", command ="leftControl" , args = {"on"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0006, MFG_CODE, data_types.Boolean, true) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability leftControl off and driver send off ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.ai_mode.ID, component = "main", command ="leftControl" , args = {"off"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0006, MFG_CODE, data_types.Boolean, false) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability rightControl on and driver send on ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.ai_mode.ID, component = "main", command ="rightControl" , args = {"on"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0007, MFG_CODE, data_types.Boolean, true) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability rightControl off and driver send off ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.ai_mode.ID, component = "main", command ="rightControl" , args = {"off"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0007, MFG_CODE, data_types.Boolean, false) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability auto_inflation stateControl on and driver send on ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.auto_inflation.ID, component = "main", command ="stateControl" , args = {"on"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0009, MFG_CODE, data_types.Boolean, true) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability auto_inflation stateControl off and driver send off ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.auto_inflation.ID, component = "main", command ="stateControl" , args = {"off"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0009, MFG_CODE, data_types.Boolean, false) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability strong_exp_mode stateControl on and driver send on ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.strong_exp_mode.ID, component = "main", command ="stateControl" , args = {"on"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x000a, MFG_CODE, data_types.Boolean, true) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability strong_exp_mode stateControl off and driver send off ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.strong_exp_mode.ID, component = "main", command ="stateControl" , args = {"off"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x000a, MFG_CODE, data_types.Boolean, false) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability left_control backControl soft and driver send soft ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="backControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0000, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftback.soft())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability waistControl backControl soft and driver send soft ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="waistControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0001, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftwaist.soft())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability left_control hipControl soft and driver send soft ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="hipControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0002, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.lefthip.soft())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability left_control backControl hard and driver send hard ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="backControl" , args = {"hard"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0000, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftback.hard())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability waistControl backControl hard and driver send hard ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="waistControl" , args = {"hard"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0001, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftwaist.hard())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability left_control hipControl hard and driver send hard ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="hipControl" , args = {"hard"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0002, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.lefthip.hard())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability right_control backControl soft and driver send soft ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.right_control.ID, component = "main", command ="backControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0003, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.rightback.soft())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability right_control waistControl soft and driver send soft ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.right_control.ID, component = "main", command ="waistControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0004, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.rightwaist.soft())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability right_control hipControl soft and driver send soft ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.right_control.ID, component = "main", command ="hipControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0005, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.righthip.soft())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability right_control backControl hard and driver send hard ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.right_control.ID, component = "main", command ="backControl" , args = {"hard"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0003, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.rightback.hard())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability right_control waistControl hard and driver send hard ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.right_control.ID, component = "main", command ="waistControl" , args = {"hard"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0004, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.rightwaist.hard())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability right_control hipControl hard and driver send hard ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.right_control.ID, component = "main", command ="hipControl" , args = {"hard"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0005, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.righthip.hard())) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability yoga stateControl left and driver send left ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.yoga.ID, component = "main", command ="stateControl" , args = {"left"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0008, MFG_CODE, data_types.Uint8, 1) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability yoga stateControl right and driver send right ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.yoga.ID, component = "main", command ="stateControl" , args = {"right"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0008, MFG_CODE, data_types.Uint8, 2) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability yoga stateControl stop and driver send stop ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.yoga.ID, component = "main", command ="stateControl" , args = {"stop"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0008, MFG_CODE, data_types.Uint8, 0) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "capability left_control backControl soft emits idle event after delay", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="backControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0000, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftback.soft())) + test.wait_for_events() + + test.mock_time.advance_time(1) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftback("idle", { visibility = { displayed = false }}))) + end, + { + min_api_version = 19 + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua index cfbd4a6845..6e4ad5793e 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua @@ -48,7 +48,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("button3", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -69,7 +72,10 @@ test.register_coroutine_test( mock_device:generate_test_message("button3", button_attr.held({ state_change = true })) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -81,7 +87,10 @@ test.register_coroutine_test( zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, OnOff.ID) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua index 27b8da764b..2d89507a07 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua @@ -91,7 +91,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -126,7 +129,10 @@ test.register_coroutine_test( Level.ID, 3) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -171,7 +177,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 b2d5a986d6..d15ea394b4 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua @@ -97,7 +97,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -126,7 +129,10 @@ test.register_coroutine_test( MFG_CODE, data_types.Uint8, 2) }) mock_device_e1:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) @@ -158,7 +164,10 @@ test.register_coroutine_test( PRIVATE_ATTRIBUTE_ID_T1, MFG_CODE, data_types.Uint8, 1) }) mock_device_h1_double_rocker:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -176,7 +185,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -194,7 +206,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -212,7 +227,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -228,6 +246,9 @@ test.register_message_test( direction = "send", message = mock_device_e1:generate_test_message("main", capabilities.batteryLevel.battery("normal")) } + }, + { + min_api_version = 19 } ) test.register_message_test( @@ -243,6 +264,9 @@ test.register_message_test( direction = "send", message = mock_device_e1:generate_test_message("main", capabilities.batteryLevel.battery("warning")) } + }, + { + min_api_version = 19 } ) test.register_message_test( @@ -258,6 +282,9 @@ test.register_message_test( direction = "send", message = mock_device_e1:generate_test_message("main", capabilities.batteryLevel.battery("critical")) } + }, + { + min_api_version = 19 } ) @@ -299,7 +326,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -318,7 +348,10 @@ test.register_coroutine_test( capabilities.batteryLevel.type("CR2450"))) test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", capabilities.batteryLevel.quantity(1))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua index c8d5ff87ae..f1e0b28ebd 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua @@ -93,7 +93,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) @@ -147,7 +150,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", button_attr.held({ state_change = true })) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -183,7 +189,10 @@ test.register_coroutine_test( OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 600, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -194,7 +203,10 @@ test.register_coroutine_test( mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -239,7 +251,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua b/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua index c552322c9b..d04587e2f0 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua @@ -53,7 +53,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -81,7 +84,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -109,7 +115,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.held({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -148,7 +157,10 @@ test.register_coroutine_test( ) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -159,7 +171,10 @@ test.register_coroutine_test( mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -258,7 +273,10 @@ test.register_coroutine_test( mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua index 5562eced88..43d6f95d84 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua @@ -62,7 +62,10 @@ test.register_coroutine_test( mock_device.id, TemperatureMeasurement.attributes.MinMeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -74,7 +77,10 @@ test.register_coroutine_test( 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.BatteryPercentageRemaining:configure_reporting(mock_device, 30, 21600, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -87,7 +93,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button.button.double({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -100,7 +109,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button.button.held({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -137,7 +149,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button.button.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua index be613bbc14..53242a2e2c 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua @@ -53,6 +53,9 @@ test.register_message_test( direction = "send", message = mock_device_ezviz_button:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -94,6 +97,9 @@ test.register_message_test( direction = "send", message = mock_device_ezviz_button:generate_test_message("main", capabilities.button.button.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -110,6 +116,9 @@ test.register_message_test( direction = "send", message = mock_device_ezviz_button:generate_test_message("main", capabilities.button.button.double({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -126,6 +135,9 @@ test.register_message_test( direction = "send", message = mock_device_ezviz_button:generate_test_message("main", capabilities.button.button.held({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -159,7 +171,10 @@ test.register_coroutine_test( } ) test.socket.zigbee:__expect_send({ mock_device_ezviz_button.id, ZoneStatusAttribute:read(mock_device_ezviz_button) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -182,7 +197,10 @@ test.register_coroutine_test( mock_device_ezviz_button:generate_test_message("main", capabilities.button.button.pushed({ state_change = false })) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua index aa6f211d65..0204586c8a 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua @@ -86,6 +86,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -108,7 +111,11 @@ test.register_message_test("Refresh should read all necessary attributes", { direction = "send", message = {mock_device.id, BasicInput.attributes.PresentValue:read(mock_device)} }, -}) +}, +{ + min_api_version = 19 +} +) test.register_coroutine_test("panicAlarm should be triggered and cleared", function() @@ -140,7 +147,11 @@ test.register_coroutine_test("panicAlarm should be triggered and cleared", funct test.socket.capability:__expect_send(mock_device_panic:generate_test_message("main", panicAlarm.clear({value = "clear", state_change = true}))) test.wait_for_events() -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test( "Battery Voltage test cases", @@ -171,7 +182,10 @@ test.register_coroutine_test( 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", capabilities.button.numberOfButtons({value = 1}))) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -210,7 +224,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, BasicInput.ID, 0x8000, DEVELCO_MANUFACTURER_CODE, data_types.Uint16, 65535):to_endpoint(0x20)}) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test("info_changed for OnOff cluster attributes should run properly", @@ -234,7 +251,11 @@ function() buttonDelay_msg.body.zcl_header.frame_ctrl.value = 0x0C buttonDelay_msg.address_header.dest_endpoint.value = 0x20 test.socket.zigbee:__expect_send({mock_device.id, buttonDelay_msg}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test(" Configuration and Switching to button-profile-panic-frient deviceProfile should be triggered", function() test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") @@ -263,7 +284,11 @@ test.register_coroutine_test(" Configuration and Switching to button-profile-pan end -- Unable to check if the emit went through successfully due to the framework limitations in swapping mock device's deviceProfile --test.socket.capability:__expect_send({mock_device.id, capabilities.panicAlarm.panicAlarm.clear({state_change = true})}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Switching from button-profile-panic-frient to button-profile-frient should work", function() test.socket.device_lifecycle:__queue_receive(mock_device_panic:generate_info_changed( @@ -275,7 +300,11 @@ test.register_coroutine_test("Switching from button-profile-panic-frient to butt )) mock_device_panic:expect_metadata_update({ profile = "button-profile-frient" }) test.socket.zigbee:__expect_send({mock_device_panic.id, cluster_base.write_manufacturer_specific_attribute(mock_device_panic,BasicInput.ID,0x8000,DEVELCO_MANUFACTURER_CODE,data_types.Uint16,0xFFFF)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("New preferences after switching the profile should work", function() test.socket.zigbee:__set_channel_ordering("relaxed") @@ -293,6 +322,10 @@ test.register_coroutine_test("New preferences after switching the profile should test.socket.zigbee:__expect_send({mock_device_panic.id, cluster_base.write_manufacturer_specific_attribute(mock_device_panic, IASZone.ID,0x8003,DEVELCO_MANUFACTURER_CODE,data_types.Uint16, 300)}) test.socket.zigbee:__expect_send({mock_device_panic.id, cluster_base.write_manufacturer_specific_attribute(mock_device_panic, IASZone.ID,0x8004,DEVELCO_MANUFACTURER_CODE,data_types.Uint16, 20)}) test.socket.zigbee:__expect_send({mock_device_panic.id, cluster_base.write_manufacturer_specific_attribute(mock_device_panic, IASZone.ID,0x8005,DEVELCO_MANUFACTURER_CODE,data_types.Enum8, 1)}) -end) +end, +{ + min_api_version = 19 +} +) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua index 0cc2c734cf..6761cf263c 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua @@ -90,7 +90,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -122,7 +125,10 @@ test.register_coroutine_test( mock_device_hs6ssb:generate_test_message("main", button_attr.pushed({ state_change = true })) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -166,7 +172,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0x0011) test.socket.zigbee:__expect_add_hub_to_group(0x0012) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -187,7 +196,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0x0012) test.socket.zigbee:__expect_add_hub_to_group(0x0013) mock_device_hs6ssb:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -265,7 +277,10 @@ test.register_coroutine_test( Basic.attributes.DeviceEnabled:write(mock_device, true) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -296,7 +311,10 @@ test.register_coroutine_test( Basic.attributes.DeviceEnabled:write(mock_device_hs6ssb, true) }) mock_device_hs6ssb:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -341,7 +359,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -386,7 +407,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device_hs6ssb.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua b/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua index 82f477f426..87e225ae16 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua @@ -73,7 +73,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.held({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -114,7 +117,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -135,7 +141,10 @@ test.register_coroutine_test( } ) test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -194,7 +203,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, Groups.commands.AddGroup(mock_device, 0x0000) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -290,7 +302,10 @@ test.register_coroutine_test( PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua b/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua index 3d5c7ab58d..a3eb2ef195 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua @@ -53,7 +53,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", (button_attr.pushed({ state_change = true }))) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -95,7 +98,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -116,7 +122,10 @@ test.register_coroutine_test( } ) test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -138,7 +147,10 @@ test.register_coroutine_test( ) test.socket.zigbee:__expect_add_hub_to_group(0x0000) test.socket.zigbee:__expect_send({mock_device.id, Groups.commands.AddGroup(mock_device, 0x0000) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -233,7 +245,10 @@ test.register_coroutine_test( PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua b/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua index ef08a1d7e5..6c77739952 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua @@ -107,7 +107,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", (button_attr.held({ state_change = true }))) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -149,7 +152,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -171,7 +177,10 @@ test.register_coroutine_test( } ) test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -275,7 +284,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -291,6 +303,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(55)) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua index 64630415a0..88fae6eeb2 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua @@ -45,7 +45,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -58,7 +61,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.held({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -97,7 +103,10 @@ test.register_coroutine_test( IASZone.server.commands.ZoneEnrollResponse(mock_device, IasEnrollResponseCode.SUCCESS, 0x00) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -112,7 +121,10 @@ test.register_coroutine_test( mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -163,7 +175,10 @@ test.register_coroutine_test( mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -181,7 +196,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -199,7 +217,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.held({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -217,7 +238,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -235,7 +259,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.held({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua index 8e3ff6e001..244969abbb 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua @@ -55,7 +55,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -71,7 +74,10 @@ test.register_coroutine_test( ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -87,7 +93,10 @@ test.register_coroutine_test( ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) @@ -104,7 +113,10 @@ test.register_coroutine_test( ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua index 27ec0a8e44..2f22a129c4 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua @@ -55,7 +55,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -71,7 +74,10 @@ test.register_coroutine_test( ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -87,7 +93,10 @@ test.register_coroutine_test( ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) @@ -104,7 +113,10 @@ test.register_coroutine_test( ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua index 4af8ef8c3c..218bb10615 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua @@ -46,6 +46,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) test.register_message_test( @@ -56,6 +59,9 @@ test.register_message_test( direction = "receive", message = { mock_device.id, ZoneStatusAttribute:build_test_attr_report(mock_device, 0x0000) } } + }, + { + min_api_version = 19 } ) @@ -72,6 +78,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -88,6 +97,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -150,7 +162,10 @@ test.register_coroutine_test( IASZone.attributes.ZoneStatus:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -202,7 +217,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua index 4888ae5f7e..3f0d63d75b 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua @@ -79,7 +79,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -119,6 +122,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.button.button.up_hold({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -161,6 +167,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.button.button.down_hold({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -201,7 +210,10 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_add_hub_to_group(0xE902) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -312,7 +324,10 @@ test.register_coroutine_test( mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -351,6 +366,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -366,7 +384,10 @@ test.register_coroutine_test( PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua index ebb324a3dc..ac1d7f4362 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua @@ -124,7 +124,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -198,6 +201,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.button.button.up_hold({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -276,6 +282,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.button.button.down_hold({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -318,7 +327,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0xE903) test.socket.zigbee:__expect_add_hub_to_group(0xE904) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -430,7 +442,10 @@ test.register_coroutine_test( mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -469,6 +484,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -484,7 +502,10 @@ test.register_coroutine_test( PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua index 1a23d68d83..4560daa1a0 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua @@ -88,7 +88,10 @@ test.register_coroutine_test( IASZone.server.commands.ZoneEnrollResponse(mock_device, IasEnrollResponseCode.SUCCESS, 0x00) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -135,7 +138,10 @@ test.register_coroutine_test( -- mock_device.id, -- IASZone.attributes.ZoneStatus:read(mock_device) -- }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua index 5278edbf8e..a0810072f8 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua @@ -83,7 +83,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) @@ -137,7 +140,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", button_attr.held({ state_change = true })) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) @@ -191,7 +197,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", button_attr.double({ state_change = true })) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -228,7 +237,10 @@ test.register_coroutine_test( Groups.commands.AddGroup(mock_device, 0x0000) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -273,7 +285,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua index 7e7e155867..f1339d6155 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua @@ -63,7 +63,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -111,7 +114,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -132,7 +138,10 @@ test.register_coroutine_test( } ) test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -191,7 +200,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, Groups.commands.AddGroup(mock_device, 0x0000) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -236,7 +248,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua index d358db3fcc..2422763c25 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua @@ -92,7 +92,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -158,7 +161,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("button11", (button_attr.pushed({ state_change = true }))) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -179,7 +185,10 @@ test.register_coroutine_test( } ) test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -224,7 +233,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua index a102e30e51..560f59940e 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua @@ -46,7 +46,10 @@ test.register_coroutine_test( capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -60,7 +63,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, 0x0012, attr_report_data, 0x110A) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -74,7 +80,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, 0x0012, attr_report_data, 0x110A) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.double({ state_change = true }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -88,7 +97,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, 0x0012, attr_report_data, 0x110A) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.held({ state_change = true }))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua index 575175aa47..3e8fc77797 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua @@ -75,7 +75,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -90,7 +93,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -105,7 +111,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -128,7 +137,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.up({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -151,7 +163,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.up({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -196,7 +211,10 @@ test.register_coroutine_test( end mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua index 98d8efbdfd..2c95ffe855 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua @@ -304,7 +304,10 @@ test.register_coroutine_test( test.wait_for_events() test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 0x1F) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -342,7 +345,10 @@ test.register_coroutine_test( test.socket:set_time_advance_per_select(0.1) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua index 92a50636a4..ad7b45ef95 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua @@ -37,6 +37,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -53,6 +56,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.held({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -69,6 +75,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.double({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -80,6 +89,9 @@ test.register_message_test( direction = "receive", message = { mock_device.id, ZoneStatusAttribute:build_test_attr_report(mock_device, 0x0000) } } + }, + { + min_api_version = 19 } ) @@ -96,6 +108,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -112,6 +127,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.held({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -128,6 +146,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", button_attr.double({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -144,6 +165,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -161,6 +185,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C"})) } + }, + { + min_api_version = 19 } ) @@ -184,6 +211,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 20.00, maximum = 30.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -264,7 +294,10 @@ test.register_coroutine_test( TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -338,7 +371,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua index c6f28dfe44..d14c324ff5 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua @@ -48,6 +48,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -93,7 +96,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -115,7 +121,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -130,7 +139,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -153,7 +165,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -195,7 +210,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -241,7 +259,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0x4003) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua index ae579dd671..eb7e222bcb 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua @@ -79,7 +79,10 @@ test.register_coroutine_test( }) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -137,7 +140,10 @@ test.register_coroutine_test( IASZone.server.commands.ZoneEnrollResponse(mock_device, IasEnrollResponseCode.SUCCESS, 0x00) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -162,7 +168,10 @@ test.register_coroutine_test( ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) @@ -188,7 +197,10 @@ test.register_coroutine_test( ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) 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 78164924d0..9e74007faa 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 @@ -41,7 +41,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 07933958de..abbe814c8e 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 @@ -42,6 +42,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "carbonMonoxideDetector", capability_attr_id = "carbonMonoxide" } } }, + }, + { + min_api_version = 19 } ) @@ -66,6 +69,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "carbonMonoxideDetector", capability_attr_id = "carbonMonoxide" } } }, + }, + { + min_api_version = 19 } ) @@ -91,6 +97,9 @@ test.register_message_test( } }, + }, + { + min_api_version = 19 } ) @@ -115,6 +124,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "carbonMonoxideDetector", capability_attr_id = "carbonMonoxide" } } }, + }, + { + min_api_version = 19 } ) @@ -131,6 +143,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -199,7 +214,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua index 7f2dae99fe..4243db2bcf 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua @@ -64,7 +64,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE , data_types.Uint8, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -85,7 +88,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.quantity(1))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.battery("normal"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -100,7 +106,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.battery("normal"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -115,7 +124,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.battery("critical"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -130,7 +142,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.battery("normal"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -145,7 +160,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.battery("warning"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -160,7 +178,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.battery("critical"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -175,7 +196,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -190,7 +214,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua index 46fc40f686..dbb8b48dff 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua @@ -42,7 +42,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -90,7 +93,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua index d914712e06..560356b3d4 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua @@ -106,7 +106,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, 0xFC02, attr_report_data, 0x110A) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({300, 200, 100})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -129,7 +132,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, 0xFC02, acceleration_report_inactive, 0x110A) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -162,7 +168,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, 0xFC02, attr_report_data, 0x110A) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -186,7 +195,10 @@ test.register_coroutine_test( mock_device.id, zigbee_test_utils.build_attribute_read(mock_device, 0xFC02, {0x0010}, 0x104E) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -207,7 +219,10 @@ test.register_coroutine_test( mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -265,7 +280,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -287,7 +305,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -310,7 +331,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device_old_firmware:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua index 075b923bed..288856d99d 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua @@ -44,7 +44,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -103,7 +106,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -119,6 +125,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -135,6 +144,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua index 716828541a..946d7ddad3 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua @@ -45,7 +45,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -112,7 +115,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PollControl.commands.SetLongPollInterval(mock_device, 0xB1040000) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -128,6 +134,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -144,6 +153,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua index f983211856..121ac601f2 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua @@ -43,7 +43,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -91,7 +94,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua index b73f7ee3fe..a7ae12f907 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua @@ -46,7 +46,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -130,7 +133,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -146,6 +152,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -162,6 +171,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -194,7 +206,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -211,6 +224,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -227,6 +243,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -243,6 +262,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -259,6 +281,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua index 4b0363caba..fe875d95f6 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua @@ -54,7 +54,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -173,7 +176,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -197,6 +203,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -213,6 +222,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -229,6 +241,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -306,7 +321,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -323,6 +339,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -339,6 +358,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -355,6 +377,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -371,6 +396,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -396,7 +424,10 @@ test.register_coroutine_test( temperatureSensitivity ):to_endpoint(TEMPERATURE_MEASUREMENT_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua index 6a7876541f..89f1e00d4e 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua @@ -54,7 +54,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -173,7 +176,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -197,6 +203,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -213,6 +222,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -229,6 +241,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -306,7 +321,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -328,6 +344,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -349,6 +368,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -370,6 +392,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -391,6 +416,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -416,7 +444,10 @@ test.register_coroutine_test( temperatureSensitivity ):to_endpoint(TEMPERATURE_MEASUREMENT_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua index 2e704a61ca..27dca9b220 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua @@ -298,7 +298,10 @@ test.register_coroutine_test( }) mock_device_contact:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -323,6 +326,9 @@ test.register_message_test( } } } + }, + { + min_api_version = 19 } ) @@ -339,6 +345,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -355,6 +364,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -393,7 +405,10 @@ function() Frient_AccelerationMeasurementCluster.ManufacturerSpecificCode ):to_endpoint(POWER_CONFIGURATION_AND_ACCELERATION_ENDPOINT) }) -end +end, +{ + min_api_version = 19 +} ) test.register_message_test( @@ -414,6 +429,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive(mock_device)) } + }, + { + min_api_version = 19 } ) @@ -435,6 +453,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active(mock_device)) } + }, + { + min_api_version = 19 } ) @@ -454,7 +475,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({300, 200, 100})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -479,7 +503,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device_contact:generate_test_message("main", capabilities.contactSensor.contact.open()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -504,7 +531,11 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device_contact:generate_test_message("main", capabilities.contactSensor.contact.closed()) ) - end + end, + { + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua index 291755bb17..8d75babbb6 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua @@ -42,7 +42,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -90,7 +93,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua index 2e0c67469d..f564e4dd7e 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua @@ -93,7 +93,10 @@ test.register_coroutine_test( mock_device.id, zigbee_test_utils.build_attribute_read(mock_device, 0xFC02, {0x0010}, 0x1241) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -114,7 +117,10 @@ test.register_coroutine_test( mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -172,7 +178,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -211,7 +220,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua index 39f127dcf4..78398bd78e 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua @@ -42,7 +42,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -90,7 +93,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua index d5482e7d6e..b9d19e6f13 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua @@ -114,7 +114,10 @@ test.register_coroutine_test( }) test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -129,7 +132,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -144,7 +150,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -159,7 +168,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -174,7 +186,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -189,7 +204,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -204,7 +222,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -219,7 +240,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -234,7 +258,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -249,7 +276,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(97))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -264,7 +294,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -279,7 +312,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -294,7 +330,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(0))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -309,7 +348,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(0))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -324,7 +366,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(0))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -336,7 +381,10 @@ test.register_coroutine_test( }) test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({1050, 3, 9})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -348,7 +396,10 @@ test.register_coroutine_test( }) test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({-1050, -3, -9})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -360,7 +411,10 @@ test.register_coroutine_test( }) test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({10, 1020, 7})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -372,7 +426,10 @@ test.register_coroutine_test( }) test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({-10, -1020, -7})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -384,7 +441,10 @@ test.register_coroutine_test( }) test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({116, 4, 1003})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -396,7 +456,10 @@ test.register_coroutine_test( }) test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({-116, -4, -1003})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -426,7 +489,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({-116, -4, -826})) ) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -442,6 +508,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -458,6 +527,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -474,6 +546,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -490,6 +565,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua index c86078f224..1337e696c2 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua @@ -87,6 +87,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -103,6 +106,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -126,7 +132,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, 0xFC02, acceleration_report_inactive, 0x110A) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -140,7 +149,10 @@ test.register_coroutine_test( cluster_base.build_test_read_attr_response(attribute_def, mock_device, 1) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -156,7 +168,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, 0xFC02, attr_report_data, 0x110A) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({300, 100, -200})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -189,7 +204,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, 0xFC02, attr_report_data, 0x110A) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -216,7 +234,10 @@ test.register_coroutine_test( mock_device.id, ZoneStatusAttribute:build_test_attr_report(mock_device, 0x0001) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -232,6 +253,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -248,6 +272,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -272,7 +299,10 @@ test.register_coroutine_test( mock_device.id, zigbee_test_utils.build_attribute_read(mock_device, 0xFC02, {0x0010}, 0x110A) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -293,7 +323,10 @@ test.register_coroutine_test( mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -355,7 +388,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -396,7 +432,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua index a6d989609f..5e6bb70e7d 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua @@ -42,7 +42,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -90,7 +93,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_thirdreality_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_thirdreality_multi_sensor.lua index 24dc80b7b2..74a6375b01 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_thirdreality_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_thirdreality_multi_sensor.lua @@ -1,3 +1,6 @@ +-- Copyright 2026 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" @@ -49,7 +52,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, 0xFFF1, acceleration_report_inactive, 0x110A) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -63,7 +69,10 @@ test.register_coroutine_test( cluster_base.build_test_read_attr_response(attribute_def, mock_device, 1) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -79,7 +88,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, 0xFFF1, attr_report_data, 0x110A) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({200, 100, 300})) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua index 8db97fd224..de9f7d97ee 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua @@ -44,6 +44,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "contactSensor", capability_attr_id = "contact" } } }, + }, + { + min_api_version = 19 } ) @@ -68,6 +71,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "contactSensor", capability_attr_id = "contact" } } }, + }, + { + min_api_version = 19 } ) @@ -94,6 +100,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "contactSensor", capability_attr_id = "contact" } } }, + }, + { + min_api_version = 19 } ) @@ -120,6 +129,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "contactSensor", capability_attr_id = "contact" } } }, + }, + { + min_api_version = 19 } ) @@ -145,6 +157,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -168,6 +183,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 20.00, maximum = 30.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -185,6 +203,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -227,7 +248,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -318,7 +342,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua index 5e3404b2a0..4f12bdb22c 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua @@ -86,7 +86,10 @@ test.register_coroutine_test( test.wait_for_events() end end - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -103,6 +106,9 @@ test.register_message_test( direction = "send", message = mock_device_sengled:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -120,6 +126,9 @@ test.register_message_test( direction = "send", message = mock_device_nyce:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua index 0ee62b73ee..62151b9baa 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua @@ -54,7 +54,11 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } + ) test.register_message_test( "Temperature report should be handled (C)", @@ -78,6 +82,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -96,7 +103,11 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" }))) mock_device:expect_native_attr_handler_registration("temperatureMeasurement", "temperature") test.wait_for_events() - end + end, + { + min_api_version = 19 + } + ) 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 751fc1bee9..6c628b7047 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 @@ -59,6 +59,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -78,6 +81,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -94,6 +100,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -110,6 +119,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -123,7 +135,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, move_command }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(10))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -136,7 +151,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, move_command }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(100))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -148,7 +166,10 @@ test.register_coroutine_test( step_command.body.zcl_header.frame_ctrl = frm_ctrl test.socket.zigbee:__queue_receive({ mock_device.id, step_command }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(90))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -160,7 +181,10 @@ test.register_coroutine_test( step_command.body.zcl_header.frame_ctrl = frm_ctrl test.socket.zigbee:__queue_receive({ mock_device.id, step_command }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(100))) - end + end, + { + min_api_version = 19 + } ) @@ -174,7 +198,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.held( { state_change = true } ))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -187,7 +214,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed( { state_change = true } ))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -214,7 +244,10 @@ test.register_coroutine_test( Scenes.ID) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -224,7 +257,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, OnOff.server.commands.On.build_test_rx(mock_device) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(10))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -238,7 +274,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, step_command }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(0))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -252,7 +291,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, step_command }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(100))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -264,7 +306,10 @@ test.register_coroutine_test( test.mock_time.advance_time(1) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(50))) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -277,7 +322,10 @@ test.register_coroutine_test( test.mock_time.advance_time(1) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(30))) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -291,7 +339,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua index 243f55946688f45d6e8bfe18217e6f2cf025daf2..a5729b649223637c7620d064eba82a049bfd19a0 100644 GIT binary patch delta 1364 zcmdmZoALT%#tln(BE>NHRSHp1fU2%@hzeW0p*0A}m_;ji( z7Mr}+UU>2val_49WfNG4b^GQ_rFX=HAlRduSF4bq_^sMoqDn)s=mVMVFuB?! zX0yL-3{f_0-fMr0D1~5mA9OlTtYYEKTU{3st5|4rwMQE#I4{Br0qn&9Fh5RCDUk delta 278 zcmccqm~rE6#tln(CT~?$o_v;%Z}U%{K1L8{^I5+62*&0Tq3ujS;gHGeB{(*(672(X zHm{eMj=~U{e9u65^BlQ}ESm$AotQRnRds^M=V`nJ85XknlGZ!0sgr-|iENhAoe8nx zr``q-Lum6ogB47Z=Ub|7K4&})#Bkqy&TJji=J}QttU#XD<`nw}AYrY^DfSwhcRI}i zb2cwkTUd3&J(0NKKGuK)l5 diff --git a/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua index 1a61602c1e..b7994fb30b 100644 --- a/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua +++ b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua @@ -48,6 +48,9 @@ test.register_message_test( message = { mock_base_device.id, Level.server.commands.MoveToLevelWithOnOff (mock_base_device, 254, 0) } } + }, + { + min_api_version = 19 } ) @@ -66,6 +69,9 @@ test.register_message_test( message = { mock_base_device.id, Level.server.commands.MoveToLevelWithOnOff (mock_base_device, 127, 0) } } + }, + { + min_api_version = 19 } ) @@ -84,6 +90,9 @@ test.register_message_test( message = { mock_base_device.id, Level.server.commands.MoveToLevelWithOnOff (mock_base_device, 127, 0) } } + }, + { + min_api_version = 19 } ) @@ -102,6 +111,9 @@ test.register_message_test( message = { mock_base_device.id, Level.server.commands.MoveToLevelWithOnOff (mock_base_device, 0, 0) } } + }, + { + min_api_version = 19 } ) @@ -124,6 +136,9 @@ test.register_message_test( direction = "send", message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } } + }, + { + min_api_version = 19 } ) @@ -146,6 +161,9 @@ test.register_message_test( direction = "send", message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } } + }, + { + min_api_version = 19 } ) @@ -163,6 +181,9 @@ test.register_message_test( direction = "send", message = mock_base_device:generate_test_message("light", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -180,6 +201,9 @@ test.register_message_test( direction = "send", message = mock_base_device:generate_test_message("light", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -197,6 +221,9 @@ test.register_message_test( direction = "send", message = mock_base_device:generate_test_message("light", capabilities.switchLevel.level(100)) } + }, + { + min_api_version = 19 } ) @@ -214,6 +241,9 @@ test.register_message_test( direction = "send", message = mock_base_device:generate_test_message("light", capabilities.switchLevel.level(50)) } + }, + { + min_api_version = 19 } ) @@ -231,6 +261,9 @@ test.register_message_test( direction = "send", message = mock_base_device:generate_test_message("light", capabilities.switchLevel.level(0)) } + }, + { + min_api_version = 19 } ) @@ -252,6 +285,9 @@ test.register_message_test( direction = "send", message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } } + }, + { + min_api_version = 19 } ) @@ -273,6 +309,9 @@ test.register_message_test( direction = "send", message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } } + }, + { + min_api_version = 19 } ) @@ -294,6 +333,9 @@ test.register_message_test( direction = "send", message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } } + }, + { + min_api_version = 19 } ) @@ -321,6 +363,9 @@ test.register_message_test( direction = "send", message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(1)) } + }, + { + min_api_version = 19 } ) @@ -348,6 +393,9 @@ test.register_message_test( direction = "send", message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(2)) } + }, + { + min_api_version = 19 } ) @@ -375,6 +423,9 @@ test.register_message_test( direction = "send", message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(3)) } + }, + { + min_api_version = 19 } ) @@ -402,6 +453,9 @@ test.register_message_test( direction = "send", message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(4)) } + }, + { + min_api_version = 19 } ) @@ -429,6 +483,9 @@ test.register_message_test( direction = "send", message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(0)) } + }, + { + min_api_version = 19 } ) @@ -450,6 +507,9 @@ test.register_message_test( direction = "send", message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } } + }, + { + min_api_version = 19 } ) @@ -471,6 +531,9 @@ test.register_message_test( direction = "send", message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } } + }, + { + min_api_version = 19 } ) 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 5ced35a0d3..8915a9c87c 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 @@ -94,7 +94,10 @@ test.register_coroutine_test( PowerConfiguration.attributes.BatteryVoltage:configure_reporting(mock_device, 30, 3600, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -114,6 +117,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 79 })) } + }, + { + min_api_version = 19 } ) @@ -134,6 +140,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 0 })) } + }, + { + min_api_version = 19 } ) @@ -154,6 +163,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 100 })) } + }, + { + min_api_version = 19 } ) @@ -182,6 +194,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -202,7 +217,10 @@ test.register_coroutine_test( capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" }))) mock_device:expect_native_attr_handler_registration("temperatureMeasurement", "temperature") test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -221,6 +239,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.batteryLevel.battery.normal()) } + }, + { + min_api_version = 19 } ) @@ -240,6 +261,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.batteryLevel.battery.critical()) } + }, + { + min_api_version = 19 } ) @@ -259,6 +283,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.batteryLevel.battery.warning()) } + }, + { + min_api_version = 19 } ) @@ -278,7 +305,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.battery("normal"))) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 c9b469376c..d83cf90b35 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 @@ -94,7 +94,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -183,7 +184,10 @@ test.register_coroutine_test( ) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) local function build_test_attr_report(device, value) @@ -225,6 +229,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 75 })) } + }, + { + min_api_version = 19 } ) 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 35f557e850..f3da0ff49d 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 @@ -65,7 +65,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -111,7 +112,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 index 063afbdf9f..8e34296f85 100644 --- 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 @@ -84,7 +84,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -101,6 +102,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -117,6 +121,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -212,7 +219,10 @@ test.register_coroutine_test( mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -231,6 +241,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 65 })) } + }, + { + min_api_version = 19 } ) @@ -247,6 +260,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -280,7 +296,10 @@ test.register_coroutine_test( ) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -308,7 +327,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -323,7 +343,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tvocMeasurement.tvocLevel({ value = 0, unit = "ppb" }))) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) 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 7d925ae94d..ea610d0550 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 @@ -66,7 +66,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -83,6 +84,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -99,6 +103,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -158,7 +165,10 @@ test.register_coroutine_test( TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -177,6 +187,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 65 })) } + }, + { + min_api_version = 19 } ) @@ -201,6 +214,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -250,7 +266,10 @@ test.register_coroutine_test( TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) 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 773bd3e9e4..276853904f 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 @@ -65,7 +65,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -122,7 +123,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 f914f0a898..9a54d39994 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 @@ -46,6 +46,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 50.0 })) } + }, + { + min_api_version = 19 } ) @@ -65,6 +68,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 0.0 })) } + }, + { + min_api_version = 19 } ) @@ -84,6 +90,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 100.0 })) } + }, + { + min_api_version = 19 } ) @@ -108,7 +117,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 6bca6c4e1a..078b7a9d58 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 @@ -49,6 +49,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 79 })) } + }, + { + min_api_version = 19 } ) @@ -68,6 +71,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 0 })) } + }, + { + min_api_version = 19 } ) @@ -87,6 +93,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 100 })) } + }, + { + min_api_version = 19 } ) @@ -114,6 +123,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -137,7 +149,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc))) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -155,7 +170,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" }))) mock_device:expect_native_attr_handler_registration("temperatureMeasurement", "temperature") test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -190,6 +208,9 @@ test.register_message_test( TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) } }, + }, + { + min_api_version = 19 } ) @@ -227,6 +248,9 @@ test.register_message_test( TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) } }, + }, + { + min_api_version = 19 } ) @@ -261,7 +285,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 6de1dceb18..a137b643c7 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 @@ -46,6 +46,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -73,6 +76,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 20.00, maximum = 30.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -92,6 +98,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 79 })) } + }, + { + min_api_version = 19 } ) @@ -126,7 +135,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -157,7 +169,10 @@ test.register_coroutine_test( } ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 79 }))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 341cd4bc20..a409ed903a 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 @@ -58,6 +58,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -85,6 +88,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 20.00, maximum = 30.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -104,6 +110,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 79 })) } + }, + { + min_api_version = 19 } ) @@ -146,7 +155,10 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -177,7 +189,10 @@ test.register_coroutine_test( } ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 79 }))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 4282db0ffd..b8d7f05920 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 @@ -53,6 +53,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -80,6 +83,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 20.00, maximum = 30.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -99,6 +105,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 79 })) } + }, + { + min_api_version = 19 } ) @@ -133,7 +142,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 92e93119f5..0e8e3889f5 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 @@ -44,6 +44,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 137 })) } + }, + { + min_api_version = 19 } ) @@ -63,6 +66,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -98,7 +104,10 @@ test.register_coroutine_test( ) 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 + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua index 81fee9249f..a532f6e4d8 100644 --- a/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua +++ b/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua @@ -57,7 +57,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", detectionFrequency.detectionFrequency(FREQUENCY_DEFAULT_VALUE, {visibility = {displayed = false}}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -95,7 +98,10 @@ test.register_coroutine_test( , data_types.Uint8, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -115,6 +121,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 137 })) } + }, + { + min_api_version = 19 } ) @@ -134,6 +143,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -169,7 +181,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, FREQUENCY_ATTRIBUTE_ID, MFG_CODE, data_types.Uint16, frequency) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -180,7 +195,10 @@ test.register_coroutine_test( build_write_attr_res(PRIVATE_CLUSTER_ID, 0x00) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", detectionFrequency.detectionFrequency(FREQUENCY_DEFAULT_VALUE, { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua index 146c628b8b..66b6b06d34 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua @@ -39,7 +39,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -70,7 +73,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -80,7 +86,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -90,7 +99,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "lock", component = "main", command = "lock", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.commands.LockDoor(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -100,7 +112,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "lock", component = "main", command = "unlock", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.commands.UnlockDoor(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -112,7 +127,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(5) test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -124,14 +142,20 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(5) test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "PinUsersSupported report should be a no-op", function () test.socket.zigbee:__queue_receive({ mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device, 8)}) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua index f287300f60..afae23fd9c 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua @@ -31,7 +31,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device)}) test.socket.zigbee:__queue_receive({mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device, 8)}) mock_device:expect_metadata_update({profile = "base-lock"}) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua index 4f50c3c24a..6f74c5c0bf 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua @@ -44,6 +44,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(55)) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua index 2fd55fd3f0..2ab9f45a65 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua @@ -75,7 +75,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) expect_reload_all_codes_messages() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -87,7 +90,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device)}) test.socket.zigbee:__expect_send({mock_device.id, DoorLock.attributes.LockState:read(mock_device)}) test.socket.zigbee:__expect_send({mock_device.id, Alarm.attributes.AlarmCount:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -104,6 +110,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked()) } + }, + { + min_api_version = 19 } ) @@ -121,6 +130,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -145,6 +157,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "manual" } })) } + }, + { + min_api_version = 19 } ) @@ -175,6 +190,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["2"] = "Code 2"} ), { visibility = { displayed = false } })) } + }, + { + min_api_version = 19 } ) @@ -191,6 +209,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, DoorLock.server.commands.LockDoor(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -207,6 +228,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lockCodes.minCodeLength(4, { visibility = { displayed = false }})) } + }, + { + min_api_version = 19 } ) @@ -223,6 +247,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lockCodes.maxCodeLength(4, { visibility = { displayed = false }})) } + }, + { + min_api_version = 19 } ) @@ -240,6 +267,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lockCodes.maxCodes(16, { visibility = { displayed = false }})) } + }, + { + min_api_version = 19 } ) @@ -248,7 +278,10 @@ test.register_coroutine_test( function() test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "reloadAllCodes", args = {} } }) expect_reload_all_codes_messages() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -264,6 +297,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 1) } } + }, + { + min_api_version = 19 } ) @@ -303,7 +339,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({} ), { visibility = { displayed = false } }) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -350,7 +389,10 @@ test.register_coroutine_test( capabilities.lockCodes.codeChanged("1 set", { data = { codeName = "test" }, state_change = true }))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "test"}), { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) local function init_code_slot(slot_number, name, device) @@ -405,7 +447,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 renamed", {state_change = true}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -420,7 +465,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 renamed", {state_change = true}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -491,7 +539,10 @@ test.register_coroutine_test( DoorLock.server.commands.GetPINCode(mock_device, 4) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -523,7 +574,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 4) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -555,6 +609,9 @@ test.register_message_test( capabilities.lockCodes.codeChanged("0 set", { data = { codeName = "Master Code"}, state_change = true }) ) } + }, + { + min_api_version = 19 } ) @@ -591,6 +648,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } })) } + }, + { + min_api_version = 19 } ) @@ -623,7 +683,10 @@ test.register_coroutine_test( ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({}), { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -677,7 +740,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({}), { visibility = { displayed = false } }))) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -705,7 +771,10 @@ test.register_coroutine_test( capabilities.lock.lock.unlocked({ data = { method = "keypad", codeId = "1", codeName = "Code 1" } }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -768,7 +837,10 @@ test.register_coroutine_test( capabilities.lock.lock.locked() ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -784,6 +856,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unknown()) } + }, + { + min_api_version = 19 } ) @@ -800,6 +875,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unknown()) } + }, + { + min_api_version = 19 } ) @@ -825,6 +903,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("5 unset", { data = { codeName = "Code 5" }, state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -852,7 +933,10 @@ test.register_coroutine_test( capabilities.lockCodes.codeChanged("1 changed", { data = { codeName = "Code 1" }, state_change = true }))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua index 7950e3f62d..c1ab2927dc 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua @@ -61,7 +61,10 @@ test.register_coroutine_test( ) -- Validate migration complete flag mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -79,7 +82,10 @@ test.register_coroutine_test( mock_datastore.__assert_device_store_contains(mock_device.id, "__state_cache", nil) -- Validate migration complete flag mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", nil) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -121,7 +127,10 @@ test.register_coroutine_test( ) -- Validate migration complete flag mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -163,7 +172,10 @@ test.register_coroutine_test( ) -- Validate migration complete flag mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "migrationComplete", true) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -226,7 +238,10 @@ test.register_coroutine_test( -- Verify the timer doesn't fire as it wasn't created test.mock_time.advance_time(4) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_v10.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_v10.lua index c4e28dcd0d..57438c7f77 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_v10.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_v10.lua @@ -79,7 +79,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) expect_reload_all_codes_messages() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -91,7 +94,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device)}) test.socket.zigbee:__expect_send({mock_device.id, DoorLock.attributes.LockState:read(mock_device)}) test.socket.zigbee:__expect_send({mock_device.id, Alarm.attributes.AlarmCount:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -108,6 +114,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked()) } + }, + { + min_api_version = 19 } ) @@ -125,6 +134,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -149,6 +161,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "manual" } })) } + }, + { + min_api_version = 19 } ) @@ -179,6 +194,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["2"] = "Code 2"} ), { visibility = { displayed = false } })) } + }, + { + min_api_version = 19 } ) @@ -195,6 +213,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, DoorLock.server.commands.LockDoor(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -211,6 +232,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lockCodes.minCodeLength(4, { visibility = { displayed = false }})) } + }, + { + min_api_version = 19 } ) @@ -227,6 +251,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lockCodes.maxCodeLength(4, { visibility = { displayed = false }})) } + }, + { + min_api_version = 19 } ) @@ -244,6 +271,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lockCodes.maxCodes(16, { visibility = { displayed = false }})) } + }, + { + min_api_version = 19 } ) @@ -252,7 +282,10 @@ test.register_coroutine_test( function() test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "reloadAllCodes", args = {} } }) expect_reload_all_codes_messages() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -268,6 +301,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 1) } } + }, + { + min_api_version = 19 } ) @@ -307,7 +343,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({} ), { visibility = { displayed = false } }) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -354,7 +393,10 @@ test.register_coroutine_test( capabilities.lockCodes.codeChanged("1 set", { data = { codeName = "test" }, state_change = true }))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "test"}), { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) local function init_code_slot(slot_number, name, device) @@ -409,7 +451,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 renamed", {state_change = true}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -424,7 +469,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 renamed", {state_change = true}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -495,7 +543,10 @@ test.register_coroutine_test( DoorLock.server.commands.GetPINCode(mock_device, 4) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -527,7 +578,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 4) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -559,6 +613,9 @@ test.register_message_test( capabilities.lockCodes.codeChanged("0 set", { data = { codeName = "Master Code"}, state_change = true }) ) } + }, + { + min_api_version = 19 } ) @@ -595,6 +652,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } })) } + }, + { + min_api_version = 19 } ) @@ -627,7 +687,10 @@ test.register_coroutine_test( ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({}), { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -681,7 +744,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({}), { visibility = { displayed = false } }))) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -709,7 +775,10 @@ test.register_coroutine_test( capabilities.lock.lock.unlocked({ data = { method = "keypad", codeId = "1", codeName = "Code 1" } }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -772,7 +841,10 @@ test.register_coroutine_test( capabilities.lock.lock.locked() ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_samsungsds.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_samsungsds.lua index bac1554790..e421ab950e 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_samsungsds.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_samsungsds.lua @@ -74,7 +74,10 @@ test.register_coroutine_test( ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -91,6 +94,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked()) } + }, + { + min_api_version = 19 } ) @@ -108,6 +114,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked()) } + }, + { + min_api_version = 19 } ) @@ -120,6 +129,9 @@ test.register_message_test( message = { mock_device.id, DoorLock.attributes.LockState:build_test_attr_report(mock_device, DoorLockState.NOT_FULLY_LOCKED) } } + }, + { + min_api_version = 19 } ) @@ -146,6 +158,9 @@ test.register_message_test( capabilities.lock.lock.locked({ data = { method = "keypad"} }) ) } + }, + { + min_api_version = 19 } ) @@ -172,6 +187,9 @@ test.register_message_test( capabilities.lock.lock.unlocked({ data = { method = "keypad"} }) ) } + }, + { + min_api_version = 19 } ) @@ -198,6 +216,9 @@ test.register_message_test( capabilities.lock.lock.locked({ data = { method = "keypad"} }) ) } + }, + { + min_api_version = 19 } ) @@ -224,6 +245,9 @@ test.register_message_test( capabilities.lock.lock.locked({ data = { method = "keypad"} }) ) } + }, + { + min_api_version = 19 } ) @@ -250,6 +274,9 @@ test.register_message_test( capabilities.lock.lock.unlocked({ data = { method = "keypad"} }) ) } + }, + { + min_api_version = 19 } ) @@ -276,6 +303,9 @@ test.register_message_test( capabilities.lock.lock.locked({ data = { method = "keypad"} }) ) } + }, + { + min_api_version = 19 } ) @@ -302,6 +332,9 @@ test.register_message_test( capabilities.lock.lock.locked({data = { method = "keypad"} }) ) } + }, + { + min_api_version = 19 } ) @@ -328,6 +361,9 @@ test.register_message_test( capabilities.lock.lock.unlocked({ data = { method = "keypad"} }) ) } + }, + { + min_api_version = 19 } ) @@ -352,6 +388,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "command" } })) } + }, + { + min_api_version = 19 } ) @@ -376,6 +415,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "command" } })) } + }, + { + min_api_version = 19 } ) @@ -400,6 +442,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "command" } })) } + }, + { + min_api_version = 19 } ) @@ -424,6 +469,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "command" } })) } + }, + { + min_api_version = 19 } ) @@ -448,6 +496,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "command" } })) } + }, + { + min_api_version = 19 } ) @@ -472,6 +523,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "auto" } })) } + }, + { + min_api_version = 19 } ) @@ -496,6 +550,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "command" } })) } + }, + { + min_api_version = 19 } ) @@ -520,6 +577,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "command" } })) } + }, + { + min_api_version = 19 } ) @@ -544,6 +604,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "manual" } })) } + }, + { + min_api_version = 19 } ) @@ -568,6 +631,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "manual" } })) } + }, + { + min_api_version = 19 } ) @@ -592,6 +658,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "manual" } })) } + }, + { + min_api_version = 19 } ) @@ -616,6 +685,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "manual" } })) } + }, + { + min_api_version = 19 } ) @@ -640,6 +712,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "manual" } })) } + }, + { + min_api_version = 19 } ) @@ -664,6 +739,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "auto" } })) } + }, + { + min_api_version = 19 } ) @@ -688,6 +766,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "manual" } })) } + }, + { + min_api_version = 19 } ) @@ -712,6 +793,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "manual" } })) } + }, + { + min_api_version = 19 } ) @@ -736,6 +820,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "rfid" } })) } + }, + { + min_api_version = 19 } ) @@ -760,6 +847,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "rfid" } })) } + }, + { + min_api_version = 19 } ) @@ -784,6 +874,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "rfid" } })) } + }, + { + min_api_version = 19 } ) @@ -808,6 +901,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "rfid" } })) } + }, + { + min_api_version = 19 } ) @@ -832,6 +928,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "rfid" } })) } + }, + { + min_api_version = 19 } ) @@ -856,6 +955,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "auto" } })) } + }, + { + min_api_version = 19 } ) @@ -880,6 +982,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "rfid" } })) } + }, + { + min_api_version = 19 } ) @@ -904,6 +1009,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "rfid" } })) } + }, + { + min_api_version = 19 } ) @@ -928,6 +1036,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "fingerprint" } })) } + }, + { + min_api_version = 19 } ) @@ -952,6 +1063,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "fingerprint" } })) } + }, + { + min_api_version = 19 } ) @@ -976,6 +1090,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "fingerprint" } })) } + }, + { + min_api_version = 19 } ) @@ -1000,6 +1117,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "fingerprint" } })) } + }, + { + min_api_version = 19 } ) @@ -1024,6 +1144,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "fingerprint" } })) } + }, + { + min_api_version = 19 } ) @@ -1048,6 +1171,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "auto" } })) } + }, + { + min_api_version = 19 } ) @@ -1072,6 +1198,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "fingerprint" } })) } + }, + { + min_api_version = 19 } ) @@ -1096,6 +1225,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "fingerprint" } })) } + }, + { + min_api_version = 19 } ) @@ -1120,6 +1252,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "bluetooth" } })) } + }, + { + min_api_version = 19 } ) @@ -1144,6 +1279,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "bluetooth" } })) } + }, + { + min_api_version = 19 } ) @@ -1168,6 +1306,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "bluetooth" } })) } + }, + { + min_api_version = 19 } ) @@ -1192,6 +1333,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "bluetooth" } })) } + }, + { + min_api_version = 19 } ) @@ -1216,6 +1360,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "bluetooth" } })) } + }, + { + min_api_version = 19 } ) @@ -1240,6 +1387,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "auto" } })) } + }, + { + min_api_version = 19 } ) @@ -1264,6 +1414,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "bluetooth" } })) } + }, + { + min_api_version = 19 } ) @@ -1288,6 +1441,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({ data = { method = "bluetooth" } })) } + }, + { + min_api_version = 19 } ) @@ -1308,7 +1464,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -1327,6 +1486,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked()) }, + }, + { + min_api_version = 19 } ) @@ -1341,6 +1503,9 @@ test.register_message_test( zigbee_test_utils.build_custom_command_id(mock_device, DoorLock.ID, SAMSUNG_SDS_MFR_SPECIFIC_COMMAND, SAMSUNG_SDS_MFR_CODE, "") } } + }, + { + min_api_version = 19 } ) @@ -1358,7 +1523,10 @@ test.register_coroutine_test( zigbee_test_utils.build_tx_custom_command_id(mock_device, DoorLock.ID, SAMSUNG_SDS_MFR_SPECIFIC_COMMAND, SAMSUNG_SDS_MFR_CODE, "1235") }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1371,7 +1539,10 @@ test.register_coroutine_test( } ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1388,7 +1559,10 @@ test.register_coroutine_test( test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua index b8f4c386d9..41445875ed 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua @@ -32,6 +32,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(55)) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua index 7cda71cdb3..9962cf59c5 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua @@ -32,6 +32,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lockCodes.maxCodes(30)) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua index 931a4b143c..40d27348f6 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua @@ -45,7 +45,10 @@ test.register_coroutine_test( function() test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "reloadAllCodes", args = {} } }) expect_reload_all_codes_messages() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -84,7 +87,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) expect_reload_all_codes_messages() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -131,7 +137,10 @@ test.register_coroutine_test( ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "test"}), { visibility = { displayed = false }}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -178,7 +187,10 @@ test.register_coroutine_test( ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["0"] = "test"}), { visibility = { displayed = false }}))) - end + end, + { + min_api_version = 19 + } ) @@ -204,6 +216,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("0 unset", { data = { codeName = "Code 0" }, state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -259,7 +274,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 renamed", {state_change = true}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false }}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -301,7 +319,10 @@ test.register_coroutine_test( ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 failed", { state_change = true }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -316,7 +337,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 renamed", {state_change = true}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false }}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -356,7 +380,10 @@ test.register_coroutine_test( capabilities.lockCodes.codeChanged("1 failed", { state_change = true }))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 is not set", { state_change = true }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -383,7 +410,10 @@ test.register_coroutine_test( capabilities.lockCodes.codeChanged("1 changed", { data = { codeName = "initialName" }, state_change = true }))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["1"] = "initialName"}), { visibility = { displayed = false }}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -410,7 +440,10 @@ test.register_coroutine_test( capabilities.lockCodes.codeChanged("1 deleted", { data = { codeName = "initialName" }, state_change = true }))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({}), { visibility = { displayed = false }}))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 948d433c9f..088e211092 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 @@ -46,6 +46,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "motionSensor", capability_attr_id = "motion" } } }, + }, + { + min_api_version = 19 } ) @@ -70,6 +73,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "motionSensor", capability_attr_id = "motion" } } }, + }, + { + min_api_version = 19 } ) @@ -94,6 +100,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "motionSensor", capability_attr_id = "motion" } } }, + }, + { + min_api_version = 19 } ) @@ -118,6 +127,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "motionSensor", capability_attr_id = "motion" } } }, + }, + { + min_api_version = 19 } ) @@ -142,6 +154,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -165,6 +180,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 20.00, maximum = 30.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -181,6 +199,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 79 })) } + }, + { + min_api_version = 19 } ) @@ -197,6 +218,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -303,7 +327,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -388,7 +415,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -407,7 +435,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" }))) mock_device:expect_native_attr_handler_registration("temperatureMeasurement", "temperature") test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -424,7 +455,10 @@ test.register_coroutine_test( ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 79 }))) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 e7bfcbf642..79bbd917c5 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 @@ -71,7 +71,10 @@ test.register_coroutine_test( sensitivityAdjustment.sensitivityAdjustment.Medium())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -91,7 +94,10 @@ test.register_coroutine_test( , data_types.Uint8, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -112,7 +118,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -134,7 +143,10 @@ test.register_coroutine_test( test.mock_time.advance_time(detect_duration) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) - end + end, + { + min_api_version = 19 + } ) local function build_write_attr_res(cluster, status) @@ -174,7 +186,10 @@ test.register_coroutine_test( local value = mock_device:get_field(PREF_CHANGED_VALUE) or 0 test.socket.capability:__expect_send(mock_device:generate_test_message("main", detectionFrequency.detectionFrequency(value, {visibility = {displayed = false}}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -188,7 +203,10 @@ test.register_coroutine_test( mock_device:set_field(PREF_CHANGED_VALUE, PREF_SENSITIVITY_VALUE_HIGH) test.socket.capability:__expect_send(mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.High())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -202,7 +220,10 @@ test.register_coroutine_test( mock_device:set_field(PREF_CHANGED_VALUE, PREF_SENSITIVITY_VALUE_MEDIUM) test.socket.capability:__expect_send(mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.Medium())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -216,7 +237,10 @@ test.register_coroutine_test( mock_device:set_field(PREF_CHANGED_VALUE, PREF_SENSITIVITY_VALUE_LOW) test.socket.capability:__expect_send(mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.Low())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -251,7 +275,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, SENSITIVITY_ATTRIBUTE_ID, MFG_CODE , data_types.Uint8, PREF_SENSITIVITY_VALUE_LOW) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 c8bb2c20f0..2e4cf1993f 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 @@ -86,7 +86,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", detectionFrequency.detectionFrequency(PREF_FREQUENCY_VALUE_DEFAULT, {visibility = {displayed = false}}))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -106,7 +109,10 @@ test.register_coroutine_test( , data_types.Uint8, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -131,7 +137,10 @@ test.register_coroutine_test( test.mock_time.advance_time(detect_duration) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -147,7 +156,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, FREQUENCY_ATTRIBUTE_ID, MFG_CODE , data_types.Uint8, PREF_FREQUENCY_VALUE_DEFAULT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -188,7 +200,10 @@ test.register_coroutine_test( test.mock_time.advance_time(detect_duration) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -202,7 +217,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", detectionFrequency.detectionFrequency(PREF_FREQUENCY_VALUE_DEFAULT, {visibility = {displayed = false}}))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 1a5a292fc3..eb2a8c3adc 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 @@ -54,6 +54,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "motionSensor", capability_attr_id = "motion" } } }, + }, + { + min_api_version = 19 } ) @@ -78,6 +81,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "motionSensor", capability_attr_id = "motion" } } }, + }, + { + min_api_version = 19 } ) @@ -102,6 +108,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "motionSensor", capability_attr_id = "motion" } } }, + }, + { + min_api_version = 19 } ) @@ -126,6 +135,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "motionSensor", capability_attr_id = "motion" } } }, + }, + { + min_api_version = 19 } ) @@ -162,7 +174,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -204,7 +219,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 737a594406..31ebd1c418 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 @@ -50,7 +50,11 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } + ) test.run_registered_tests() 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 1e35976fac..c6e49e32b6 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 @@ -69,7 +69,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -92,7 +95,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device_old_firmware:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 0660661d80..e8baf7897f 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 @@ -75,7 +75,10 @@ test.register_coroutine_test( TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 100):to_endpoint(0x03) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -96,7 +99,10 @@ test.register_coroutine_test( mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 3e3acf69e2..e50eeac530 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 @@ -69,6 +69,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(14)) } + }, + { + min_api_version = 19 } ) @@ -79,7 +82,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device):to_endpoint(POWER_CONFIGURATION_ENDPOINT)}) test.socket.zigbee:__expect_send({ mock_device.id, OccupancySensing.attributes.Occupancy:read(mock_device):to_endpoint(OCCUPANCY_ENDPOINT)}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -206,7 +212,10 @@ test.register_coroutine_test( mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device):to_endpoint(POWER_CONFIGURATION_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -222,6 +231,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -238,6 +250,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -267,7 +282,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OccupancySensing.attributes.PIROccupiedToUnoccupiedDelay:write(mock_device, 200):to_endpoint(OCCUPANCY_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 6869412d49..0686d410cd 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 @@ -77,7 +77,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -93,6 +96,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(14)) } + }, + { + min_api_version = 19 } ) @@ -117,6 +123,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -136,6 +145,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 137 })) } + }, + { + min_api_version = 19 } ) @@ -187,7 +199,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, OccupancySensing.attributes.Occupancy:read(mock_device):to_endpoint(OCCUPANCY_ENDPOINT)}) test.socket.zigbee:__expect_send({mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(TEMPERATURE_MEASUREMENT_ENDPOINT)}) test.socket.zigbee:__expect_send({mock_device.id, IlluminanceMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(ILLUMINANCE_ENDPOINT)}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -342,7 +357,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -382,7 +400,10 @@ test.register_coroutine_test( temperatureSensitivity ):to_endpoint(TEMPERATURE_MEASUREMENT_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 1f72d365e6..dee4d52657 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 @@ -80,7 +80,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -96,6 +99,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(14)) } + }, + { + min_api_version = 19 } ) @@ -113,6 +119,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -130,6 +139,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -154,6 +166,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -173,6 +188,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 137 })) } + }, + { + min_api_version = 19 } ) @@ -186,7 +204,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, OccupancySensing.attributes.Occupancy:read(mock_device):to_endpoint(OCCUPANCY_ENDPOINT)}) test.socket.zigbee:__expect_send({mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(TEMPERATURE_MEASUREMENT_ENDPOINT)}) test.socket.zigbee:__expect_send({mock_device.id, IlluminanceMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(ILLUMINANCE_ENDPOINT)}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -344,7 +365,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -384,7 +408,10 @@ test.register_coroutine_test( temperatureSensitivity ):to_endpoint(TEMPERATURE_MEASUREMENT_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -401,6 +428,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -418,6 +448,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) 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 3afdbbcaa2..3635f43f76 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 @@ -44,7 +44,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) test.mock_time.advance_time(120) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -60,7 +63,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.presenceSensor.presence.present())) test.mock_time.advance_time(60) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.presenceSensor.presence.not_present())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -73,7 +79,10 @@ test.register_coroutine_test( } ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -86,7 +95,10 @@ test.register_coroutine_test( } ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -99,7 +111,10 @@ test.register_coroutine_test( } ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -112,7 +127,10 @@ test.register_coroutine_test( } ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(0))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -125,7 +143,10 @@ test.register_coroutine_test( } ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(10))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -140,7 +161,10 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) test.wait_for_events() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -156,7 +180,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) test.mock_time.advance_time(120) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 36312c8fdb..f56fb96c1d 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 @@ -58,6 +58,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -97,7 +100,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -117,7 +123,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(180) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -138,7 +147,10 @@ test.register_coroutine_test( } ) test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -197,7 +209,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, Groups.commands.AddGroup(mock_device, 0x0000) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -220,7 +235,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, Groups.commands.AddGroup(mock_device, 0x0000) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -245,7 +263,10 @@ test.register_coroutine_test( -- Only the second timer fires test.mock_time.advance_time(180) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 d966d301f3..09a7355b6a 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 @@ -93,7 +93,10 @@ test.register_coroutine_test( -- zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, IASZone.ID) -- }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -115,7 +118,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 4450761c5d..745639cdf2 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 @@ -44,7 +44,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:configure_reporting(mock_device, 0xFFFF, 0x0000, 0) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -93,7 +96,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 f96322467f..8e15316aa0 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 @@ -67,7 +67,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.lqi(50))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -81,7 +84,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.lqi(50))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -95,7 +101,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.lqi(50))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -109,7 +118,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.lqi(50))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -122,7 +134,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.lqi(50))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -135,7 +150,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.lqi(50))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -149,7 +167,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.lqi(50))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -159,7 +180,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.lqi(0))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({value = -100, unit = 'dBm'}))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 5211ce1982..02bd5bf744 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 @@ -53,7 +53,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 7a1655fc41..16d018d803 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 @@ -68,7 +68,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device1:generate_test_message("main", capabilities.battery.battery(55)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -91,7 +94,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device1:generate_test_message("main", capabilities.battery.battery(100)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -114,7 +120,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device2:generate_test_message("main", capabilities.battery.battery(55)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -137,7 +146,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device2:generate_test_message("main", capabilities.battery.battery(100)) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -156,6 +168,9 @@ test.register_message_test( Basic.attributes.ApplicationVersion:read(mock_device1) } } + }, + { + min_api_version = 19 } ) 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 f0cd1053dc..5dcc3e57a1 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 @@ -54,7 +54,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -139,7 +142,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 e368e852c8..0f25100055 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 @@ -44,6 +44,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -60,6 +63,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -86,7 +92,11 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } + ) test.run_registered_tests() 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 1f586bce88..e6d7fa615d 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 @@ -44,6 +44,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -60,6 +63,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -76,6 +82,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -92,6 +101,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -116,7 +128,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -140,7 +155,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) 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 78634bbaa2..499e96f745 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 @@ -47,6 +47,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -63,6 +66,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -107,6 +113,9 @@ test.register_message_test( OccupancySensing.attributes.Occupancy:read(mock_device) } }, + }, + { + min_api_version = 19 } ) @@ -128,7 +137,10 @@ test.register_coroutine_test( ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter.lua index 5f96805360..1787af3b0d 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter.lua @@ -90,7 +90,8 @@ test.register_coroutine_test( { test_init = function() -- no op to override auto device add on startup - end + end, + min_api_version = 19 } ) @@ -116,7 +117,8 @@ test.register_coroutine_test( { test_init = function() -- no op to override auto device add on startup - end + end, + min_api_version = 19 } ) @@ -141,7 +143,8 @@ test.register_coroutine_test( { test_init = function() -- no op to override auto device add on startup - end + end, + min_api_version = 19 } ) @@ -166,7 +169,8 @@ test.register_coroutine_test( { test_init = function() -- no op to override auto device add on startup - end + end, + min_api_version = 19 } ) @@ -189,6 +193,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 2.7, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -210,6 +217,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.027, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -279,7 +289,10 @@ test.register_coroutine_test( SimpleMetering.attributes.Divisor:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) 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 index a148df5b1e..384e893b30 100644 --- 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 @@ -1,3 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" local ElectricalMeasurement = clusters.ElectricalMeasurement @@ -71,7 +74,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 2.0, unit = "kWh"})) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -88,6 +94,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseA", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -105,6 +114,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseA", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -122,6 +134,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseA", capabilities.currentMeasurement.current({ value = 0.34, unit = "A" })) } + }, + { + min_api_version = 19 } ) @@ -139,6 +154,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseA", capabilities.voltageMeasurement.voltage({ value = 220.0, unit = "V" })) } + }, + { + min_api_version = 19 } ) @@ -220,7 +238,10 @@ test.register_coroutine_test( ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -236,6 +257,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, clusters.OnOff.server.commands.On(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -279,7 +303,10 @@ test.register_coroutine_test( mock_device.id, ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -295,7 +322,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.0, unit = "kWh"})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -315,7 +345,11 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.0, unit = "kWh"})) ) - end + end, + { + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + 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 index b85c955005..b6621e5673 100644 --- 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 @@ -1,3 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" local ElectricalMeasurement = clusters.ElectricalMeasurement @@ -68,7 +71,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 2.0, unit = "kWh"})) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -85,6 +91,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseA", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -102,6 +111,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseA", capabilities.currentMeasurement.current({ value = 0.34, unit = "A" })) } + }, + { + min_api_version = 19 } ) @@ -119,6 +131,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseA", capabilities.voltageMeasurement.voltage({ value = 220.0, unit = "V" })) } + }, + { + min_api_version = 19 } ) @@ -136,6 +151,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseB", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -153,6 +171,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseB", capabilities.currentMeasurement.current({ value = 0.34, unit = "A" })) } + }, + { + min_api_version = 19 } ) @@ -170,6 +191,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseB", capabilities.voltageMeasurement.voltage({ value = 220.0, unit = "V" })) } + }, + { + min_api_version = 19 } ) @@ -275,7 +299,11 @@ test.register_coroutine_test( SimpleMetering.attributes.InstantaneousDemand:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + 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 index fad26aad40..419d737b69 100644 --- 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 @@ -1,3 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" local ElectricalMeasurement = clusters.ElectricalMeasurement @@ -67,7 +70,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 2.0, unit = "kWh"})) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -84,6 +90,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseA", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -101,6 +110,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseA", capabilities.currentMeasurement.current({ value = 0.34, unit = "A" })) } + }, + { + min_api_version = 19 } ) @@ -118,6 +130,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseA", capabilities.voltageMeasurement.voltage({ value = 220.0, unit = "V" })) } + }, + { + min_api_version = 19 } ) @@ -135,6 +150,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseB", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -152,6 +170,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseB", capabilities.currentMeasurement.current({ value = 0.34, unit = "A" })) } + }, + { + min_api_version = 19 } ) @@ -169,6 +190,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseB", capabilities.voltageMeasurement.voltage({ value = 220.0, unit = "V" })) } + }, + { + min_api_version = 19 } ) @@ -186,6 +210,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseC", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -203,6 +230,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseC", capabilities.currentMeasurement.current({ value = 0.34, unit = "A" })) } + }, + { + min_api_version = 19 } ) @@ -220,6 +250,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("PhaseC", capabilities.voltageMeasurement.voltage({ value = 220.0, unit = "V" })) } + }, + { + min_api_version = 19 } ) @@ -349,7 +382,11 @@ test.register_coroutine_test( SimpleMetering.attributes.InstantaneousDemand:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua index 2949961fb1..56dd597d92 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua @@ -98,6 +98,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -140,7 +143,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 2.0, unit = "kWh"})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -201,7 +207,10 @@ test.register_coroutine_test( ElectricalMeasurement.attributes.ACPowerDivisor:configure_reporting(mock_device, 1, 43200, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -226,7 +235,8 @@ test.register_coroutine_test( { test_init = function() -- no op to override auto device add on startup - end + end, + min_api_version = 19 } ) test.register_coroutine_test( @@ -242,7 +252,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 0.1, unit = "kWh"})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -262,7 +275,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.5, unit = "kWh"})) ) - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_ezex.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_ezex.lua index 7f582d55b7..8673e2d468 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_ezex.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_ezex.lua @@ -91,6 +91,9 @@ test.register_message_test( message = { mock_device.id, ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_device, 27) }, } + }, + { + min_api_version = 19 } ) @@ -127,6 +130,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 0.0015, unit = "kWh"})) } + }, + { + min_api_version = 19 } ) @@ -158,7 +164,10 @@ test.register_coroutine_test( SimpleMetering.attributes.CurrentSummationDelivered:configure_reporting(mock_device, 5, 3600, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -183,7 +192,8 @@ test.register_coroutine_test( { test_init = function() -- no op to override auto device add on startup - end + end, + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua index 81caddae56..56b2f45114 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua @@ -39,7 +39,10 @@ test.register_coroutine_test( "SIMPLE_METERING_DIVISOR_KEY should be 1000") assert(mock_device:get_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY) == 10000, "ELECTRICAL_MEASUREMENT_DIVISOR_KEY should be 10000") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -96,7 +99,10 @@ test.register_coroutine_test( ElectricalMeasurement.attributes.ACPowerDivisor:configure_reporting(mock_device, 1, 43200, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/test/test_aqara_presence_sensor_fp1.lua b/drivers/SmartThings/zigbee-presence-sensor/src/test/test_aqara_presence_sensor_fp1.lua index 56740db3c8..dc630a78e4 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/test/test_aqara_presence_sensor_fp1.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/test/test_aqara_presence_sensor_fp1.lua @@ -1,3 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local cluster_base = require "st.zigbee.cluster_base" local t_utils = require "integration_test.utils" @@ -58,7 +61,10 @@ test.register_coroutine_test( data_types.Uint8, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -83,7 +89,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x010C, MFG_CODE, data_types.Uint8, updates.preferences["stse.sensitivity"]) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -108,7 +117,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, RESET_MODE, MFG_CODE, data_types.Uint8, 0x01) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -133,7 +145,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0146, MFG_CODE, data_types.Uint8, updates.preferences["stse.approachDistance"]) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -148,7 +163,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", PresenceSensor.presence("present"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -163,7 +181,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", PresenceSensor.presence("not present"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -183,7 +204,10 @@ test.register_coroutine_test( test.mock_time.advance_time(movement_timer) test.socket.capability:__expect_send(mock_device:generate_test_message("main", MovementSensor.movement("noMovement"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -203,7 +227,10 @@ test.register_coroutine_test( test.mock_time.advance_time(movement_timer) test.socket.capability:__expect_send(mock_device:generate_test_message("main", MovementSensor.movement("noMovement"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -223,7 +250,10 @@ test.register_coroutine_test( test.mock_time.advance_time(movement_timer) test.socket.capability:__expect_send(mock_device:generate_test_message("main", MovementSensor.movement("noMovement"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -243,7 +273,10 @@ test.register_coroutine_test( test.mock_time.advance_time(movement_timer) test.socket.capability:__expect_send(mock_device:generate_test_message("main", MovementSensor.movement("noMovement"))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 123edc9de1..8ecc7f1ff1 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 @@ -96,7 +96,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(7) end - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -114,7 +117,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -123,7 +127,10 @@ test.register_coroutine_test( function () add_device() add_device_after_switch_over() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -135,7 +142,10 @@ test.register_coroutine_test( }) test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -148,7 +158,10 @@ test.register_coroutine_test( test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.battery.battery(100))) test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -161,7 +174,10 @@ test.register_coroutine_test( test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.battery.battery(75))) test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -174,7 +190,10 @@ test.register_coroutine_test( test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.battery.battery(0))) test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -188,7 +207,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("not present")) ) end, { - test_init = function() end + test_init = function() end, min_api_version = 19 } ) 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 ca98e8a1a7..ba6defe3db 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 @@ -78,7 +78,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -95,6 +96,9 @@ test.register_message_test( direction = "send", message = { mock_simple_device.id, IdentifyCluster.server.commands.Identify(mock_simple_device, 0x05) } } + }, + { + min_api_version = 19 } ) @@ -146,7 +150,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence.present())) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -179,7 +186,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_simple_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -187,7 +197,10 @@ test.register_coroutine_test( function () add_device() add_device_after_switch_over() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -201,7 +214,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("not present")) ) end, { - test_init = function() end + test_init = function() end, min_api_version = 19 } ) @@ -230,7 +243,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(121) test.socket.capability:__expect_send( mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("not present")) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -262,7 +278,10 @@ test.register_coroutine_test( test.mock_time.advance_time(305) test.socket.capability:__expect_send( mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("not present")) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -292,7 +311,10 @@ test.register_coroutine_test( PowerConfiguration.ID ) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -310,7 +332,10 @@ test.register_coroutine_test( mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present")) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -322,7 +347,10 @@ test.register_coroutine_test( mock_simple_device:generate_info_changed({ preferences = { check_interval = 100 } }) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) -- Build two additional mock devices (module-level) for checkInterval type variants. @@ -369,7 +397,8 @@ test.register_coroutine_test( mock_device_str_interval:generate_test_message("main", capabilities.presenceSensor.presence("not present")) ) end, - { test_init = function() end } + { test_init = function() end, min_api_version = 19 + } ) test.register_coroutine_test( @@ -384,7 +413,8 @@ test.register_coroutine_test( mock_device_nil_interval:generate_test_message("main", capabilities.presenceSensor.presence("not present")) ) end, - { test_init = function() end } + { test_init = function() end, min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua b/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua index bdd79dd59f..e3013f4631 100644 --- a/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua +++ b/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua @@ -50,7 +50,10 @@ test.register_coroutine_test( PowerConfiguration.attributes.BatteryVoltage:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -91,7 +94,10 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -107,6 +113,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) } + }, + { + min_api_version = 19 } ) @@ -123,6 +132,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.battery()) } + }, + { + min_api_version = 19 } ) @@ -139,6 +151,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -155,6 +170,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(50)) } + }, + { + min_api_version = 19 } ) @@ -171,6 +189,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -187,6 +208,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) } + }, + { + min_api_version = 19 } ) @@ -203,6 +227,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-range-extender/src/test/test_zigbee_extend.lua b/drivers/SmartThings/zigbee-range-extender/src/test/test_zigbee_extend.lua index c76e228cef..1423b1ab90 100644 --- a/drivers/SmartThings/zigbee-range-extender/src/test/test_zigbee_extend.lua +++ b/drivers/SmartThings/zigbee-range-extender/src/test/test_zigbee_extend.lua @@ -28,7 +28,10 @@ test.register_coroutine_test( Basic.attributes.ZCLVersion:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) -- test.register_coroutine_test( diff --git a/drivers/SmartThings/zigbee-sensor/src/test/test_zigbee_sensor.lua b/drivers/SmartThings/zigbee-sensor/src/test/test_zigbee_sensor.lua index a87f7a0550..eaa8663726 100644 --- a/drivers/SmartThings/zigbee-sensor/src/test/test_zigbee_sensor.lua +++ b/drivers/SmartThings/zigbee-sensor/src/test/test_zigbee_sensor.lua @@ -119,7 +119,10 @@ test.register_coroutine_test( function () test.socket.zigbee:__queue_receive({mock_device_generic_sensor.id, ZoneTypeAttribute:build_test_attr_report(mock_device_generic_sensor, 0x0015)}) mock_device_generic_sensor:expect_metadata_update({profile = ZIGBEE_GENERIC_CONTACT_SENSOR_PROFILE}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -128,7 +131,10 @@ test.register_coroutine_test( function () test.socket.zigbee:__queue_receive({mock_device_generic_sensor.id, ZoneTypeAttribute:build_test_attr_report(mock_device_generic_sensor, 0x000d)}) mock_device_generic_sensor:expect_metadata_update({profile = ZIGBEE_GENERIC_MOTION_SENSOR_PROFILE}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -136,7 +142,10 @@ test.register_coroutine_test( function () test.socket.zigbee:__queue_receive({mock_device_motion_illuminance.id, ZoneTypeAttribute:build_test_attr_report(mock_device_motion_illuminance, 0x000d)}) mock_device_motion_illuminance:expect_metadata_update({profile = ZIGBEE_GENERIC_MOTION_ILLUMINANCE_PROFILE}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -144,7 +153,10 @@ test.register_coroutine_test( function () test.socket.zigbee:__queue_receive({mock_device_generic_sensor.id, ZoneTypeAttribute:build_test_attr_report(mock_device_generic_sensor, 0x002a)}) mock_device_generic_sensor:expect_metadata_update({profile = ZIGBEE_GENERIC_WATERLEAK_SENSOR_PROFILE}) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -160,6 +172,9 @@ test.register_message_test( direction = "send", message = mock_device_contact_sensor:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -176,6 +191,9 @@ test.register_message_test( direction = "send", message = mock_device_motion_sensor:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -192,6 +210,9 @@ test.register_message_test( direction = "send", message = mock_device_motion_illuminance:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -208,6 +229,9 @@ test.register_message_test( direction = "send", message = mock_device_waterleak_sensor:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -224,6 +248,9 @@ test.register_message_test( direction = "send", message = mock_device_contact_sensor:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -240,6 +267,9 @@ test.register_message_test( direction = "send", message = mock_device_contact_sensor:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -258,6 +288,9 @@ test.register_message_test( direction = "send", message = mock_device_contact_sensor:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -276,6 +309,9 @@ test.register_message_test( direction = "send", message = mock_device_contact_sensor:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -292,6 +328,9 @@ test.register_message_test( direction = "send", message = mock_device_motion_sensor:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -308,6 +347,9 @@ test.register_message_test( direction = "send", message = mock_device_motion_sensor:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -324,6 +366,9 @@ test.register_message_test( direction = "send", message = mock_device_motion_sensor:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -340,6 +385,9 @@ test.register_message_test( direction = "send", message = mock_device_motion_sensor:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -356,6 +404,9 @@ test.register_message_test( direction = "send", message = mock_device_motion_illuminance:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -372,6 +423,9 @@ test.register_message_test( direction = "send", message = mock_device_motion_illuminance:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -391,6 +445,9 @@ test.register_message_test( direction = "send", message = mock_device_motion_illuminance:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 137 })) } + }, + { + min_api_version = 19 } ) @@ -407,6 +464,9 @@ test.register_message_test( direction = "send", message = mock_device_motion_illuminance:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -423,6 +483,9 @@ test.register_message_test( direction = "send", message = mock_device_motion_illuminance:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -439,6 +502,9 @@ test.register_message_test( direction = "send", message = mock_device_waterleak_sensor:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -455,6 +521,9 @@ test.register_message_test( direction = "send", message = mock_device_waterleak_sensor:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -471,6 +540,9 @@ test.register_message_test( direction = "send", message = mock_device_waterleak_sensor:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -487,6 +559,9 @@ test.register_message_test( direction = "send", message = mock_device_waterleak_sensor:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -597,7 +672,10 @@ test.register_coroutine_test( ) test.socket.zigbee:__expect_send({ mock_device_contact_sensor.id, ZoneStatusAttribute:read(mock_device_contact_sensor) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -614,7 +692,10 @@ test.register_coroutine_test( } ) test.socket.zigbee:__expect_send({ mock_device_motion_sensor.id, ZoneStatusAttribute:read(mock_device_motion_sensor) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -631,7 +712,10 @@ test.register_coroutine_test( } ) test.socket.zigbee:__expect_send({ mock_device_motion_illuminance.id, ZoneStatusAttribute:read(mock_device_motion_illuminance) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -648,7 +732,10 @@ test.register_coroutine_test( } ) test.socket.zigbee:__expect_send({ mock_device_waterleak_sensor.id, ZoneStatusAttribute:read(mock_device_waterleak_sensor) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -719,7 +806,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device_contact_sensor.id, IASZone.attributes.ZoneStatus:read(mock_device_contact_sensor) }) mock_device_contact_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -782,7 +872,10 @@ test.register_coroutine_test( } ) mock_device_motion_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -845,7 +938,10 @@ test.register_coroutine_test( } ) mock_device_motion_illuminance:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -909,7 +1005,11 @@ test.register_coroutine_test( } ) mock_device_waterleak_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua index c88d272807..13f3e9a913 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua @@ -257,7 +257,10 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -374,7 +377,10 @@ test.register_coroutine_test( capabilities.alarm.alarm.off() ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -402,7 +408,10 @@ test.register_coroutine_test( -- Expect the OFF command get_siren_OFF_commands() test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -429,7 +438,10 @@ test.register_coroutine_test( -- Expect the OFF command get_siren_OFF_commands() test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -442,7 +454,10 @@ test.register_coroutine_test( }) get_siren_OFF_commands() test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -499,7 +514,10 @@ test.register_coroutine_test( -- stop the siren -- Expect the OFF command get_siren_OFF_commands() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -582,7 +600,10 @@ test.register_coroutine_test( ) -- Expect the OFF command get_siren_OFF_commands() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -658,7 +679,10 @@ test.register_coroutine_test( -- stop the siren -- Expect the OFF command get_siren_OFF_commands(expectedWarningDuration) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -682,7 +706,10 @@ test.register_coroutine_test( -- Expect the command with given configuration get_squawk_command_new_fw( SquawkMode.SOUND_FOR_SYSTEM_IS_ARMED, IaswdLevel.VERY_HIGH_LEVEL ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -705,7 +732,10 @@ test.register_coroutine_test( -- Expect the command with given configuration get_squawk_command_older_fw( SquawkMode.SOUND_FOR_SYSTEM_IS_ARMED, IaswdLevel.VERY_HIGH_LEVEL ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -747,7 +777,10 @@ test.register_coroutine_test( test.mock_time.advance_time(1) -- Expect the command with given configuration get_squawk_command_new_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -789,7 +822,10 @@ test.register_coroutine_test( test.mock_time.advance_time(1) -- Expect the command with given configuration get_squawk_command_older_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -843,7 +879,10 @@ test.register_coroutine_test( ) -- Expect the command with given configuration get_squawk_command_new_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -874,7 +913,10 @@ test.register_coroutine_test( } ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -918,7 +960,10 @@ test.register_coroutine_test( } ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -934,6 +979,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) } + }, + { + min_api_version = 19 } ) @@ -950,6 +998,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.battery()) } + }, + { + min_api_version = 19 } ) @@ -966,6 +1017,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -982,6 +1036,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(50)) } + }, + { + min_api_version = 19 } ) @@ -998,6 +1055,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -1041,7 +1101,10 @@ test.register_coroutine_test( build_sw_version_attr_report(mock_device, "\x01\x09\x03") }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1056,7 +1119,10 @@ test.register_coroutine_test( build_sw_version_attr_report(mock_device, "\x01\x09\x01") }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1114,7 +1180,11 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua index aed318f49a..bbc34efe64 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua @@ -275,7 +275,10 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -399,7 +402,10 @@ test.register_coroutine_test( capabilities.tamperAlert.tamper.clear() ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -427,7 +433,10 @@ test.register_coroutine_test( -- Expect the OFF command get_siren_OFF_commands() test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -454,7 +463,10 @@ test.register_coroutine_test( -- Expect the OFF command get_siren_OFF_commands() test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -467,7 +479,10 @@ test.register_coroutine_test( }) get_siren_OFF_commands() test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -525,7 +540,10 @@ test.register_coroutine_test( -- stop the siren -- Expect the OFF command get_siren_OFF_commands() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -608,7 +626,10 @@ test.register_coroutine_test( ) -- Expect the OFF command get_siren_OFF_commands() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -684,7 +705,10 @@ test.register_coroutine_test( -- stop the siren -- Expect the OFF command get_siren_OFF_commands(expectedWarningDuration) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -707,7 +731,10 @@ test.register_coroutine_test( -- Expect the command with given configuration get_squawk_command_new_fw( SquawkMode.SOUND_FOR_SYSTEM_IS_ARMED, IaswdLevel.VERY_HIGH_LEVEL ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -730,7 +757,10 @@ test.register_coroutine_test( -- Expect the command with given configuration get_squawk_command_older_fw( SquawkMode.SOUND_FOR_SYSTEM_IS_ARMED, IaswdLevel.VERY_HIGH_LEVEL ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -772,7 +802,10 @@ test.register_coroutine_test( test.mock_time.advance_time(1) -- Expect the command with given configuration get_squawk_command_new_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -814,7 +847,10 @@ test.register_coroutine_test( test.mock_time.advance_time(1) -- Expect the command with given configuration get_squawk_command_older_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -868,7 +904,10 @@ test.register_coroutine_test( ) -- Expect the command with given configuration get_squawk_command_new_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -899,7 +938,10 @@ test.register_coroutine_test( } ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -945,7 +987,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -968,7 +1013,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -992,7 +1038,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1009,6 +1056,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -1025,6 +1075,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(50)) } + }, + { + min_api_version = 19 } ) @@ -1041,6 +1094,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -1077,7 +1133,10 @@ test.register_coroutine_test( end, { test_init = function() test.mock_device.add_test_device(mock_device_112) - end } + end, + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_ozom_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_ozom_siren.lua index f05c58468f..5c767317b0 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_ozom_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_ozom_siren.lua @@ -60,6 +60,9 @@ test.register_message_test( data_types.Enum8(0)) } } + }, + { + min_api_version = 19 } ) @@ -94,7 +97,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -118,7 +124,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua index 52db9f96f4..e6b5062322 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua @@ -67,6 +67,9 @@ test.register_message_test( data_types.Uint8(40), data_types.Enum8(3)) } } + }, + { + min_api_version = 19 } ) @@ -87,6 +90,9 @@ test.register_message_test( data_types.Uint8(0x28), data_types.Enum8(0)) } } + }, + { + min_api_version = 19 } ) @@ -107,6 +113,9 @@ test.register_message_test( data_types.Uint8(40), data_types.Enum8(3)) } } + }, + { + min_api_version = 19 } ) @@ -127,6 +136,9 @@ test.register_message_test( data_types.Uint8(40), data_types.Enum8(0)) } } + }, + { + min_api_version = 19 } ) @@ -147,6 +159,9 @@ test.register_message_test( data_types.Uint8(40), data_types.Enum8(0)) } } + }, + { + min_api_version = 19 } ) @@ -167,6 +182,9 @@ test.register_message_test( data_types.Uint8(40), data_types.Enum8(3)) } } + }, + { + min_api_version = 19 } ) @@ -229,7 +247,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -250,6 +271,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -290,7 +314,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -337,7 +362,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.siren())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) - end + end, + { + min_api_version = 19 + } ) local function build_default_response_zigbee_msg() @@ -364,7 +392,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, build_default_response_zigbee_msg() }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.off())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -379,7 +410,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.off())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -394,7 +428,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.off())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -413,7 +450,10 @@ test.register_coroutine_test( data_types.Uint16(0x0032), data_types.Uint8(40), data_types.Enum8(0)) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua index 1203ffa00a..652807aa91 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua @@ -63,7 +63,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.High())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", selfCheck.selfCheckState.idle())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", lifeTimeReport.lifeTimeState.normal())) - end + end, + { + min_api_version = 19 + } ) @@ -81,7 +84,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, config_attr_message}) test.socket.zigbee:__expect_send({mock_device.id, write_attr_messge}) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) @@ -97,7 +103,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.gasDetector.gas.detected())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -112,7 +121,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.gasDetector.gas.clear())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -127,7 +139,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.audioMute.mute.muted())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -142,7 +157,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.audioMute.mute.unmuted())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -153,7 +171,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_MUTE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -163,7 +184,10 @@ test.register_coroutine_test( { capability = "audioMute", component = "main", command = "unmute", args = {} } }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.audioMute.mute.muted())) - end + end, + { + min_api_version = 19 + } ) @@ -180,7 +204,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", selfCheck.selfCheckState.selfCheckCompleted())) - end + end, + { + min_api_version = 19 + } ) @@ -194,7 +221,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_SELF_CHECK_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) - end + end, + { + min_api_version = 19 + } ) @@ -211,7 +241,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", lifeTimeReport.lifeTimeState.endOfLife())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -226,7 +259,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", lifeTimeReport.lifeTimeState.normal())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -241,7 +277,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.Low())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -256,7 +295,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", selfCheck.selfCheckState.idle())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -271,7 +313,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.High())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -302,7 +347,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.High()) ) - end + end, + { + min_api_version = 19 + } ) @@ -334,7 +382,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.Low()) ) - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua index b0b6a4875a..74c4c6e371 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua @@ -49,7 +49,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.audioMute.mute.unmuted())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", selfCheck.selfCheckState.idle())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -77,7 +80,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -92,7 +98,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -107,7 +116,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.audioMute.mute.muted())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -118,7 +130,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_MUTE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -133,7 +148,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", selfCheck.selfCheckState.selfCheckCompleted())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -145,7 +163,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_SELF_CHECK_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -160,7 +181,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -175,7 +199,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.audioMute.mute.unmuted())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -190,7 +217,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", selfCheck.selfCheckState.idle())) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -206,6 +236,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) 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 index 21b1660c1a..06415f6e00 100644 --- 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 @@ -74,7 +74,10 @@ test.register_coroutine_test( }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -193,7 +196,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -209,6 +215,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(14)) } + }, + { + min_api_version = 19 } ) @@ -225,7 +234,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -241,7 +253,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -259,7 +274,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -283,6 +301,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -323,7 +344,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -358,7 +380,10 @@ test.register_coroutine_test( test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -391,7 +416,10 @@ test.register_coroutine_test( }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -425,7 +453,10 @@ test.register_coroutine_test( }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -458,7 +489,10 @@ test.register_coroutine_test( }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -492,7 +526,10 @@ test.register_coroutine_test( }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -566,7 +603,10 @@ test.register_coroutine_test( string.format("Version mismatch! Expected '%s' but got '%s'", expected_hex, stored_version or "nil")) end - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -582,6 +622,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) } + }, + { + min_api_version = 19 } ) @@ -598,7 +641,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 fdb05cf22d..6ca8a3a0d9 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 @@ -81,7 +81,10 @@ test.register_coroutine_test( }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -200,7 +203,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -216,6 +222,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(14)) } + }, + { + min_api_version = 19 } ) @@ -233,6 +242,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -250,6 +262,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.tested()) } + }, + { + min_api_version = 19 } ) @@ -268,7 +283,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -292,6 +310,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -369,7 +390,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -404,7 +426,10 @@ test.register_coroutine_test( test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -437,7 +462,10 @@ test.register_coroutine_test( }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -471,7 +499,10 @@ test.register_coroutine_test( }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -504,7 +535,10 @@ test.register_coroutine_test( }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -538,7 +572,10 @@ test.register_coroutine_test( }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -612,7 +649,10 @@ test.register_coroutine_test( string.format("Version mismatch! Expected '%s' but got '%s'", expected_hex, stored_version or "nil")) end - end + end, + { + min_api_version = 19 + } ) local function build_default_response_msg(device, cluster, command, status) @@ -651,6 +691,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -667,7 +710,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", smokeDetector.smoke.clear()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -683,7 +729,10 @@ test.register_coroutine_test( test.mock_time.advance_time(ALARM_DEFAULT_MAX_DURATION) test.socket.capability:__expect_send(mock_device:generate_test_message("main", alarm.alarm.off())) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -696,7 +745,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", alarm.alarm.off())) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_zigbee_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_zigbee_smoke_detector.lua index b27713fa19..af1ee02936 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_zigbee_smoke_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_zigbee_smoke_detector.lua @@ -43,6 +43,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "smokeDetector", capability_attr_id = "smoke" } } }, + }, + { + min_api_version = 19 } ) @@ -67,6 +70,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "smokeDetector", capability_attr_id = "smoke" } } }, + }, + { + min_api_version = 19 } ) @@ -91,6 +97,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "smokeDetector", capability_attr_id = "smoke" } } }, + }, + { + min_api_version = 19 } ) @@ -115,6 +124,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "smokeDetector", capability_attr_id = "smoke" } } }, + }, + { + min_api_version = 19 } ) @@ -131,6 +143,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -228,7 +243,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -265,7 +283,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua b/drivers/SmartThings/zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua index 4ab2f31e92..3ea9dfbdd9 100644 --- a/drivers/SmartThings/zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua +++ b/drivers/SmartThings/zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua @@ -121,7 +121,10 @@ test.register_coroutine_test( PollControl.attributes.CheckInInterval:write(mock_device, data_types.Uint32(6480)) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -187,7 +190,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -204,6 +208,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.soundSensor.sound.detected()) } + }, + { + min_api_version = 19 } ) @@ -220,6 +227,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.soundSensor.sound.detected()) } + }, + { + min_api_version = 19 } ) @@ -236,6 +246,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.soundSensor.sound.not_detected()) } + }, + { + min_api_version = 19 } ) @@ -252,6 +265,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -276,6 +292,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -297,6 +316,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 20.00, maximum = 30.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) 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 b581c5c61e..c9d4284407 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 @@ -99,6 +99,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -124,6 +127,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -149,6 +155,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -175,6 +184,9 @@ test.register_message_test( math.floor(57 * 0xFE / 100), 0) } } + }, + { + min_api_version = 19 } ) @@ -200,6 +212,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -217,6 +232,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 27000.0, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -242,6 +260,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "hue" } } } + }, + { + min_api_version = 19 } ) @@ -267,6 +288,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "saturation" } } } + }, + { + min_api_version = 19 } ) @@ -395,7 +419,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) -- test.register_coroutine_test( @@ -445,7 +472,8 @@ test.register_coroutine_test( { test_init = function() -- no op to override auto device add on startup - end + end, + min_api_version = 19 } ) @@ -469,7 +497,8 @@ test.register_coroutine_test( { test_init = function() -- no op to override auto device add on startup - end + end, + min_api_version = 19 } ) @@ -492,7 +521,8 @@ test.register_coroutine_test( { test_init = function() -- no op to override auto device add on startup - end + end, + min_api_version = 19 } ) @@ -515,7 +545,8 @@ test.register_coroutine_test( { test_init = function() -- no op to override auto device add on startup - end + end, + min_api_version = 19 } ) @@ -550,7 +581,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device)}) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) -- This tests that our code responsible for handling conversion errors from Kelvin<->Mireds works as expected @@ -566,7 +600,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({mock_device.id, ColorControl.attributes.ColorTemperatureMireds:build_test_attr_report(mock_device, 556)}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800))) mock_device:expect_native_attr_handler_registration("colorTemperature", "colorTemperature") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -587,7 +624,10 @@ test.register_coroutine_test( --- offset should be reset by a reading under the previous offset test.socket.zigbee:__queue_receive({mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 14)}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 14.0, unit = "kWh" }))) - end + end, + { + min_api_version = 19 + } ) 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 ce4bb13957..d1cd3c7b72 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 @@ -52,7 +52,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -104,7 +107,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -128,7 +134,10 @@ test.register_coroutine_test( ColorControl.commands.MoveToColorTemperature(mock_device, temp_in_mired, 0x0000) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -140,7 +149,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 516a18c326..aeec82d57e 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua @@ -54,7 +54,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -109,7 +112,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -133,7 +139,10 @@ test.register_coroutine_test( ColorControl.commands.MoveToColorTemperature(mock_device, temp_in_mired, 0x0000) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -145,7 +154,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -157,7 +169,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, TURN_OFF_INDICATOR_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -167,7 +182,10 @@ test.register_coroutine_test( preferences = { ["stse.lightFadeInTimeInSec"] = 1 } })) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnTransitionTime:write(mock_device, 10) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -177,7 +195,10 @@ test.register_coroutine_test( preferences = { ["stse.lightFadeOutTimeInSec"] = 1 } })) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OffTransitionTime:write(mock_device, 10) }) - end + end, + { + min_api_version = 19 + } ) local mock_device_cwacn1 = test.mock_device.build_test_zigbee_device( @@ -210,7 +231,8 @@ test.register_coroutine_test( { test_init = function() test.mock_device.add_test_device(mock_device_cwacn1) - end + end, + min_api_version = 19 } ) 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 0d51bd11c2..0320900511 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 @@ -76,7 +76,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -115,7 +118,10 @@ test.register_coroutine_test( SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -131,7 +137,10 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -145,7 +154,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -159,7 +171,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -170,7 +185,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "on") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -181,7 +199,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "off") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -200,7 +221,10 @@ test.register_coroutine_test( ) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -232,7 +256,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 1000000.0 })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -244,7 +271,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -256,7 +286,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, MAX_POWER_ATTRIBUTE_ID, MFG_CODE, data_types.SinglePrecisionFloat, SinglePrecisionFloat(0, 6, 0.5625)) }) - end + end, + { + min_api_version = 19 + } ) -- with standard cluster @@ -282,7 +315,10 @@ test.register_coroutine_test( ElectricalMeasurement.attributes.ActivePower:read(mock_standard) }) test.socket.zigbee:__expect_send({ mock_standard.id, SimpleMetering.attributes.CurrentSummationDelivered:read(mock_standard) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -305,7 +341,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_standard:generate_test_message("main", capabilities.powerMeter.power({ value = 10.0, unit = "W" })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -327,7 +366,10 @@ test.register_coroutine_test( mock_standard:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 10 })) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 8a544caf13..ea404f542f 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 @@ -77,7 +77,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -116,7 +119,10 @@ test.register_coroutine_test( SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -132,7 +138,10 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -146,7 +155,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -160,7 +172,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -171,7 +186,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "on") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -182,7 +200,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "off") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -201,7 +222,10 @@ test.register_coroutine_test( ) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -233,7 +257,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 1000000.0 })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -245,7 +272,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -257,7 +287,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, MAX_POWER_ATTRIBUTE_ID, MFG_CODE, data_types.SinglePrecisionFloat, SinglePrecisionFloat(0, 6, 0.5625)) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -269,7 +302,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, RESTORE_TURN_OFF_INDICATOR_LIGHT_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) - end + end, + { + min_api_version = 19 + } ) -- with standard cluster @@ -295,7 +331,10 @@ test.register_coroutine_test( ElectricalMeasurement.attributes.ActivePower:read(mock_standard) }) test.socket.zigbee:__expect_send({ mock_standard.id, SimpleMetering.attributes.CurrentSummationDelivered:read(mock_standard) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -318,7 +357,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_standard:generate_test_message("main", capabilities.powerMeter.power({ value = 10.0, unit = "W" })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -340,7 +382,10 @@ test.register_coroutine_test( mock_standard:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 10 })) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 f27d84cfc6..0e5783e0ba 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 @@ -54,7 +54,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -68,7 +71,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -82,7 +88,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -93,7 +102,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "on") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -104,7 +116,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "off") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -123,7 +138,10 @@ test.register_coroutine_test( ) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -147,7 +165,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 1000000.0 })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -159,7 +180,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -171,7 +195,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 840aadacb9..478647e819 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 @@ -46,7 +46,10 @@ test.register_coroutine_test( function() test.socket.zigbee:__set_channel_ordering("relaxed") test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -57,7 +60,10 @@ test.register_coroutine_test( { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) -end +end, +{ + min_api_version = 19 +} ) test.register_coroutine_test( @@ -67,7 +73,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, true):from_endpoint(0x01) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -77,7 +86,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, false):from_endpoint(0x01) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -88,7 +100,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "on") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -99,7 +114,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "off") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -111,7 +129,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -123,7 +144,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 207e7bf21b..6fb6269226 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 @@ -97,7 +97,10 @@ test.register_coroutine_test( data_types.Uint8, 1) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -118,7 +121,10 @@ test.register_coroutine_test( { visibility = { displayed = false } }))) test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -142,7 +148,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_base_device:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) test.socket.capability:__expect_send(mock_base_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -153,7 +162,10 @@ test.register_coroutine_test( { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -163,7 +175,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, true):from_endpoint(0x01) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -173,7 +188,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, true):from_endpoint(0x02) }) test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.switch.switch.on())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -183,7 +201,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, false):from_endpoint(0x01) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -193,7 +214,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, false):from_endpoint(0x02) }) test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.switch.switch.off())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -204,7 +228,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "on") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -215,7 +242,10 @@ test.register_coroutine_test( mock_child:expect_native_cmd_handler_registration("switch", "on") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x02) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -226,7 +256,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "off") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -237,7 +270,10 @@ test.register_coroutine_test( mock_child:expect_native_cmd_handler_registration("switch", "off") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x02) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -251,7 +287,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -265,7 +304,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) - end + end, + { + min_api_version = 19 + } ) @@ -279,7 +321,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -291,7 +336,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 a02ef37aa2..6251e277e1 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 @@ -84,7 +84,10 @@ test.register_coroutine_test( { visibility = { displayed = false } }))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -97,7 +100,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -113,7 +119,10 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -127,7 +136,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -141,7 +153,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -155,7 +170,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -169,7 +187,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -180,7 +201,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "on") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -191,7 +215,10 @@ test.register_coroutine_test( mock_child:expect_native_cmd_handler_registration("switch", "on") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x02) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -202,7 +229,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "off") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -213,7 +243,10 @@ test.register_coroutine_test( mock_child:expect_native_cmd_handler_registration("switch", "off") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x02) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -227,7 +260,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -241,7 +277,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -260,7 +299,10 @@ test.register_coroutine_test( ) test.socket.zigbee:__expect_send({ mock_device.id, AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -284,7 +326,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 1000000.0 })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -296,7 +341,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -308,7 +356,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 3711116d99..95b56b2af7 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 @@ -58,7 +58,10 @@ test.register_coroutine_test( OnOff.attributes.OnOff:build_test_attr_report(mock_device, true):from_endpoint(0x01) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) mock_device:expect_native_attr_handler_registration("switch", "switch") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -68,7 +71,10 @@ test.register_coroutine_test( OnOff.attributes.OnOff:build_test_attr_report(mock_device, true):from_endpoint(0x02) }) test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.switch.switch.on())) mock_device:expect_native_attr_handler_registration("switch", "switch") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -78,7 +84,10 @@ test.register_coroutine_test( OnOff.attributes.OnOff:build_test_attr_report(mock_device, false):from_endpoint(0x01) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) mock_device:expect_native_attr_handler_registration("switch", "switch") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -88,7 +97,10 @@ test.register_coroutine_test( OnOff.attributes.OnOff:build_test_attr_report(mock_device, false):from_endpoint(0x02) }) test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.switch.switch.off())) mock_device:expect_native_attr_handler_registration("switch", "switch") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -99,7 +111,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "on") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -110,7 +125,10 @@ test.register_coroutine_test( mock_child:expect_native_cmd_handler_registration("switch", "on") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x02) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -121,7 +139,10 @@ test.register_coroutine_test( mock_device:expect_native_cmd_handler_registration("switch", "off") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -132,7 +153,10 @@ test.register_coroutine_test( mock_child:expect_native_cmd_handler_registration("switch", "off") test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x02) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -146,7 +170,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -160,7 +187,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -172,7 +202,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 57d6576955..c583aab35e 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aurora_relay.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aurora_relay.lua @@ -40,7 +40,10 @@ test.register_coroutine_test( 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) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -64,6 +67,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.commands.On(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -88,6 +94,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.commands.Off(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -112,7 +121,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 60.0, unit = "W" })) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 b30f49df23..a65a0a1f92 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 @@ -41,6 +41,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) 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 bd8ce15b10..60b0f7709f 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 @@ -32,7 +32,9 @@ test.register_coroutine_test("zwave_device_handled", function() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", dkjson.encode(mock_device.raw_st_data) }) test.wait_for_events() end, - nil + { + min_api_version = 19 + } ) test.register_message_test( @@ -41,6 +43,9 @@ test.register_message_test( channel = "capability", direction = "receive", message = { mock_device.id, { capability = "switch", component = "main", command = "on", args = { } } } + }, + { + min_api_version = 19 } ) 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 8841c5a53d..7d2b0a6abc 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_cree_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_cree_bulb.lua @@ -64,7 +64,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -72,7 +75,10 @@ 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",capabilities.switchLevel.level(100))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -82,7 +88,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -93,7 +102,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.server.commands.MoveToLevelWithOnOff(mock_device, 144, 0xFFFF) }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 55c491e77d..7be4180bd8 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 @@ -74,7 +74,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -111,7 +114,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -139,7 +143,10 @@ test.register_coroutine_test( test.mock_time.advance_time(1) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -158,7 +165,10 @@ test.register_coroutine_test( command } ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 3b5a5b93b1..e123a6724c 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 @@ -128,7 +128,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -152,6 +155,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOffCluster.server.commands.On(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -176,6 +182,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOffCluster.server.commands.Off(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -203,6 +212,9 @@ test.register_message_test( LevelCluster.server.commands.MoveToLevelWithOnOff(mock_device, math.floor(57 * 254 / 100)) } } + }, + { + min_api_version = 19 } ) @@ -230,6 +242,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -246,6 +261,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 33.3, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -262,6 +280,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.5555, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) 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 index c9d5f9b75e..3b44ff375f 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_frient_IO_module.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_frient_IO_module.lua @@ -520,7 +520,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -551,7 +554,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_output_child_1:generate_test_message("main", Switch.switch.off())) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -608,7 +614,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_parent_device.id, direct_off }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -629,7 +638,10 @@ test.register_coroutine_test( }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -677,7 +689,10 @@ test.register_coroutine_test( }) 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 + end, + { + min_api_version = 19 + } ) 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 4290d6ebbe..3e730e989c 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_frient_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_frient_switch.lua @@ -79,6 +79,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) } + }, + { + min_api_version = 19 } ) @@ -113,7 +116,11 @@ test.register_message_test("Current divisor, multiplier, summation should be han direction = "send", message = mock_device:generate_test_message("main", capabilities.currentMeasurement.current({ value = 200.0, unit = "A" })) }, - }) + }, + { + min_api_version = 19 + } +) test.register_coroutine_test("Refresh command should read all necessary attributes", function() test.socket.zigbee:__set_channel_ordering("relaxed") @@ -144,7 +151,11 @@ test.register_coroutine_test("Refresh command should read all necessary attribut test.socket.zigbee:__expect_send( {mock_device.id, OnOff.attributes.OnOff:read(mock_device) } ) -end) +end, +{ + min_api_version = 19 +} +) test.register_message_test( "Handle switch ON report", @@ -167,6 +178,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -191,6 +205,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -220,6 +237,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -242,6 +262,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.027, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -252,7 +275,10 @@ test.register_coroutine_test( assert(mock_device:get_field(constants.SIMPLE_METERING_DIVISOR_KEY) == 1000) assert(mock_device:get_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY) == 1000) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test("doConfigure should send bind request, read attributes and configure reporting", function() @@ -313,7 +339,11 @@ test.register_coroutine_test("doConfigure should send bind request, read attribu test.socket.zigbee:__expect_send({mock_device.id, Alarms.attributes.AlarmCount:read(mock_device)}) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test( "Alarm report should be handled", @@ -325,7 +355,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.powerSource.powerSource.unknown())) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 bd54b1b20f..4e8bfcb8fa 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 @@ -45,7 +45,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -59,7 +62,10 @@ test.register_coroutine_test( } test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 50) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -71,7 +77,10 @@ test.register_coroutine_test( } } test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -101,7 +110,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -135,7 +147,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -152,7 +167,10 @@ test.register_coroutine_test( preferences = { dimOnOff = 0 } })) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 0) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -169,7 +187,10 @@ test.register_coroutine_test( preferences = { dimOnOff = 1, dimRate = 50 } })) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 50) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 96f4581310..bfe587bbe4 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_hanssem_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_hanssem_switch.lua @@ -126,6 +126,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -156,6 +159,9 @@ test.register_message_test( { device_uuid = mock_first_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -186,6 +192,9 @@ test.register_message_test( { device_uuid = mock_second_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -216,6 +225,9 @@ test.register_message_test( { device_uuid = mock_third_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -246,6 +258,9 @@ test.register_message_test( { device_uuid = mock_fourth_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -276,6 +291,9 @@ test.register_message_test( { device_uuid = mock_fifth_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -306,6 +324,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -336,6 +357,9 @@ test.register_message_test( { device_uuid = mock_first_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -366,6 +390,9 @@ test.register_message_test( { device_uuid = mock_second_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -396,6 +423,9 @@ test.register_message_test( { device_uuid = mock_third_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -426,6 +456,9 @@ test.register_message_test( { device_uuid = mock_fourth_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -456,6 +489,9 @@ test.register_message_test( { device_uuid = mock_fifth_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -485,6 +521,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x01) } } + }, + { + min_api_version = 19 } ) @@ -509,6 +548,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x02) } } + }, + { + min_api_version = 19 } ) @@ -533,6 +575,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x03) } } + }, + { + min_api_version = 19 } ) @@ -557,6 +602,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x04) } } + }, + { + min_api_version = 19 } ) @@ -581,6 +629,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x05) } } + }, + { + min_api_version = 19 } ) @@ -605,6 +656,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x06) } } + }, + { + min_api_version = 19 } ) @@ -629,6 +683,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x01) } } + }, + { + min_api_version = 19 } ) @@ -653,6 +710,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x02) } } + }, + { + min_api_version = 19 } ) @@ -677,6 +737,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x03) } } + }, + { + min_api_version = 19 } ) @@ -701,6 +764,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x04) } } + }, + { + min_api_version = 19 } ) @@ -725,6 +791,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x05) } } + }, + { + min_api_version = 19 } ) @@ -749,6 +818,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x06) } } + }, + { + min_api_version = 19 } ) @@ -796,7 +868,11 @@ test.register_coroutine_test( mock_base_device.id, OnOff.attributes.OnOff:read(mock_base_device):to_endpoint(0x01) }) - end + end, + { + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +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 index 79e7a66b54..b3daa53bc9 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn.lua @@ -104,7 +104,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -142,7 +143,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -165,7 +167,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -188,7 +191,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -214,7 +218,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -283,7 +288,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -314,7 +320,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -350,7 +357,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_inovelli_vzm30_sn:generate_test_message("button1", capabilities.button.button.pushed_2x({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) -- Test temperature measurement @@ -370,6 +380,9 @@ test.register_message_test( direction = "send", message = mock_inovelli_vzm30_sn:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 25.0, unit = "C"})) } + }, + { + min_api_version = 19 } ) @@ -390,6 +403,9 @@ test.register_message_test( direction = "send", message = mock_inovelli_vzm30_sn:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity(65)) } + }, + { + min_api_version = 19 } ) @@ -409,7 +425,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_inovelli_vzm30_sn:generate_test_message("main", capabilities.powerMeter.power({value = 200.0, unit = "W"})) ) - end + end, + { + min_api_version = 19 + } ) -- Test energy meter @@ -427,7 +446,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_inovelli_vzm30_sn:generate_test_message("main", capabilities.energyMeter.energy({value = 0.212, unit = "kWh"})) ) - end + end, + { + min_api_version = 19 + } ) -- Test energy meter reset command @@ -474,7 +496,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -531,7 +554,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) 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 index e40ead721a..201367b101 100644 --- 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 @@ -90,7 +90,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -143,7 +144,10 @@ test.register_coroutine_test( utils.serialize_int(notificationValue, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) -- Test child device switch off command @@ -174,7 +178,10 @@ test.register_coroutine_test( utils.serialize_int(0, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) -- Test child device level command @@ -230,7 +237,10 @@ test.register_coroutine_test( utils.serialize_int(notificationValue, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) -- Test child device color command @@ -290,7 +300,10 @@ test.register_coroutine_test( utils.serialize_int(notificationValue, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) -- Test child device color temperature command @@ -350,7 +363,10 @@ test.register_coroutine_test( utils.serialize_int(notificationValue, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) 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 index 0726a2d575..a12be49120 100644 --- 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 @@ -61,7 +61,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter9 preference change @@ -83,7 +86,10 @@ test.register_coroutine_test( expected_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter52 preference change @@ -104,7 +110,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter258 preference change @@ -125,7 +134,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter11 preference change (VZM30-only, same as VZM31) @@ -146,7 +158,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter17 preference change (VZM30-only, same as VZM31) @@ -167,7 +182,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter22 preference change (VZM30-only, same as VZM31) @@ -188,7 +206,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test notificationChild preference change @@ -204,7 +225,10 @@ test.register_coroutine_test( }) test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm30_sn:generate_info_changed({preferences = {notificationChild = true}})) - end + end, + { + min_api_version = 19 + } ) 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 index a3d57415b1..f777ce7b0c 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn.lua @@ -72,7 +72,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -95,7 +96,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -118,7 +120,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -144,7 +147,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -213,7 +217,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -244,7 +249,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -280,7 +286,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_inovelli_vzm31_sn:generate_test_message("button1", capabilities.button.button.pushed_2x({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) -- Test power meter from ElectricalMeasurement @@ -299,7 +308,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_inovelli_vzm31_sn:generate_test_message("main", capabilities.powerMeter.power({value = 200.0, unit = "W"})) ) - end + end, + { + min_api_version = 19 + } ) -- Test energy meter @@ -318,7 +330,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_inovelli_vzm31_sn:generate_test_message("main", capabilities.energyMeter.energy({value = 500.0, unit = "kWh"})) ) - end + end, + { + min_api_version = 19 + } ) -- Test energy meter reset command @@ -365,7 +380,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -415,7 +431,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) 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 index c6476cba5e..d26bcad631 100644 --- 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 @@ -79,7 +79,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -132,7 +133,10 @@ test.register_coroutine_test( utils.serialize_int(notificationValue, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) -- Test child device switch off command @@ -163,7 +167,10 @@ test.register_coroutine_test( utils.serialize_int(0, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) -- Test child device level command @@ -219,7 +226,10 @@ test.register_coroutine_test( utils.serialize_int(notificationValue, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) -- Test child device color command @@ -279,7 +289,10 @@ test.register_coroutine_test( utils.serialize_int(notificationValue, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) -- Test child device color temperature command @@ -339,7 +352,10 @@ test.register_coroutine_test( utils.serialize_int(notificationValue, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) 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 index 64b0d51ee5..e8b22b0584 100644 --- 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 @@ -50,7 +50,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter9 preference change @@ -72,7 +75,10 @@ test.register_coroutine_test( expected_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter52 preference change @@ -93,7 +99,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter258 preference change @@ -114,7 +123,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter11 preference change (VZM31-only) @@ -135,7 +147,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter17 preference change (VZM31-only) @@ -156,7 +171,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter22 preference change (VZM31-only) @@ -177,7 +195,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test notificationChild preference change @@ -193,7 +214,10 @@ test.register_coroutine_test( }) test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm31_sn:generate_info_changed({preferences = {notificationChild = true}})) - end + end, + { + min_api_version = 19 + } ) 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 index 0a288f27d4..501575ff45 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn.lua @@ -83,7 +83,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -116,7 +117,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -139,7 +141,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -162,7 +165,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -188,7 +192,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -257,7 +262,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -288,7 +294,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -324,7 +331,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_inovelli_vzm32_sn:generate_test_message("button1", capabilities.button.button.pushed_2x({ state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) -- Test illuminance measurement @@ -344,6 +354,9 @@ test.register_message_test( direction = "send", message = mock_inovelli_vzm32_sn:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({value = 13})) } + }, + { + min_api_version = 19 } ) @@ -364,6 +377,9 @@ test.register_message_test( direction = "send", message = mock_inovelli_vzm32_sn:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -384,6 +400,9 @@ test.register_message_test( direction = "send", message = mock_inovelli_vzm32_sn:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -404,7 +423,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_inovelli_vzm32_sn:generate_test_message("main", capabilities.powerMeter.power({value = 200.0, unit = "W"})) ) - end + end, + { + min_api_version = 19 + } ) -- Test energy meter @@ -421,7 +443,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_inovelli_vzm32_sn:generate_test_message("main", capabilities.energyMeter.energy({value = 0.212, unit = "kWh"})) ) - end + end, + { + min_api_version = 19 + } ) -- Test energy meter reset command @@ -468,7 +493,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -521,7 +547,11 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + 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 index 26346c04a9..f2a6c79229 100644 --- 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 @@ -79,7 +79,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -132,7 +133,10 @@ test.register_coroutine_test( utils.serialize_int(notificationValue, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) -- Test child device switch off command @@ -163,7 +167,10 @@ test.register_coroutine_test( utils.serialize_int(0, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) -- Test child device level command @@ -219,7 +226,10 @@ test.register_coroutine_test( utils.serialize_int(notificationValue, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) -- Test child device color command @@ -279,7 +289,10 @@ test.register_coroutine_test( utils.serialize_int(notificationValue, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) -- Test child device color temperature command @@ -339,7 +352,10 @@ test.register_coroutine_test( utils.serialize_int(notificationValue, 4, false, false) ) }) - end + end, + { + min_api_version = 19 + } ) 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 index 28ae4829a0..5bfc25c66a 100644 --- 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 @@ -50,7 +50,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter9 preference change @@ -72,7 +75,10 @@ test.register_coroutine_test( expected_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter52 preference change @@ -93,7 +99,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test parameter258 preference change @@ -114,7 +123,10 @@ test.register_coroutine_test( new_param_value ) }) - end + end, + { + min_api_version = 19 + } ) -- Test notificationChild preference change @@ -130,7 +142,10 @@ test.register_coroutine_test( }) test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm32_sn:generate_info_changed({preferences = {notificationChild = true}})) - end + end, + { + min_api_version = 19 + } ) -- Test parameter101 preference change @@ -157,7 +172,11 @@ test.register_coroutine_test( mock_inovelli_vzm32_sn.id, expected_command }) - end + end, + { + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + 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 d6dcc08ea4..349a9ce3e7 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua @@ -54,6 +54,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOffCluster.server.commands.On(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -78,6 +81,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOffCluster.server.commands.Off(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -94,6 +100,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 33.3, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -115,6 +124,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.5555, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -141,7 +153,10 @@ test.register_coroutine_test( 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.ACPowerMultiplier:configure_reporting(mock_device, 1, 43200, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 3b004c8f91..b6237eff0e 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 @@ -48,6 +48,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -65,6 +68,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch2", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -82,6 +88,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch3", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -99,6 +108,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch4", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -116,6 +128,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch5", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -133,6 +148,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch6", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -150,6 +168,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch7", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -167,6 +188,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch8", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -186,6 +210,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -203,6 +230,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch2", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -220,6 +250,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch3", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -237,6 +270,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch4", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -254,6 +290,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch5", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -271,6 +310,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch6", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -288,6 +330,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch7", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -305,6 +350,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("switch8", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -315,7 +363,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch2", command = "on", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x02) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -325,7 +376,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch3", command = "on", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x03) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -335,7 +389,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch4", command = "on", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x04) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -345,7 +402,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch5", command = "on", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x05) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -355,7 +415,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch6", command = "on", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x06) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -365,7 +428,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch7", command = "on", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x07) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -375,7 +441,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch8", command = "on", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x08) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -385,7 +454,10 @@ test.register_coroutine_test( { capability = "switch", component = "main", command = "off", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x01) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -395,7 +467,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch2", command = "off", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x02) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -405,7 +480,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch3", command = "off", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x03) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -415,7 +493,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch4", command = "off", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x04) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -425,7 +506,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch5", command = "off", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x05) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -435,7 +519,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch6", command = "off", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x06) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -445,7 +532,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch7", command = "off", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x07) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -455,7 +545,10 @@ test.register_coroutine_test( { capability = "switch", component = "switch8", command = "off", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x08) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -472,7 +565,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.switch.switch.off()) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 1ff11bd754..0a4be10929 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch.lua @@ -37,6 +37,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("switch1", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -54,6 +57,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("switch2", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) test.register_message_test( @@ -70,6 +76,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("switch3", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -87,6 +96,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("switch1", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -103,6 +115,9 @@ test.register_message_test( direction = "send", message = { mock_simple_device.id, OnOff.server.commands.On(mock_simple_device):to_endpoint(0x03) } } + }, + { + min_api_version = 19 } ) 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 b4ded05175..4f01185c1b 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 @@ -103,6 +103,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -133,6 +136,9 @@ test.register_message_test( { device_uuid = mock_first_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -163,6 +169,9 @@ test.register_message_test( { device_uuid = mock_second_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -193,6 +202,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -223,6 +235,9 @@ test.register_message_test( { device_uuid = mock_first_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -253,6 +268,9 @@ test.register_message_test( { device_uuid = mock_second_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -277,6 +295,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device) } } + }, + { + min_api_version = 19 } ) @@ -301,6 +322,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x02) } } + }, + { + min_api_version = 19 } ) @@ -325,6 +349,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x03) } } + }, + { + min_api_version = 19 } ) @@ -349,6 +376,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device) } } + }, + { + min_api_version = 19 } ) @@ -373,6 +403,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x02) } } + }, + { + min_api_version = 19 } ) @@ -397,6 +430,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x03) } } + }, + { + min_api_version = 19 } ) @@ -473,7 +509,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) local mock_non_mns_device = test.mock_device.build_test_zigbee_device( @@ -513,7 +552,8 @@ test.register_coroutine_test( { test_init = function() test.mock_device.add_test_device(mock_non_mns_device) - end + end, + min_api_version = 19 } ) 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 4e35d5067d..4511a2e610 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 @@ -116,7 +116,8 @@ test.register_coroutine_test( { test_init = function() -- no op to avoid auto device add and immediate init event on driver startup - end + end, + min_api_version = 19 } ) @@ -139,7 +140,8 @@ test.register_coroutine_test( { test_init = function() -- no op to avoid auto device add and immediate init event on driver startup - end + end, + min_api_version = 19 } ) @@ -169,7 +171,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -199,7 +202,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -225,6 +229,9 @@ test.register_message_test( { device_uuid = mock_child_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -250,6 +257,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -275,6 +285,9 @@ test.register_message_test( { device_uuid = mock_child_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -300,6 +313,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -327,6 +343,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -354,6 +373,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -378,6 +400,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x02) } } + }, + { + min_api_version = 19 } ) @@ -402,6 +427,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x01) } } + }, + { + min_api_version = 19 } ) @@ -426,6 +454,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x02) } } + }, + { + min_api_version = 19 } ) @@ -450,6 +481,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x01) } } + }, + { + min_api_version = 19 } ) @@ -473,7 +507,10 @@ test.register_coroutine_test( mock_base_device.id, ElectricalMeasurement.attributes.ActivePower:read(mock_base_device):to_endpoint(0x01) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -489,7 +526,10 @@ test.register_coroutine_test( mock_parent_device.id, ElectricalMeasurement.attributes.ActivePower:read(mock_child_device):to_endpoint(0x02) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 7f0d412016..e1a6b4c1b4 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 @@ -45,6 +45,9 @@ test.register_message_test( { device_uuid = mock_simple_device.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -70,6 +73,9 @@ test.register_message_test( { device_uuid = mock_simple_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -95,6 +101,9 @@ test.register_message_test( { device_uuid = mock_simple_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -121,6 +130,9 @@ test.register_message_test( math.floor(57 * 0xFE / 100), 0) } } + }, + { + min_api_version = 19 } ) @@ -161,7 +173,10 @@ test.register_coroutine_test( 1) }) mock_simple_device:expect_metadata_update({provisioning_state = "PROVISIONED"}) - end + end, + { + min_api_version = 19 + } ) -- test.register_coroutine_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 082aa0c5c2..9a3f8bdf3b 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 @@ -63,7 +63,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -71,7 +74,10 @@ 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",capabilities.switchLevel.level(100))) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -100,7 +106,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -130,7 +137,8 @@ test.register_message_test( } }, { - inner_block_ordering = "RELAXED" + inner_block_ordering = "RELAXED", + min_api_version = 19 } ) 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 8e2e847e2c..e956f63685 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_osram_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_osram_light.lua @@ -63,7 +63,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -71,7 +74,10 @@ 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",capabilities.switchLevel.level(100))) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -100,7 +106,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 68af9fdedb..61af32086d 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_rgb_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_rgb_bulb.lua @@ -65,7 +65,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -110,7 +113,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -135,7 +139,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -159,7 +166,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -188,7 +198,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device)}) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 24ec471c9d..401ed092bf 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_rgbw_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_rgbw_bulb.lua @@ -88,7 +88,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -141,7 +144,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -166,7 +170,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -190,7 +197,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -216,7 +226,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device)}) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -239,7 +252,10 @@ test.register_coroutine_test( test.mock_time.advance_time(1) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -252,7 +268,10 @@ test.register_coroutine_test( ColorControl.commands.MoveToColorTemperature(mock_device, 200, 0) } ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 1c815e2d7c..7638b23411 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 @@ -54,6 +54,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.server.commands.On(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -78,6 +81,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.server.commands.Off(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -103,6 +109,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -120,6 +129,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 9.0, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -138,6 +150,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 15.0, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) 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 3ed587916c..577e587dfc 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 @@ -54,6 +54,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.server.commands.On(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -78,6 +81,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.server.commands.Off(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -103,6 +109,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -121,6 +130,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 15.0, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) 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 e2ddba42d3..a1696f4110 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 @@ -74,7 +74,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -111,7 +114,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -139,7 +143,10 @@ test.register_coroutine_test( test.mock_time.advance_time(1) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 e28c4de0b6..f3a25f2186 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 @@ -67,7 +67,10 @@ test.register_coroutine_test( IASZone.attributes.ZoneStatus:configure_reporting(mock_device, 30, 300, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -78,7 +81,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -89,7 +95,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -108,7 +117,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -127,7 +139,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -146,7 +161,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -170,6 +188,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "motionSensor", capability_attr_id = "motion" } } }, + }, + { + min_api_version = 19 } ) @@ -194,6 +215,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "motionSensor", capability_attr_id = "motion" } } }, + }, + { + min_api_version = 19 } ) 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 0859138a8c..345c84059a 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_sinope_dimmer.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_sinope_dimmer.lua @@ -71,7 +71,11 @@ test.register_coroutine_test( data_types.validate_or_build_type(updates.preferences.ledIntensity, data_types.Uint8, "payload"))}) test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } + ) test.register_coroutine_test( @@ -103,7 +107,11 @@ test.register_coroutine_test( data_types.validate_or_build_type(updates.preferences.ledIntensity, data_types.Uint8, "payload"))}) test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } + ) test.register_coroutine_test( "infochanged to check for necessary preferences settings or updated when ledIntensity and minimalIntensity preference settings are zero with swBuild > 106", @@ -138,7 +146,11 @@ test.register_coroutine_test( data_types.validate_or_build_type(updates.preferences.ledIntensity, data_types.Uint8, "payload"))}) test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } + ) test.register_coroutine_test( @@ -170,7 +182,11 @@ test.register_coroutine_test( data_types.validate_or_build_type(updates.preferences.ledIntensity, data_types.Uint8, "payload"))}) test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } + ) test.register_coroutine_test( @@ -196,7 +212,11 @@ test.register_coroutine_test( data_types.validate_or_build_type(600, data_types.Uint16, "payload"))}) test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } + ) test.register_coroutine_test( @@ -218,7 +238,11 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } + ) test.register_coroutine_test( @@ -254,7 +278,11 @@ test.register_coroutine_test( data_types.validate_or_build_type(updates.preferences.ledIntensity, data_types.Uint8, "payload"))}) test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } + ) test.register_coroutine_test( @@ -286,7 +314,11 @@ test.register_coroutine_test( data_types.validate_or_build_type(updates.preferences.ledIntensity, data_types.Uint8, "payload"))}) test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } + ) test.register_message_test( @@ -326,6 +358,9 @@ test.register_message_test( Basic.attributes.ApplicationVersion:read(mock_device) } }, + }, + { + min_api_version = 19 } ) 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 34e5f78640..d6c5ee2b14 100755 --- a/drivers/SmartThings/zigbee-switch/src/test/test_sinope_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_sinope_switch.lua @@ -55,7 +55,11 @@ test.register_coroutine_test( data_types.validate_or_build_type(device_info_copy.preferences.ledIntensity, data_types.Uint8, "payload"))}) test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } + ) test.register_coroutine_test( @@ -78,7 +82,11 @@ test.register_coroutine_test( data_types.validate_or_build_type(device_info_copy.preferences.ledIntensity, data_types.Uint8, "payload"))}) test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } + ) test.register_coroutine_test( @@ -101,7 +109,11 @@ test.register_coroutine_test( data_types.validate_or_build_type(device_info_copy.preferences.ledIntensity, data_types.Uint8, "payload"))}) test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } + ) test.run_registered_tests() 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 04c17b15de..ce9fd12cb1 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_switch_power.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_switch_power.lua @@ -123,7 +123,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, SimpleMetering.attributes.Divisor:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, SimpleMetering.attributes.Multiplier:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -176,7 +179,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -236,7 +240,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_aurora_relay_device.id, ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_aurora_relay_device) }) test.socket.zigbee:__expect_send({ mock_aurora_relay_device.id, SimpleMetering.attributes.InstantaneousDemand:read(mock_aurora_relay_device) }) mock_aurora_relay_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -303,7 +310,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_vimar_device.id, ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_vimar_device) }) test.socket.zigbee:__expect_send({ mock_vimar_device.id, SimpleMetering.attributes.InstantaneousDemand:read(mock_vimar_device) }) mock_vimar_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) 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 a00f74bddf..6d1c3ed59b 100755 --- a/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua @@ -1,82 +1,85 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local test = require "integration_test" -local clusters = require "st.zigbee.zcl.clusters" -local BasicCluster = clusters.Basic -local OnOffCluster = clusters.OnOff -local t_utils = require "integration_test.utils" -local zigbee_test_utils = require "integration_test.zigbee_test_utils" - -local profile = t_utils.get_profile_definition("basic-switch.yml") - -local mock_device = test.mock_device.build_test_zigbee_device({ - label = "Zigbee Switch", - profile = profile, - zigbee_endpoints = { - [1] = { - id = 1, - manufacturer = "_TZ123fas", - server_clusters = { 0x0006 }, - }, - [2] = { - id = 2, - manufacturer = "_TZ123fas", - server_clusters = { 0x0006 }, - }, - }, - fingerprinted_endpoint_id = 0x01 -}) - -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) -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, - 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: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, - 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_attribute_read(mock_device, BasicCluster.ID, {0x0004, 0x0000, 0x0001, 0x0005, 0x0007, 0xfffe}) - }) - - mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end -) - -test.run_registered_tests() +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local BasicCluster = clusters.Basic +local OnOffCluster = clusters.OnOff +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local profile = t_utils.get_profile_definition("basic-switch.yml") + +local mock_device = test.mock_device.build_test_zigbee_device({ + label = "Zigbee Switch", + profile = profile, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "_TZ123fas", + server_clusters = { 0x0006 }, + }, + [2] = { + id = 2, + manufacturer = "_TZ123fas", + server_clusters = { 0x0006 }, + }, + }, + fingerprinted_endpoint_id = 0x01 +}) + +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) +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, + 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: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, + 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_attribute_read(mock_device, BasicCluster.ID, {0x0004, 0x0000, 0x0001, 0x0005, 0x0007, 0xfffe}) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 19 + } +) + +test.run_registered_tests() 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 1ff11bd754..0a4be10929 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 @@ -37,6 +37,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("switch1", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -54,6 +57,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("switch2", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) test.register_message_test( @@ -70,6 +76,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("switch3", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -87,6 +96,9 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("switch1", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -103,6 +115,9 @@ test.register_message_test( direction = "send", message = { mock_simple_device.id, OnOff.server.commands.On(mock_simple_device):to_endpoint(0x03) } } + }, + { + min_api_version = 19 } ) 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 1a8d42dba5..2940de9cc1 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua @@ -180,6 +180,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -210,6 +213,9 @@ test.register_message_test( { device_uuid = mock_first_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -240,6 +246,9 @@ test.register_message_test( { device_uuid = mock_second_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -270,6 +279,9 @@ test.register_message_test( { device_uuid = mock_third_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -300,6 +312,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -330,6 +345,9 @@ test.register_message_test( { device_uuid = mock_first_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -360,6 +378,9 @@ test.register_message_test( { device_uuid = mock_second_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -390,6 +411,9 @@ test.register_message_test( { device_uuid = mock_third_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -402,7 +426,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_parent_device.id, cluster_base.write_manufacturer_specific_attribute(mock_parent_device, 0x0006, 0x6000, 0x1235, data_types.Uint8, 0x01) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -414,7 +441,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_parent_device.id, cluster_base.write_manufacturer_specific_attribute(mock_parent_device, 0x0006, 0x6000, 0x1235, data_types.Uint8, 0x00) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -443,6 +473,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x01) } } + }, + { + min_api_version = 19 } ) @@ -467,6 +500,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x02) } } + }, + { + min_api_version = 19 } ) @@ -491,6 +527,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x03) } } + }, + { + min_api_version = 19 } ) @@ -515,6 +554,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x04) } } + }, + { + min_api_version = 19 } ) @@ -540,6 +582,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x01) } } + }, + { + min_api_version = 19 } ) @@ -564,6 +609,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x02) } } + }, + { + min_api_version = 19 } ) @@ -588,6 +636,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x03) } } + }, + { + min_api_version = 19 } ) @@ -612,6 +663,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x04) } } + }, + { + min_api_version = 19 } ) @@ -626,7 +680,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_fourth_child:generate_test_message("main", capabilities.button.button.pushed( { state_change = true } ))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -639,7 +696,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_fifth_child:generate_test_message("main", capabilities.button.button.pushed( { state_change = true } ))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -652,7 +712,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_sixth_child:generate_test_message("main", capabilities.button.button.pushed( { state_change = true } ))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -665,7 +728,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_seventh_child:generate_test_message("main", capabilities.button.button.pushed( { state_change = true } ))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -726,7 +792,10 @@ test.register_coroutine_test( mock_base_device.id, OnOff.attributes.OnOff:read(mock_base_device):to_endpoint(0x01) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -737,7 +806,10 @@ test.register_coroutine_test( capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }))) test.socket.capability:__expect_send(mock_button_device:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 f024aa45be..5614cfddc1 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 @@ -74,7 +74,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -111,7 +114,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -139,7 +143,10 @@ test.register_coroutine_test( test.mock_time.advance_time(1) test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua index 61ad14e75d..b5f6b3ea89 100755 --- a/drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua @@ -99,6 +99,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -129,6 +132,9 @@ test.register_message_test( { device_uuid = mock_first_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -159,6 +165,9 @@ test.register_message_test( { device_uuid = mock_second_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -190,6 +199,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -220,6 +232,9 @@ test.register_message_test( { device_uuid = mock_first_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -250,6 +265,9 @@ test.register_message_test( { device_uuid = mock_second_child.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -280,6 +298,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x01) } } + }, + { + min_api_version = 19 } ) @@ -304,6 +325,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x02) } } + }, + { + min_api_version = 19 } ) @@ -328,6 +352,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x03) } } + }, + { + min_api_version = 19 } ) @@ -355,6 +382,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x01) } } + }, + { + min_api_version = 19 } ) @@ -379,6 +409,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x02) } } + }, + { + min_api_version = 19 } ) @@ -403,6 +436,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x03) } } + }, + { + min_api_version = 19 } ) 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 8995523952..13e5d3bba0 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 @@ -52,6 +52,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.server.commands.On(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -76,6 +79,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.server.commands.Off(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -92,6 +98,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 9.766, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -116,6 +125,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -132,6 +144,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.009766, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) 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 d701bd89aa..a7988b9681 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 @@ -64,6 +64,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 0.042, unit = "kWh"})) } + }, + { + min_api_version = 19 } ) @@ -80,6 +83,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 32.0, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -145,7 +151,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) 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 e1519a4d1a..a6fbabf429 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 @@ -47,6 +47,9 @@ test.register_message_test( direction = "send", message = { mock_simple_device.id, OnOff.server.commands.On(mock_simple_device):to_endpoint(0x02) } } + }, + { + min_api_version = 19 } ) @@ -68,6 +71,9 @@ test.register_message_test( direction = "send", message = { mock_simple_device.id, OnOff.server.commands.Off(mock_simple_device):to_endpoint(0x02) } } + }, + { + min_api_version = 19 } ) 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 1a13044f48..4a46c58828 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 @@ -42,7 +42,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -63,7 +66,8 @@ test.register_coroutine_test( test_init = function() test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "polling") - end + end, + min_api_version = 19 } ) @@ -80,7 +84,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -96,7 +103,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -112,7 +122,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -129,7 +142,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 71305fba20..d1bb3db974 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer.lua @@ -65,7 +65,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -73,7 +76,10 @@ 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",capabilities.switchLevel.level(100))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -83,7 +89,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -147,7 +156,8 @@ test.register_coroutine_test( test_init = function() test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(5*60, "interval", "polling") - end + end, + min_api_version = 19 } ) @@ -159,7 +169,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.server.commands.MoveToLevelWithOnOff(mock_device, 144, 0xFFFF) }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 0ac17313d3..b925cd62d2 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 @@ -61,7 +61,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -71,7 +74,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -81,7 +87,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -101,7 +110,8 @@ test.register_coroutine_test( test_init = function() test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "polling") - end + end, + min_api_version = 19 } ) @@ -120,7 +130,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -138,7 +151,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -156,7 +172,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 a8384d1b13..3c4dd1c320 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 @@ -83,7 +83,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -95,7 +98,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentX:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -107,7 +113,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentX:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -129,7 +138,8 @@ test.register_coroutine_test( test_init = function() test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "polling") - end + end, + min_api_version = 19 } ) @@ -150,7 +160,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentX:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -170,7 +183,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentX:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -190,7 +206,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentX:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) local test_data = { @@ -235,7 +254,11 @@ for _, data in ipairs(test_data) do test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentX:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) - end + end, + { + min_api_version = 19 + } + ) end @@ -280,7 +303,11 @@ for _, data in ipairs(test_data) do test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentX:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) - end + end, + { + min_api_version = 19 + } + ) end @@ -319,7 +346,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentX:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -357,7 +387,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentX:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -381,7 +414,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentX:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -393,7 +429,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.hue(75))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.saturation(65))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -405,7 +444,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.hue(75))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.saturation(65))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -417,7 +459,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.hue(75))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.saturation(65))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -429,7 +474,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.hue(75))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.saturation(65))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 cf53f7e359..4ee1098364 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 @@ -84,7 +84,10 @@ test.register_coroutine_test( } ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -97,7 +100,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -120,7 +126,8 @@ test.register_coroutine_test( test_init = function() test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(5*60, "interval", "polling") - end + end, + min_api_version = 19 } ) @@ -142,7 +149,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -163,7 +173,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -184,7 +197,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -206,7 +222,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -231,7 +250,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -256,7 +278,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -283,7 +308,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua index e026edb5f8..bce1f03b95 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua @@ -75,7 +75,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_THERM0STAT_VALVE_DETECTION_SWITCH_ID, MFG_CODE, data_types.Uint8, 0x01) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -87,7 +90,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ANTIFREEZE_MODE_TEMPERATURE_SETTING_ID, MFG_CODE, data_types.Uint32, 500) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -112,7 +118,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -147,7 +156,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", valveCalibration.calibrationState.calibrationFailure())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -176,7 +188,10 @@ test.register_coroutine_test( capabilities.thermostatMode.thermostatMode.antifreezing())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", invisibleCapabilities.invisibleCapabilities({"thermostatHeatingSetpoint"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -241,7 +256,10 @@ test.register_coroutine_test( capabilities.valve.valve.open())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", invisibleCapabilities.invisibleCapabilities({"thermostatHeatingSetpoint"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -266,7 +284,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("ChildLock", capabilities.lock.lock.locked())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -282,7 +303,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(48)) ) - end + end, + { + min_api_version = 19 + } ) -- test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events", function() @@ -326,7 +350,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.manual()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -337,7 +364,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_VALVE_SWITCH_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -348,7 +378,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_VALVE_CALIBRATION_ID, MFG_CODE, data_types.Uint8, 0x01) }) - end + end, + { + min_api_version = 19 + } ) @@ -360,7 +393,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_CHILD_LOCK_ID, MFG_CODE, data_types.Uint8, 0x01) }) - end + end, + { + min_api_version = 19 + } ) --]] test.register_coroutine_test( @@ -432,7 +468,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(100)) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_centralite_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_centralite_thermostat.lua index 206333ffa7..6188e8327b 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_centralite_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_centralite_thermostat.lua @@ -46,6 +46,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -63,6 +66,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -80,6 +86,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -108,7 +117,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedCoolingSetpoint:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -136,7 +148,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedCoolingSetpoint:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_danfoss_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_danfoss_thermostat.lua index 98727e563e..aba128d9ec 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_danfoss_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_danfoss_thermostat.lua @@ -42,6 +42,9 @@ test.register_message_test( direction = "send", message = mock_device_danfoss:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -50,7 +53,10 @@ test.register_coroutine_test( function () test.socket.zigbee:__queue_receive({ mock_device_danfoss.id, Thermostat.attributes.LocalTemperature:build_test_attr_report(mock_device_danfoss, 2100) }) test.socket.capability:__expect_send(mock_device_danfoss:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.0, unit = "C"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -58,14 +64,20 @@ test.register_coroutine_test( function () test.socket.zigbee:__queue_receive({ mock_device_danfoss.id, Thermostat.attributes.OccupiedHeatingSetpoint:build_test_attr_report(mock_device_danfoss, 2100) }) test.socket.capability:__expect_send(mock_device_danfoss:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 21.0, unit = "C"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Thermostat cooling setpoint reporting should not create setpoint events, the mode is not supported Danfoss", function () test.socket.zigbee:__queue_receive({ mock_device_danfoss.id, Thermostat.attributes.OccupiedCoolingSetpoint:build_test_attr_report(mock_device_danfoss, 2100) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -88,7 +100,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device_danfoss:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -104,6 +119,9 @@ test.register_message_test( direction = "send", message = mock_device_danfoss:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_fidure_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_fidure_thermostat.lua index 1f7a03f049..a6c72695fc 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_fidure_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_fidure_thermostat.lua @@ -61,7 +61,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -73,6 +76,9 @@ test.register_message_test( message = { mock_device.id, Thermostat.attributes.ThermostatRunningMode:build_test_attr_report(mock_device, 3), } } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_leviton_rc.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_leviton_rc.lua index 6e6cdb0305..a62cb6a2a2 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_leviton_rc.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_leviton_rc.lua @@ -51,7 +51,10 @@ test.register_coroutine_test( mock_device.id, FanControl.attributes.FanMode:configure_reporting(mock_device, 5, 1800, nil):to_endpoint(ENDPOINT)}) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -65,7 +68,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.OccupiedCoolingSetpoint:read(mock_device):to_endpoint(ENDPOINT) }) test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:read(mock_device):to_endpoint(ENDPOINT) }) test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.LocalTemperature:read(mock_device):to_endpoint(ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -73,7 +79,10 @@ test.register_coroutine_test( function () test.socket.zigbee:__queue_receive({ mock_device.id, Thermostat.attributes.LocalTemperature:build_test_attr_report(mock_device, 2100) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 21.0, unit = "C"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -81,7 +90,10 @@ test.register_coroutine_test( function () test.socket.zigbee:__queue_receive({ mock_device.id, Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, Thermostat.attributes.SystemMode.OFF) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.off())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -90,7 +102,10 @@ test.register_coroutine_test( function () test.socket.zigbee:__queue_receive({ mock_device.id, Thermostat.attributes.ControlSequenceOfOperation:build_test_attr_report(mock_device, 0x02)}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"auto", "cool", "heat", "emergency heat"},{visibility = {displayed = false }}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -98,7 +113,10 @@ test.register_coroutine_test( function () test.socket.zigbee:__queue_receive({ mock_device.id, FanControl.attributes.FanMode:build_test_attr_report(mock_device, FanControl.attributes.FanMode.AUTO) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatFanMode.thermostatFanMode.auto())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -110,7 +128,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.cool())) test.socket.zigbee:__queue_receive({ mock_device.id, Thermostat.attributes.OccupiedCoolingSetpoint:build_test_attr_report(mock_device, 2100) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({value = 21.0, unit = "C"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -122,7 +143,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.emergency_heat())) test.socket.zigbee:__queue_receive({ mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:build_test_attr_report(mock_device, 2100) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 21.0, unit = "C"}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -133,7 +157,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, Thermostat.attributes.SystemMode.COOL)}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.cool())) test.socket.zigbee:__queue_receive({ mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:build_test_attr_report(mock_device, 2100) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -144,7 +171,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, Thermostat.attributes.SystemMode.EMERGENCY_HEATING)}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.emergency_heat())) test.socket.zigbee:__queue_receive({ mock_device.id, Thermostat.attributes.OccupiedCoolingSetpoint:build_test_attr_report(mock_device, 2100) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -153,7 +183,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { component = "main", capability = capabilities.thermostatHeatingSetpoint.ID, command = "setHeatingSetpoint", args = {21} } }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 21.0, unit = "C"}))) test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 2100):to_endpoint(ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -162,7 +195,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { component = "main", capability = capabilities.thermostatCoolingSetpoint.ID, command = "setCoolingSetpoint", args = {21} } }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({value = 21.0, unit = "C"}))) test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, 2100):to_endpoint(ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -170,7 +206,10 @@ test.register_coroutine_test( function () test.socket.capability:__queue_receive({ mock_device.id, { component = "main", capability = capabilities.thermostatMode.ID, command = "setThermostatMode", args = {"cool"} } }) test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.SystemMode:write(mock_device, 3):to_endpoint(ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -178,7 +217,10 @@ test.register_coroutine_test( function () test.socket.capability:__queue_receive({ mock_device.id, { component = "main", capability = capabilities.thermostatFanMode.ID, command = "setThermostatFanMode", args = {"auto"} } }) test.socket.zigbee:__expect_send( { mock_device.id, FanControl.attributes.FanMode:write(mock_device, 5):to_endpoint(ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -209,7 +251,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.OccupiedCoolingSetpoint:read(mock_device):to_endpoint(ENDPOINT) }) test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:read(mock_device):to_endpoint(ENDPOINT) }) test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.LocalTemperature:read(mock_device):to_endpoint(ENDPOINT) }) - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_popp_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_popp_thermostat.lua index ecff7883da..abf082dc0a 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_popp_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_popp_thermostat.lua @@ -67,6 +67,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -95,7 +98,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedHeatingSetpoint:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -114,7 +120,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, Thermostat.ID, EXTERNAL_WINDOW_OPEN_DETECTION, MFG_CODE, data_types.Boolean, false) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -133,7 +142,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, Thermostat.ID, EXTERNAL_WINDOW_OPEN_DETECTION, MFG_CODE, data_types.Boolean, true) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -154,7 +166,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedHeatingSetpoint:configure_reporting(mock_device, 5, 300, 50) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -173,6 +188,9 @@ test.register_message_test( { ThermostatMode.thermostatMode.heat.NAME, ThermostatMode.thermostatMode.eco.NAME }, { visibility = { displayed = false } })) } + }, + { + min_api_version = 19 } ) @@ -234,6 +252,9 @@ test.register_message_test( MFG_CODE) } } + }, + { + min_api_version = 19 } ) @@ -268,7 +289,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" }))) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -293,7 +317,10 @@ test.register_coroutine_test( capabilities.battery.battery(batt_perc))) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -327,7 +354,10 @@ test.register_coroutine_test( Thermostat.attributes.SystemMode:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -361,7 +391,10 @@ test.register_coroutine_test( Thermostat.attributes.SystemMode:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -375,7 +408,10 @@ test.register_coroutine_test( } ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.eco())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -424,7 +460,10 @@ test.register_coroutine_test( EXTERNAL_WINDOW_OPEN_DETECTION, MFG_CODE )}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -438,7 +477,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, Thermostat.ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -452,7 +494,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, Thermostat.ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua index 4c7f4bdcaf..22004cb322 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua @@ -100,7 +100,11 @@ test.register_coroutine_test("Configure should configure all necessary attribute mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -end) +end, +{ + min_api_version = 19 +} +) -------------------------------------------------------------------------------- -- Parent thermostat device @@ -119,7 +123,11 @@ test.register_coroutine_test("Refresh should read all necessary attributes", fun for _, attribute in pairs(attributes) do test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_device)}) end -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Temperature reporting should create the appropriate events", function() test.socket.zigbee:__queue_receive({mock_device.id, @@ -129,7 +137,11 @@ test.register_coroutine_test("Temperature reporting should create the appropriat value = 21.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Thermostat mode reporting should create the appropriate events", function() test.socket.zigbee:__queue_receive({mock_device.id, @@ -142,7 +154,11 @@ test.register_coroutine_test("Thermostat mode reporting should create the approp Thermostat.attributes.SystemMode.HEAT)}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode .thermostatMode.heat())) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events", function() test.socket.zigbee:__queue_receive({mock_device.id, @@ -154,7 +170,11 @@ test.register_coroutine_test("ControlSequenceOfOperation reporting should create displayed = false } }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create the appropriate events", function() test.socket.zigbee:__queue_receive({mock_device.id, @@ -165,7 +185,11 @@ test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create th value = 21.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the heating setpoint should generate the appropriate messages", function() test.socket.capability:__queue_receive({mock_device.id, { @@ -176,7 +200,11 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a }}) test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 2100)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the thermostat mode to away should generate the appropriate messages", function() test.socket.capability:__queue_receive({mock_device.id, { @@ -188,7 +216,11 @@ test.register_coroutine_test("Setting the thermostat mode to away should generat test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.SystemMode:write(mock_device, Thermostat.attributes.SystemMode.OFF)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the thermostat mode to heat should generate the appropriate messages", function() test.socket.capability:__queue_receive({mock_device.id, { @@ -200,7 +232,11 @@ test.register_coroutine_test("Setting the thermostat mode to heat should generat test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.SystemMode:write(mock_device, Thermostat.attributes.SystemMode.HEAT)}) -end) +end, +{ + min_api_version = 19 +} +) -------------------------------------------------------------------------------- -- First child thermostat device @@ -219,7 +255,11 @@ test.register_coroutine_test("Refresh should read all necessary attributes with for _, attribute in pairs(attributes) do test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_first_child)}) end -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Temperature reporting should create the appropriate events with first child device", function() test.socket.zigbee:__queue_receive({mock_first_child.id, @@ -229,7 +269,11 @@ test.register_coroutine_test("Temperature reporting should create the appropriat value = 21.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Thermostat mode reporting should create the appropriate events with first child device", function() test.socket.zigbee:__queue_receive({mock_first_child.id, @@ -242,7 +286,11 @@ test.register_coroutine_test("Thermostat mode reporting should create the approp Thermostat.attributes.SystemMode.HEAT)}) test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.thermostatMode .thermostatMode.heat())) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events with first child device", function() test.socket.zigbee:__queue_receive({mock_first_child.id, @@ -254,7 +302,11 @@ test.register_coroutine_test("ControlSequenceOfOperation reporting should create displayed = false } }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create the appropriate events with first child device", function() test.socket.zigbee:__queue_receive({mock_first_child.id, @@ -265,7 +317,11 @@ test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create th value = 21.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the heating setpoint should generate the appropriate messages with first child device", function() test.socket.capability:__queue_receive({mock_first_child.id, { @@ -276,7 +332,11 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a }}) test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_first_child, 2100)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the thermostat mode to away should generate the appropriate messages with first child device", function() test.socket.capability:__queue_receive({mock_first_child.id, { @@ -288,7 +348,11 @@ test.register_coroutine_test("Setting the thermostat mode to away should generat test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.SystemMode:write(mock_first_child, Thermostat.attributes.SystemMode.OFF)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the thermostat mode to heat should generate the appropriate messages with first child device", function() test.socket.capability:__queue_receive({mock_first_child.id, { @@ -300,7 +364,11 @@ test.register_coroutine_test("Setting the thermostat mode to heat should generat test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.SystemMode:write(mock_first_child, Thermostat.attributes.SystemMode.HEAT)}) -end) +end, +{ + min_api_version = 19 +} +) -------------------------------------------------------------------------------- -- Second child thermostat device @@ -319,7 +387,11 @@ test.register_coroutine_test("Refresh should read all necessary attributes with for _, attribute in pairs(attributes) do test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_second_child)}) end -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Temperature reporting should create the appropriate events with second child device", function() test.socket.zigbee:__queue_receive({mock_second_child.id, @@ -329,7 +401,11 @@ test.register_coroutine_test("Temperature reporting should create the appropriat value = 21.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Thermostat mode reporting should create the appropriate events with second child device", function() test.socket.zigbee:__queue_receive({mock_second_child.id, @@ -342,7 +418,11 @@ test.register_coroutine_test("Thermostat mode reporting should create the approp Thermostat.attributes.SystemMode.HEAT)}) test.socket.capability:__expect_send(mock_second_child:generate_test_message("main", capabilities.thermostatMode .thermostatMode.heat())) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events with second child device", function() test.socket.zigbee:__queue_receive({mock_second_child.id, @@ -354,7 +434,11 @@ test.register_coroutine_test("ControlSequenceOfOperation reporting should create displayed = false } }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create the appropriate events with second child device", function() test.socket.zigbee:__queue_receive({mock_second_child.id, @@ -365,7 +449,11 @@ test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create th value = 21.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the heating setpoint should generate the appropriate messages with second child device", function() test.socket.capability:__queue_receive({mock_second_child.id, { @@ -376,7 +464,11 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a }}) test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_second_child, 2100)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the thermostat mode to away should generate the appropriate messages with second child device", function() test.socket.capability:__queue_receive({mock_second_child.id, { @@ -388,7 +480,11 @@ test.register_coroutine_test("Setting the thermostat mode to away should generat test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.SystemMode:write(mock_second_child, Thermostat.attributes.SystemMode.OFF)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the thermostat mode to heat should generate the appropriate messages with second child device", function() test.socket.capability:__queue_receive({mock_second_child.id, { @@ -400,7 +496,11 @@ test.register_coroutine_test("Setting the thermostat mode to heat should generat test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.SystemMode:write(mock_second_child, Thermostat.attributes.SystemMode.HEAT)}) -end) +end, +{ + min_api_version = 19 +} +) -------------------------------------------------------------------------------- -- Third child thermostat device @@ -419,7 +519,11 @@ test.register_coroutine_test("Refresh should read all necessary attributes with for _, attribute in pairs(attributes) do test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_third_child)}) end -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Temperature reporting should create the appropriate events with third child device", function() test.socket.zigbee:__queue_receive({mock_third_child.id, @@ -429,7 +533,11 @@ test.register_coroutine_test("Temperature reporting should create the appropriat value = 21.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Thermostat mode reporting should create the appropriate events with third child device", function() test.socket.zigbee:__queue_receive({mock_third_child.id, @@ -442,7 +550,11 @@ test.register_coroutine_test("Thermostat mode reporting should create the approp Thermostat.attributes.SystemMode.HEAT)}) test.socket.capability:__expect_send(mock_third_child:generate_test_message("main", capabilities.thermostatMode .thermostatMode.heat())) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events with third child device", function() test.socket.zigbee:__queue_receive({mock_third_child.id, @@ -454,7 +566,11 @@ test.register_coroutine_test("ControlSequenceOfOperation reporting should create displayed = false } }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create the appropriate events with third child device", function() test.socket.zigbee:__queue_receive({mock_third_child.id, @@ -465,7 +581,11 @@ test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create th value = 21.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the heating setpoint should generate the appropriate messages with third child device", function() test.socket.capability:__queue_receive({mock_third_child.id, { @@ -476,7 +596,11 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a }}) test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_third_child, 2100)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the thermostat mode to away should generate the appropriate messages with third child device", function() test.socket.capability:__queue_receive({mock_third_child.id, { @@ -488,7 +612,11 @@ test.register_coroutine_test("Setting the thermostat mode to away should generat test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.SystemMode:write(mock_third_child, Thermostat.attributes.SystemMode.OFF)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the thermostat mode to heat should generate the appropriate messages with third child device", function() test.socket.capability:__queue_receive({mock_third_child.id, { @@ -500,7 +628,11 @@ test.register_coroutine_test("Setting the thermostat mode to heat should generat test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.SystemMode:write(mock_third_child, Thermostat.attributes.SystemMode.HEAT)}) -end) +end, +{ + min_api_version = 19 +} +) -------------------------------------------------------------------------------- -- Forth child thermostat device @@ -519,7 +651,11 @@ test.register_coroutine_test("Refresh should read all necessary attributes with for _, attribute in pairs(attributes) do test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_forth_child)}) end -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Temperature reporting should create the appropriate events with forth child device", function() test.socket.zigbee:__queue_receive({mock_forth_child.id, @@ -529,7 +665,11 @@ test.register_coroutine_test("Temperature reporting should create the appropriat value = 21.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Thermostat mode reporting should create the appropriate events with forth child device", function() test.socket.zigbee:__queue_receive({mock_forth_child.id, @@ -542,7 +682,11 @@ test.register_coroutine_test("Thermostat mode reporting should create the approp Thermostat.attributes.SystemMode.HEAT)}) test.socket.capability:__expect_send(mock_forth_child:generate_test_message("main", capabilities.thermostatMode .thermostatMode.heat())) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events with forth child device", function() test.socket.zigbee:__queue_receive({mock_forth_child.id, @@ -554,7 +698,11 @@ test.register_coroutine_test("ControlSequenceOfOperation reporting should create displayed = false } }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create the appropriate events with forth child device", function() test.socket.zigbee:__queue_receive({mock_forth_child.id, @@ -565,7 +713,11 @@ test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create th value = 21.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the heating setpoint should generate the appropriate messages with forth child device", function() test.socket.capability:__queue_receive({mock_forth_child.id, { @@ -576,7 +728,11 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a }}) test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_forth_child, 2100)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the thermostat mode to away should generate the appropriate messages with forth child device", function() test.socket.capability:__queue_receive({mock_forth_child.id, { @@ -588,7 +744,11 @@ test.register_coroutine_test("Setting the thermostat mode to away should generat test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.SystemMode:write(mock_forth_child, Thermostat.attributes.SystemMode.OFF)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the thermostat mode to heat should generate the appropriate messages with forth child device", function() test.socket.capability:__queue_receive({mock_forth_child.id, { @@ -600,7 +760,11 @@ test.register_coroutine_test("Setting the thermostat mode to heat should generat test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.SystemMode:write(mock_forth_child, Thermostat.attributes.SystemMode.HEAT)}) -end) +end, +{ + min_api_version = 19 +} +) -------------------------------------------------------------------------------- -- Fifth child thermostat device @@ -619,7 +783,11 @@ test.register_coroutine_test("Refresh should read all necessary attributes with for _, attribute in pairs(attributes) do test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_fifth_child)}) end -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Temperature reporting should create the appropriate events with fifth child device", function() test.socket.zigbee:__queue_receive({mock_fifth_child.id, @@ -629,7 +797,11 @@ test.register_coroutine_test("Temperature reporting should create the appropriat value = 21.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Thermostat mode reporting should create the appropriate events with fifth child device", function() test.socket.zigbee:__queue_receive({mock_fifth_child.id, @@ -642,7 +814,11 @@ test.register_coroutine_test("Thermostat mode reporting should create the approp Thermostat.attributes.SystemMode.HEAT)}) test.socket.capability:__expect_send(mock_fifth_child:generate_test_message("main", capabilities.thermostatMode .thermostatMode.heat())) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events with fifth child device", function() test.socket.zigbee:__queue_receive({mock_fifth_child.id, @@ -654,7 +830,11 @@ test.register_coroutine_test("ControlSequenceOfOperation reporting should create displayed = false } }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create the appropriate events with fifth child device", function() test.socket.zigbee:__queue_receive({mock_fifth_child.id, @@ -665,7 +845,11 @@ test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create th value = 21.0, unit = "C" }))) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the heating setpoint should generate the appropriate messages with fifth child device", function() test.socket.capability:__queue_receive({mock_fifth_child.id, { @@ -676,7 +860,11 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a }}) test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_fifth_child, 2100)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the thermostat mode to away should generate the appropriate messages with fifth child device", function() test.socket.capability:__queue_receive({mock_fifth_child.id, { @@ -688,7 +876,11 @@ test.register_coroutine_test("Setting the thermostat mode to away should generat test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.SystemMode:write(mock_fifth_child, Thermostat.attributes.SystemMode.OFF)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("Setting the thermostat mode to heat should generate the appropriate messages with fifth child device", function() test.socket.capability:__queue_receive({mock_fifth_child.id, { @@ -700,7 +892,11 @@ test.register_coroutine_test("Setting the thermostat mode to heat should generat test.socket.zigbee:__expect_send({mock_device.id, Thermostat.attributes.SystemMode:write(mock_fifth_child, Thermostat.attributes.SystemMode.HEAT)}) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test("ThermostatRunningState reporting shoulb create the appropriate events", function() test.socket.zigbee:__queue_receive({mock_device.id, @@ -715,6 +911,10 @@ test.register_coroutine_test("ThermostatRunningState reporting shoulb create the Thermostat.attributes.ThermostatRunningState:build_test_attr_report(mock_device, 0x0004)}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState({value="fan only"}))) -end) +end, +{ + min_api_version = 19 +} +) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1300_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1300_thermostat.lua index 6805e6c604..13fee65646 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1300_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1300_thermostat.lua @@ -45,6 +45,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -61,6 +64,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState("idle")) } + }, + { + min_api_version = 19 } ) @@ -94,7 +100,10 @@ test.register_coroutine_test( Thermostat.attributes.SystemMode:configure_reporting(mock_device, 10, 305) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -187,7 +196,10 @@ test.register_coroutine_test( data_types.validate_or_build_type(3000, data_types.Int16, "payload") ) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1400_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1400_thermostat.lua index 5ca78c3675..1f945776cd 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1400_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1400_thermostat.lua @@ -45,6 +45,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -61,6 +64,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState("idle")) } + }, + { + min_api_version = 19 } ) @@ -94,7 +100,10 @@ test.register_coroutine_test( Thermostat.attributes.SystemMode:configure_reporting(mock_device, 10, 305) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -197,7 +206,10 @@ test.register_coroutine_test( data_types.validate_or_build_type(0x0708, data_types.Uint16, "payload") ) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua index 74cc7a2115..e039e0999f 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua @@ -45,6 +45,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -61,6 +64,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState("idle")) } + }, + { + min_api_version = 19 } ) @@ -94,7 +100,10 @@ test.register_coroutine_test( Thermostat.attributes.SystemMode:configure_reporting(mock_device, 10, 305) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -171,7 +180,10 @@ test.register_coroutine_test( ) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -198,7 +210,10 @@ test.register_coroutine_test( mock_device.id, Thermostat.attributes.SystemMode:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua index 4ff17803fa..358c381eb7 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua @@ -57,6 +57,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.freeze()) } + }, + { + min_api_version = 19 } ) @@ -73,6 +76,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) } + }, + { + min_api_version = 19 } ) @@ -94,6 +100,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 55.0, unit = "C"})) } + }, + { + min_api_version = 19 } ) @@ -115,6 +124,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = -1.0, unit = "C"})) } + }, + { + min_api_version = 19 } ) @@ -136,6 +148,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 15.0, unit = "C"})) } + }, + { + min_api_version = 19 } ) @@ -152,6 +167,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 25.0, unit = "C"})) } + }, + { + min_api_version = 19 } ) @@ -168,6 +186,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", ThermostatMode.thermostatMode.off()) } + }, + { + min_api_version = 19 } ) @@ -187,6 +208,9 @@ test.register_message_test( zigbee_test_utils.build_attribute_read(mock_device, Thermostat.ID, {MFR_SETPOINT_MODE_ATTTRIBUTE}, MFG_CODE) } } + }, + { + min_api_version = 19 } ) @@ -216,7 +240,10 @@ test.register_coroutine_test( Thermostat.attributes.PIHeatingDemand:build_test_attr_report(mock_device, 0) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", ThermostatOperatingState.thermostatOperatingState("idle"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -236,7 +263,10 @@ test.register_coroutine_test( {{ MFR_SETPOINT_MODE_ATTTRIBUTE, data_types.Uint16.ID, 0x04}}, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", ThermostatMode.thermostatMode.heat())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -266,7 +296,10 @@ test.register_coroutine_test( mock_device.id, cluster_base.read_manufacturer_specific_attribute(mock_device, Thermostat.ID, MFR_SETPOINT_MODE_ATTTRIBUTE, MFG_CODE) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -296,7 +329,10 @@ test.register_coroutine_test( mock_device.id, cluster_base.read_manufacturer_specific_attribute(mock_device, Thermostat.ID, MFR_SETPOINT_MODE_ATTTRIBUTE, MFG_CODE) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -385,7 +421,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -467,6 +506,9 @@ test.register_message_test( cluster_base.read_manufacturer_specific_attribute(mock_device, Thermostat.ID, MFR_SETPOINT_MODE_ATTTRIBUTE, MFG_CODE) } }, + }, + { + min_api_version = 19 } ) @@ -487,7 +529,11 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a mock_device.id, Thermostat.attributes.PIHeatingDemand:read(mock_device) }) -end) +end, +{ + min_api_version = 19 +} +) test.register_coroutine_test( "Setting thermostat mode to eco should generate correct zigbee messages", @@ -515,7 +561,10 @@ test.register_coroutine_test( mock_device.id, cluster_base.read_manufacturer_specific_attribute(mock_device, Thermostat.ID, MFR_SETPOINT_MODE_ATTTRIBUTE, MFG_CODE) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "LocalTemperature handler should request PIHeatingDemand when setpoint > temperature", @@ -537,7 +586,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 20.0, unit = "C" })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -561,7 +613,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", ThermostatMode.thermostatMode.off()) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_thermostat.lua index 3380856f1c..60470857d6 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_thermostat.lua @@ -49,6 +49,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 18.5, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -70,6 +73,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.freeze()) } + }, + { + min_api_version = 19 } ) @@ -91,6 +97,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) } + }, + { + min_api_version = 19 } ) @@ -122,7 +131,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -153,7 +165,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -173,7 +188,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.freeze()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -193,7 +211,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.freeze()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -214,7 +235,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -230,6 +254,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState("idle")) } + }, + { + min_api_version = 19 } ) @@ -246,6 +273,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState("heating")) } + }, + { + min_api_version = 19 } ) @@ -262,6 +292,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 18.5, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -278,6 +311,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 25 })) } + }, + { + min_api_version = 19 } ) @@ -317,7 +353,10 @@ test.register_coroutine_test( test.wait_for_events() test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -359,7 +398,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -396,7 +438,10 @@ test.register_coroutine_test( test.wait_for_events() test.socket.zigbee:__set_channel_ordering("relaxed") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -438,7 +483,10 @@ test.register_coroutine_test( }) mock_device_maestro:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -450,7 +498,10 @@ test.register_coroutine_test( test.wait_for_events() -- Event not to be handled by driver test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({preferences = { lock = 1 } })) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -460,7 +511,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({preferences = { lock = 0 } })) test.socket.zigbee:__expect_send({mock_device.id, ThermostatUserInterfaceConfiguration.attributes.KeypadLockout:write(mock_device, 0x00)}) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -495,7 +549,10 @@ test.register_coroutine_test( mock_device.id, RelativeHumidity.attributes.MeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_vimar_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_vimar_thermostat.lua index 7b2a0cc8f6..e300ddd797 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_vimar_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_vimar_thermostat.lua @@ -85,6 +85,9 @@ test.register_message_test( capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" }) ) } + }, + { + min_api_version = 19 } ) @@ -113,6 +116,9 @@ test.register_message_test( capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 30.0, unit = "C" }) ) } + }, + { + min_api_version = 19 } ) @@ -141,6 +147,9 @@ test.register_message_test( capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 18.0, unit = "C" }) ) } + }, + { + min_api_version = 19 } ) @@ -177,7 +186,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedCoolingSetpoint:read(mock_device_vimar_cooling) } ) - end + end, + { + min_api_version = 19 + } ) -- Test (SmartThings -> Device) @@ -213,7 +225,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedCoolingSetpoint:read(mock_device_vimar_cooling) } ) - end + end, + { + min_api_version = 19 + } ) -- Test (SmartThings -> Device) @@ -252,7 +267,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedCoolingSetpoint:read(mock_device_vimar_cooling) } ) - end + end, + { + min_api_version = 19 + } ) -- Test (SmartThings -> Device) @@ -291,7 +309,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedCoolingSetpoint:read(mock_device_vimar_cooling) } ) - end + end, + { + min_api_version = 19 + } ) @@ -352,7 +373,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device_vimar_cooling, 2720) } ) - end + end, + { + min_api_version = 19 + } ) @@ -389,7 +413,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedHeatingSetpoint:read(mock_device_vimar_heating) } ) - end + end, + { + min_api_version = 19 + } ) -- Test (SmartThings -> Device) @@ -425,7 +452,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedHeatingSetpoint:read(mock_device_vimar_heating) } ) - end + end, + { + min_api_version = 19 + } ) -- Test (SmartThings -> Device) @@ -464,7 +494,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedHeatingSetpoint:read(mock_device_vimar_heating) } ) - end + end, + { + min_api_version = 19 + } ) -- Test (SmartThings -> Device) @@ -503,7 +536,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedHeatingSetpoint:read(mock_device_vimar_heating) } ) - end + end, + { + min_api_version = 19 + } ) @@ -564,7 +600,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device_vimar_heating, 1920) } ) - end + end, + { + min_api_version = 19 + } ) @@ -596,6 +635,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -627,6 +669,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -683,6 +728,9 @@ test.register_message_test( Thermostat.attributes.OccupiedHeatingSetpoint:read(mock_device_vimar_heating) } } + }, + { + min_api_version = 19 } ) @@ -740,6 +788,9 @@ test.register_message_test( Thermostat.attributes.OccupiedCoolingSetpoint:read(mock_device_vimar_cooling) } } + }, + { + min_api_version = 19 } ) @@ -767,7 +818,10 @@ test.register_coroutine_test( } } ) - end + end, + { + min_api_version = 19 + } ) -- Test (Device -> SmartThings) @@ -794,7 +848,10 @@ test.register_coroutine_test( } } ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua index 0886a7cd7f..a336f60170 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua @@ -47,7 +47,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -64,6 +67,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState("cooling")) } + }, + { + min_api_version = 19 } ) test.register_message_test( @@ -80,6 +86,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -97,6 +106,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -135,7 +147,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -194,7 +209,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({ "off", "heat", "cool" }, { visibility = { displayed = false } })) ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -271,7 +289,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 25.0, unit = "C" })) ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -403,7 +424,10 @@ test.register_coroutine_test( ) test.mock_time.advance_time(2) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -435,7 +459,10 @@ test.register_coroutine_test( ) test.mock_time.advance_time(2) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -447,6 +474,9 @@ test.register_message_test( message = { mock_device.id, Thermostat.attributes.ThermostatRunningMode:build_test_attr_report(mock_device, 3), } } + }, + { + min_api_version = 19 } ) @@ -487,7 +517,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 25.0, unit = "C" })) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat.lua index 380eacc1ce..db79a62151 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat.lua @@ -36,6 +36,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -53,6 +56,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -77,6 +83,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -94,6 +103,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -110,6 +122,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C"})) }, + }, + { + min_api_version = 19 } ) @@ -131,6 +146,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 20.00, maximum = 30.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -148,6 +166,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -165,6 +186,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -193,6 +217,9 @@ test.register_message_test( } )) } + }, + { + min_api_version = 19 } ) @@ -221,6 +248,9 @@ test.register_message_test( } )) } + }, + { + min_api_version = 19 } ) @@ -243,7 +273,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -265,7 +298,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -282,6 +318,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState("cooling")) } + }, + { + min_api_version = 19 } ) @@ -320,7 +359,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -337,6 +379,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.battery()) } + }, + { + min_api_version = 19 } ) @@ -365,7 +410,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedHeatingSetpoint:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -393,7 +441,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedCoolingSetpoint:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -421,7 +472,10 @@ test.register_coroutine_test( Thermostat.attributes.OccupiedCoolingSetpoint:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -449,7 +503,10 @@ test.register_coroutine_test( Thermostat.attributes.SystemMode:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -476,7 +533,10 @@ test.register_coroutine_test( Thermostat.attributes.SystemMode:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -504,7 +564,10 @@ test.register_coroutine_test( FanControl.attributes.FanMode:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -532,7 +595,10 @@ test.register_coroutine_test( FanControl.attributes.FanMode:read(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -575,7 +641,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -714,6 +783,9 @@ test.register_message_test( PowerConfiguration.attributes.BatteryAlarmState:read(mock_device) } }, + }, + { + min_api_version = 19 } ) @@ -837,6 +909,9 @@ test.register_message_test( PowerConfiguration.attributes.BatteryAlarmState:read(mock_device) } }, + }, + { + min_api_version = 19 } ) @@ -854,6 +929,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode("cool")) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua index 083d522bb3..ba5696eaef 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua @@ -45,6 +45,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.open()) } + }, + { + min_api_version = 19 } ) @@ -63,6 +66,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.closed()) } + }, + { + min_api_version = 19 } ) @@ -79,6 +85,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(5)) } + }, + { + min_api_version = 19 } ) @@ -95,6 +104,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(50)) } + }, + { + min_api_version = 19 } ) @@ -112,6 +124,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.unknown()) } + }, + { + min_api_version = 19 } ) @@ -129,6 +144,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) } + }, + { + min_api_version = 19 } ) @@ -146,6 +164,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.battery()) } + }, + { + min_api_version = 19 } ) @@ -163,6 +184,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.dc()) } + }, + { + min_api_version = 19 } ) @@ -179,6 +203,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.server.commands.On(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -195,6 +222,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.server.commands.Off(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -240,7 +270,10 @@ test.register_coroutine_test( IASZone.attributes.ZoneStatus:configure_reporting(mock_device, 0, 3600, 1) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -280,7 +313,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -318,7 +352,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua index d30c60ddc6..cbecbcd4f6 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua @@ -47,7 +47,10 @@ test.register_coroutine_test( mock_device.id, Basic.attributes.PowerSource:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -92,7 +95,10 @@ test.register_coroutine_test( zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Basic.ID) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -112,7 +118,10 @@ test.register_coroutine_test( mock_device.id, Basic.attributes.PowerSource:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -120,7 +129,10 @@ test.register_coroutine_test( function() test.socket.zigbee:__queue_receive({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device, 55) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(20)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -128,7 +140,10 @@ test.register_coroutine_test( function() test.socket.zigbee:__queue_receive({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device, 0) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(0)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -136,7 +151,10 @@ test.register_coroutine_test( function() test.socket.zigbee:__queue_receive({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device, 65) }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(100)) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_zigbee_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_zigbee_valve.lua index 5b4756f207..2df6ff5c8c 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_zigbee_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_zigbee_valve.lua @@ -45,6 +45,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.open()) } + }, + { + min_api_version = 19 } ) @@ -63,6 +66,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.closed()) } + }, + { + min_api_version = 19 } ) @@ -79,6 +85,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -96,6 +105,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.unknown()) } + }, + { + min_api_version = 19 } ) @@ -113,6 +125,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) } + }, + { + min_api_version = 19 } ) @@ -130,6 +145,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.battery()) } + }, + { + min_api_version = 19 } ) @@ -147,6 +165,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.dc()) } + }, + { + min_api_version = 19 } ) @@ -163,6 +184,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.server.commands.On(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -179,6 +203,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.server.commands.Off(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -225,7 +252,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -265,7 +295,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -303,7 +334,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-vent/src/test/test_zigbee_vent.lua b/drivers/SmartThings/zigbee-vent/src/test/test_zigbee_vent.lua index 524a16d580..9073338a81 100644 --- a/drivers/SmartThings/zigbee-vent/src/test/test_zigbee_vent.lua +++ b/drivers/SmartThings/zigbee-vent/src/test/test_zigbee_vent.lua @@ -59,6 +59,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -76,6 +79,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -93,6 +99,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -108,7 +117,10 @@ test.register_coroutine_test( test.mock_time.advance_time(1) test.socket.zigbee:__expect_send({mock_device.id, Level.attributes.CurrentLevel:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -124,6 +136,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C"})) } + }, + { + min_api_version = 19 } ) @@ -145,6 +160,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 20.00, maximum = 30.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -222,7 +240,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -278,7 +297,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) @@ -296,7 +318,10 @@ test.register_coroutine_test( test.wait_for_events() test.socket.capability:__queue_receive({mock_device.id, { capability = "switch", component = "main", command = "on", args = {}}}) test.socket.zigbee:__expect_send({mock_device.id, Level.commands.MoveToLevelWithOnOff(mock_device, math.floor(83 / 100 * 254), 0xFFFF)}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -306,7 +331,10 @@ test.register_coroutine_test( mock_device, 50 )}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(50))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -315,7 +343,10 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({mock_device.id, zigbee_test_utils.build_attribute_report(mock_device, clusters.PressureMeasurement.ID, {{ KEEN_PRESSURE_ATTRIBUTE, data_types.Uint16.ID, 10000}}, KEEN_MFG_CODE)}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.atmosphericPressureMeasurement.atmosphericPressure({value = 1, unit = "kPa"}))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 ea80228581..2776ca8f38 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 @@ -51,7 +51,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE , data_types.Uint8, 1) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -75,6 +78,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -99,6 +105,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -115,6 +124,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) 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 b42f3484ae..5cc8a2b283 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 @@ -46,7 +46,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -105,7 +108,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -121,6 +127,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -137,6 +146,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -153,6 +165,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -169,6 +184,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -193,6 +211,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -217,6 +238,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -246,6 +270,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) 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 56c264a505..5392a3d1b3 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 @@ -46,7 +46,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -111,7 +114,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -127,6 +133,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -143,6 +152,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -159,6 +171,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -175,6 +190,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -199,6 +217,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -223,6 +244,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -252,6 +276,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) 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 082c9fb97c..0e2b9d9161 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 @@ -40,7 +40,10 @@ test.register_coroutine_test( local alert_command = ApplianceEventsAlerts.client.commands.AlertsNotification.build_test_rx(mock_device, 0x01, {0x001181}) test.socket.zigbee:__queue_receive({ mock_device.id, alert_command }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.waterSensor.water.wet())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -49,7 +52,10 @@ test.register_coroutine_test( local alert_command = ApplianceEventsAlerts.client.commands.AlertsNotification.build_test_rx(mock_device, 0x01, {0x000081}) test.socket.zigbee:__queue_receive({ mock_device.id, alert_command }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.waterSensor.water.dry())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -58,7 +64,10 @@ test.register_coroutine_test( local alert_command = ApplianceEventsAlerts.client.commands.AlertsNotification.build_test_rx(mock_device, 0x01, {0x000581}) test.socket.zigbee:__queue_receive({ mock_device.id, alert_command }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.waterSensor.water.dry())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -67,7 +76,10 @@ test.register_coroutine_test( local alert_command = ApplianceEventsAlerts.client.commands.AlertsNotification.build_test_rx(mock_device, 0x01, {0x001081}) test.socket.zigbee:__queue_receive({ mock_device.id, alert_command }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.waterSensor.water.dry())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -76,7 +88,10 @@ test.register_coroutine_test( local alert_command = ApplianceEventsAlerts.client.commands.AlertsNotification.build_test_rx(mock_device, 0x01, {0x001281}) test.socket.zigbee:__queue_receive({ mock_device.id, alert_command }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.waterSensor.water.dry())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -86,7 +101,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -149,7 +167,10 @@ test.register_coroutine_test( IASZone.attributes.ZoneStatus:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 3eb7cb201a..ff1c5219dc 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 @@ -46,7 +46,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -105,7 +108,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -121,6 +127,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -137,6 +146,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(50)) } + }, + { + min_api_version = 19 } ) @@ -161,6 +173,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -185,6 +200,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -214,6 +232,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) 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 348ab81f0e..077cc41d1a 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 @@ -43,7 +43,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -91,7 +94,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 19e1c7053e..1b630ba3e9 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 @@ -46,6 +46,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -62,6 +65,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -78,6 +84,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -94,6 +103,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -110,6 +122,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -134,6 +149,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -150,6 +168,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -290,7 +311,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -351,7 +375,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 2b9ee2d3c1..26f12bea23 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 @@ -46,7 +46,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -105,7 +108,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -135,7 +141,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -159,6 +168,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -183,6 +195,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -212,6 +227,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) 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 63be06f0a8..2132397068 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 @@ -41,7 +41,10 @@ test.register_coroutine_test( function() test.socket.device_lifecycle:__queue_receive({mock_device.id, "added"}) test.socket.zigbee:__expect_send({mock_device.id, Basic.attributes.ApplicationVersion:read(mock_device)}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -51,7 +54,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -73,7 +79,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(55)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -95,7 +104,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(55)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -117,7 +129,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(100)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -139,7 +154,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(100)) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 3221e4fc11..906e98a9ce 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 @@ -44,6 +44,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -68,6 +71,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -92,6 +98,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -116,6 +125,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "waterSensor", capability_attr_id = "water" } } }, + }, + { + min_api_version = 19 } ) @@ -140,6 +152,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -167,6 +182,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 20.00, maximum = 30.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -183,6 +201,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(28)) } + }, + { + min_api_version = 19 } ) @@ -323,7 +344,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -384,7 +408,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 9188bbc996..06bdf8f342 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 @@ -52,6 +52,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -73,6 +76,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = -25.0, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -94,6 +100,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 20.00, maximum = 30.00 }, unit = "C" })) } + }, + { + min_api_version = 19 } ) @@ -215,7 +224,10 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -276,7 +288,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua b/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua index 1ad780782c..5ec003564e 100644 --- a/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua +++ b/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua @@ -1,3 +1,6 @@ +-- Copyright 2026 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" @@ -43,7 +46,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(10))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.mode.mode("0"))) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -59,6 +65,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(50)) } + }, + { + min_api_version = 19 } ) @@ -75,6 +84,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -91,6 +103,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -107,6 +122,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.server.commands.On(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -123,6 +141,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, OnOff.server.commands.Off(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -139,6 +160,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.detected()) } + }, + { + min_api_version = 19 } ) @@ -155,6 +179,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) } + }, + { + min_api_version = 19 } ) @@ -169,7 +196,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, THIRDREALITY_WATERING_CLUSTER, attr_report_data, 0x1407) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(10))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -183,7 +213,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, THIRDREALITY_WATERING_CLUSTER, attr_report_data, 0x1407) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(30))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -197,7 +230,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, THIRDREALITY_WATERING_CLUSTER, attr_report_data, 0x1407) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.mode.mode("4"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -206,7 +242,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "fanSpeed", component = "main", command = "setFanSpeed", args = { 20 } } }) test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, THIRDREALITY_WATERING_CLUSTER, WATERING_TIME_ATTR, 0x1407, data_types.Uint16, 20) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -215,7 +254,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({ mock_device.id, { capability = "mode", component = "main", command = "setMode", args = { "2" } } }) test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, THIRDREALITY_WATERING_CLUSTER, WATERING_INTERVAL_ATTR, 0x1407, data_types.Uint8, 2) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -231,6 +273,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.detected()) } + }, + { + min_api_version = 19 } ) @@ -247,6 +292,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) } + }, + { + min_api_version = 19 } ) @@ -261,7 +309,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, THIRDREALITY_WATERING_CLUSTER, attr_report_data, 0x1407) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(0))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua index 8c87907886..9c3bab7c56 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua @@ -54,7 +54,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -73,7 +76,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -92,7 +98,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -122,7 +131,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -172,7 +184,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -196,7 +211,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 100 - 30) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -212,7 +230,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 50) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -250,7 +271,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -286,7 +310,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -322,7 +349,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua index 725fda3f62..b7f0cf9164 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua @@ -84,7 +84,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(99)) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -119,7 +122,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -138,7 +144,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 50) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -156,7 +165,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 0) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -192,7 +204,10 @@ test.register_coroutine_test( mock_device.id, clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -225,7 +240,10 @@ test.register_coroutine_test( test.mock_time.advance_time(30) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -273,7 +291,10 @@ test.register_coroutine_test( clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -319,7 +340,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -356,7 +380,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -389,7 +416,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50)) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -424,7 +454,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 index 4c3028fd46..a7876e7223 100755 --- 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 @@ -43,7 +43,10 @@ test.register_coroutine_test( 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 + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -64,6 +67,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, clusters.WindowCovering.server.commands.UpOrOpen(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -88,6 +94,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.DownOrClose(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -107,6 +116,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.Stop(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -122,7 +134,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -137,7 +152,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -152,7 +170,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -167,7 +188,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -182,7 +206,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open())) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua index 16d7ebf366..8bbe64ddc5 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua @@ -53,7 +53,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -103,7 +106,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -124,6 +130,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, clusters.WindowCovering.server.commands.UpOrOpen(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -148,6 +157,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.DownOrClose(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -167,6 +179,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.Stop(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -192,6 +207,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 33) } } + }, + { + min_api_version = 19 } ) @@ -252,6 +270,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 20) } }, + }, + { + min_api_version = 19 } ) @@ -275,7 +296,10 @@ test.register_coroutine_test( mock_device.id, clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -307,7 +331,10 @@ test.register_coroutine_test( clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.lua index 69da00efb8..a971a05967 100755 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.lua @@ -49,7 +49,10 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({mock_device.id, read_0x0000_messge}) test.socket.zigbee:__expect_send({mock_device.id, read_0x0001_messge}) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -65,7 +68,10 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({mock_device.id, read_0x0000_messge}) test.socket.zigbee:__expect_send({mock_device.id, read_0x0001_messge}) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -86,6 +92,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, clusters.WindowCovering.server.commands.UpOrOpen(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -110,6 +119,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.DownOrClose(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -129,6 +141,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.Stop(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -143,7 +158,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE, data_types.Uint8, 0) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -157,7 +175,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE, data_types.Uint8, 1) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -171,7 +192,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE, data_types.Uint8, 2) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -185,7 +209,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE, data_types.Uint8, 3) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -200,7 +227,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.mode.mode("Delete upper limit"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -215,7 +245,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.mode.mode("Set the upper limit"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -230,7 +263,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.mode.mode("Delete lower limit"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -245,7 +281,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.mode.mode("Set the lower limit"))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -260,7 +299,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("hardwareFault", capabilities.hardwareFault.hardwareFault.clear())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -275,7 +317,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("hardwareFault", capabilities.hardwareFault.hardwareFault.detected())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -293,7 +338,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -311,7 +359,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara.lua index 050d0b34f0..27f00003dc 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara.lua @@ -141,7 +141,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, Basic.ID, PREF_ATTRIBUTE_ID, MFG_CODE, data_types.CharString, PREF_SOFT_TOUCH_ON) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -173,7 +176,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_read(mock_device, Basic.ID, { PREF_ATTRIBUTE_ID }, MFG_CODE) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -192,7 +198,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -211,7 +220,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -230,7 +242,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -246,7 +261,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.UpOrOpen(mock_device, 'open') }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -262,7 +280,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.DownOrClose(mock_device, 'close') }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -278,7 +299,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.Stop(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -322,7 +346,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", deviceInitialization.initializedState.initialized())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -351,7 +378,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, Basic.ID, PREF_ATTRIBUTE_ID, MFG_CODE, data_types.CharString, PREF_SOFT_TOUCH_OFF) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -372,7 +402,10 @@ test.register_coroutine_test( mock_device.id, AnalogOutput.attributes.PresentValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -399,7 +432,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_version_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -426,7 +462,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_version_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -453,7 +492,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_version_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -470,7 +512,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 50) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -497,7 +542,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 0) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -526,7 +574,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 100) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -556,7 +607,10 @@ test.register_coroutine_test( cluster_base.read_manufacturer_specific_attribute(mock_device, Basic.ID, PREF_ATTRIBUTE_ID, MFG_CODE) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -575,7 +629,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -594,7 +651,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) ) - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_curtain_driver_e1.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_curtain_driver_e1.lua index ea389680f2..07d17c72a7 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_curtain_driver_e1.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_curtain_driver_e1.lua @@ -109,7 +109,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(100)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -153,7 +156,10 @@ test.register_coroutine_test( PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -167,7 +173,10 @@ test.register_coroutine_test( custom_write_attribute(mock_device , WindowCovering.ID, WindowCovering.attributes.Mode.ID, data_types.Bitmap8, 0x01, nil) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -179,7 +188,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_MANUAL_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, false) }) - end + end, + { + min_api_version = 19 + } ) @@ -207,7 +219,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.UpOrOpen(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -233,7 +248,10 @@ test.register_coroutine_test( -- mock_device.id, -- WindowCovering.server.commands.DownOrClose(mock_device) -- }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -260,7 +278,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.Stop(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -275,7 +296,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_LOCKING_SETTING_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -290,7 +314,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_LOCKING_SETTING_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x00) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -319,7 +346,10 @@ test.register_coroutine_test( mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) @@ -336,7 +366,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", chargingState.chargingState.charging()) ) - end + end, + { + min_api_version = 19 + } ) @@ -353,6 +386,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -369,7 +405,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", initializedStateWithGuide.initializedStateWithGuide.initialized()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -385,7 +424,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -401,7 +443,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", hookLockState.hookLockState.locking()) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_roller_shade_rotate.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_roller_shade_rotate.lua index bd9d5684e6..bc99731313 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_roller_shade_rotate.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_roller_shade_rotate.lua @@ -114,7 +114,10 @@ test.register_coroutine_test( cluster_base.write_manufacturer_specific_attribute(mock_device, Basic.ID, PREF_ATTRIBUTE_ID, MFG_CODE, data_types.CharString, PREF_REVERSE_OFF) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -146,7 +149,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_read(mock_device, Basic.ID, { PREF_ATTRIBUTE_ID }, MFG_CODE) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -165,7 +171,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -184,7 +193,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -203,7 +215,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -229,7 +244,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 100) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -255,7 +273,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 0) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -271,7 +292,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.Stop(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -287,7 +311,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", shadeRotateState.rotateState.idle({state_change = true, visibility = { displayed = false }})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -304,7 +331,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", shadeRotateState.rotateState.idle({state_change = true, visibility = { displayed = false }})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -339,7 +369,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", initializedStateWithGuide.initializedStateWithGuide.initialized())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -360,7 +393,10 @@ test.register_coroutine_test( mock_device.id, AnalogOutput.attributes.PresentValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -389,7 +425,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 50) }) - end + end, + { + min_api_version = 19 + } ) @@ -405,7 +444,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", initializedStateWithGuide.initializedStateWithGuide.notInitialized())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -422,7 +464,10 @@ test.register_coroutine_test( mock_device.id, AnalogOutput.attributes.PresentValue:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -460,7 +505,10 @@ test.register_coroutine_test( message.body.zcl_header.frame_ctrl = FrameCtrl(0x10) test.socket.zigbee:__expect_send({ mock_device.id, message }) - end + end, + { + min_api_version = 19 + } ) 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 e8faf2a33d..02efc68fe6 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 @@ -80,7 +80,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -118,7 +121,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -168,7 +174,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -193,7 +202,10 @@ test.register_coroutine_test( Level.server.commands.MoveToLevelWithOnOff(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -222,7 +234,10 @@ test.register_coroutine_test( WindowCovering.server.commands.DownOrClose(mock_device) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -247,7 +262,10 @@ test.register_coroutine_test( Level.server.commands.MoveToLevelWithOnOff(mock_device, 0xFE) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -276,7 +294,10 @@ test.register_coroutine_test( WindowCovering.server.commands.UpOrOpen(mock_device, 0x64) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -313,7 +334,10 @@ test.register_coroutine_test( mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -352,7 +376,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.Stop(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -377,7 +404,10 @@ test.register_coroutine_test( Level.server.commands.MoveToLevelWithOnOff(mock_device, 84) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -409,7 +439,10 @@ test.register_coroutine_test( WindowCovering.server.commands.GoToLiftPercentage(mock_device, 100 - 33) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -434,7 +467,10 @@ test.register_coroutine_test( Level.server.commands.MoveToLevelWithOnOff(mock_device, 127) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -468,7 +504,10 @@ test.register_coroutine_test( WindowCovering.server.commands.GoToLiftPercentage(mock_device, 100 - 50) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -500,7 +539,10 @@ test.register_coroutine_test( WindowCovering.server.commands.GoToLiftPercentage(mock_device, 100 - 50) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -542,7 +584,10 @@ test.register_coroutine_test( mock_device.id, Basic.attributes.SWBuildID:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -577,7 +622,10 @@ test.register_coroutine_test( mock_device.id, Basic.attributes.SWBuildID:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -629,7 +677,10 @@ test.register_coroutine_test( }) 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 + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 7cfb443256..42b98aa088 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 @@ -66,7 +66,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -116,7 +119,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -137,6 +143,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, clusters.WindowCovering.server.commands.UpOrOpen(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -161,6 +170,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.DownOrClose(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -180,6 +192,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.Stop(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -205,6 +220,9 @@ test.register_message_test( Level.server.commands.MoveToLevelWithOnOff(mock_device,math.floor(33/100 * 254)) } } + }, + { + min_api_version = 19 } ) @@ -224,7 +242,10 @@ test.register_coroutine_test( mock_device.id, Level.server.commands.MoveToLevelWithOnOff(mock_device,math.floor(50/100 * 254)) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -242,7 +263,10 @@ test.register_coroutine_test( mock_device.id, Level.server.commands.MoveToLevelWithOnOff(mock_device,math.floor(50/100 * 254)) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -261,7 +285,10 @@ test.register_coroutine_test( mock_device.id, Level.server.commands.MoveToLevelWithOnOff(mock_device,math.floor(50/100 * 254)) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -288,7 +315,10 @@ test.register_coroutine_test( mock_device.id, Level.server.commands.MoveToLevelWithOnOff(mock_device,math.floor(1/100 * 254)) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -315,7 +345,10 @@ test.register_coroutine_test( mock_device.id, Level.server.commands.MoveToLevelWithOnOff(mock_device,math.floor(0/100 * 254)) }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -340,6 +373,9 @@ test.register_message_test( Level.server.commands.MoveToLevelWithOnOff(mock_device, math.floor(50 / 100 * 254)) } } + }, + { + min_api_version = 19 } ) @@ -363,7 +399,10 @@ test.register_coroutine_test( mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -389,7 +428,10 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 db50f28d32..5cc0a8cbce 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 @@ -91,7 +91,10 @@ test.register_coroutine_test( mock_device.id, build_tx_message(mock_device,"\x02\x02\x00\x04\x00\x00\x00\x32") }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -133,7 +136,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -176,7 +182,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -231,7 +240,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -274,7 +286,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -326,7 +341,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(30))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -358,7 +376,10 @@ test.register_coroutine_test( mock_device.id, build_tx_message(mock_device,"\x05\x04\x00\x01\x01") }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_rooms.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_rooms.lua index 303397191d..04e5a0b43a 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_rooms.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_rooms.lua @@ -56,7 +56,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -75,7 +78,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -94,7 +100,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(25)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -113,7 +122,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -132,7 +144,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -152,7 +167,10 @@ test.register_coroutine_test( WindowCovering.server.commands.GoToLiftPercentage(mock_device,100 - 33) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -169,7 +187,10 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -186,7 +207,10 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -203,7 +227,10 @@ test.register_coroutine_test( WindowCovering.server.commands.Stop(mock_device) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -222,7 +249,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -264,7 +294,11 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, INVERT_CLUSTER, INVERT_CLUSTER_ATTRIBUTE, MFG_CODE, data_types.Boolean, updates.preferences.invert)}) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100))) - end + end, + { + min_api_version = 19 + } + ) test.register_coroutine_test( @@ -299,7 +333,10 @@ test.register_coroutine_test( mock_device.id, zigbee_test_utils.build_attribute_read(mock_device, INVERT_CLUSTER, {INVERT_CLUSTER_ATTRIBUTE}, 0x0000) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -345,7 +382,10 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_read(mock_device, INVERT_CLUSTER, {INVERT_CLUSTER_ATTRIBUTE}, 0x0000) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua index c0a004ff6e..4abafaea50 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua @@ -62,7 +62,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -81,7 +84,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -107,7 +113,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(25)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -124,7 +133,10 @@ test.register_coroutine_test( WindowCovering.server.commands.UpOrOpen(mock_device) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -141,7 +153,10 @@ test.register_coroutine_test( WindowCovering.server.commands.DownOrClose(mock_device) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -158,7 +173,10 @@ test.register_coroutine_test( WindowCovering.server.commands.Stop(mock_device) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -176,7 +194,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc_out)) ) test.wait_for_events() end - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -196,7 +217,10 @@ test.register_coroutine_test( mock_device.id, Basic.attributes.PowerSource:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -245,7 +269,10 @@ test.register_coroutine_test( mock_device.id, Basic.attributes.PowerSource:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -274,7 +301,10 @@ test.register_coroutine_test( mock_device.id, Basic.attributes.PowerSource:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -314,7 +344,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(45)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -354,7 +387,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(85)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -394,7 +430,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -416,7 +455,10 @@ test.register_coroutine_test( Basic.attributes.PowerSource:build_test_attr_report(mock_device, 0)}) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.powerSource.powerSource.unknown())) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua index fa764a5db0..47230e9b6b 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua @@ -66,7 +66,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -116,7 +119,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -153,7 +159,10 @@ test.register_coroutine_test( }) test.mock_time.advance_time(3) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -232,7 +241,10 @@ test.register_coroutine_test( } ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -253,6 +265,9 @@ test.register_message_test( direction = "send", message = { mock_device.id, WindowCovering.server.commands.UpOrOpen(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -277,6 +292,9 @@ test.register_message_test( WindowCovering.server.commands.DownOrClose(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -296,6 +314,9 @@ test.register_message_test( WindowCovering.server.commands.Stop(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -321,6 +342,9 @@ test.register_message_test( WindowCovering.server.commands.GoToLiftPercentage(mock_device, 100 - 33) } } + }, + { + min_api_version = 19 } ) @@ -346,6 +370,9 @@ test.register_message_test( WindowCovering.server.commands.GoToLiftPercentage(mock_device, 50) } } + }, + { + min_api_version = 19 } ) @@ -371,7 +398,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 99) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -395,7 +425,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 0) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -413,7 +446,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 50) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -437,7 +473,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 100) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -459,7 +498,10 @@ test.register_coroutine_test( mock_device.id, WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -492,7 +534,10 @@ test.register_coroutine_test( WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -514,7 +559,10 @@ test.register_coroutine_test( } } ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -541,6 +589,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) } + }, + { + min_api_version = 19 } ) @@ -568,6 +619,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) } + }, + { + min_api_version = 19 } ) @@ -606,7 +660,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua index 37841524c4..f002cf5d84 100755 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua @@ -61,7 +61,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -105,7 +108,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -139,6 +145,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 0) } }, + }, + { + min_api_version = 19 } ) @@ -168,6 +177,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) }, + }, + { + min_api_version = 19 } ) @@ -187,6 +199,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.Stop(mock_device) } } + }, + { + min_api_version = 19 } ) @@ -222,6 +237,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 67) } }, + }, + { + min_api_version = 19 } ) @@ -257,6 +275,9 @@ test.register_message_test( clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 50) } } + }, + { + min_api_version = 19 } ) @@ -280,7 +301,10 @@ test.register_coroutine_test( mock_device.id, clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -312,7 +336,10 @@ test.register_coroutine_test( clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -339,6 +366,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) } + }, + { + min_api_version = 19 } ) @@ -366,6 +396,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) } + }, + { + min_api_version = 19 } ) @@ -399,7 +432,10 @@ test.register_coroutine_test( clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 70) }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -430,7 +466,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 7ae91bc853..e569cb8d6e 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 @@ -60,7 +60,11 @@ do direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.level({value = level})) } + }, + { + min_api_version = 19 } + ) end @@ -77,6 +81,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(2700)) } + }, + { + min_api_version = 19 } ) @@ -98,6 +105,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.level({value = 100})) } + }, + { + min_api_version = 19 } ) @@ -119,6 +129,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.level({value = 0})) } + }, + { + min_api_version = 19 } ) @@ -142,7 +155,11 @@ do direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.level({value = level})) } + }, + { + min_api_version = 19 } + ) end @@ -164,6 +181,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.level({value = 100})) } + }, + { + min_api_version = 19 } ) @@ -185,6 +205,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.level({value = 0})) } + }, + { + min_api_version = 19 } ) @@ -207,6 +230,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.level({value = 0})) } + }, + { + min_api_version = 19 } ) @@ -229,6 +255,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.level({value = 100})) } + }, + { + min_api_version = 19 } ) @@ -250,6 +279,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.level({value = 0})) } + }, + { + min_api_version = 19 } ) @@ -279,7 +311,11 @@ do direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.level({value = level})) } + }, + { + min_api_version = 19 } + ) end @@ -309,6 +345,9 @@ test.register_message_test( Configuration:Get({parameter_number = 0x52}) ) } + }, + { + min_api_version = 19 } ) @@ -338,6 +377,9 @@ test.register_message_test( Configuration:Get({parameter_number = 0x51}) ) } + }, + { + min_api_version = 19 } ) @@ -364,7 +406,11 @@ do direction = "send", message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature({value = temp})) } + }, + { + min_api_version = 19 } + ) end @@ -390,7 +436,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -414,7 +463,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -439,7 +491,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -513,7 +568,10 @@ test.register_coroutine_test( SwitchColor:Get({ color_component_id=SwitchColor.color_component_id.COLD_WHITE }) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 8757c4e11a..8b1633440d 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 @@ -104,7 +104,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -156,7 +157,10 @@ test.register_coroutine_test( Configuration:Get({ parameter_number=parameter_number }) ) ) - end + end, + { + min_api_version = 19 + } ) do @@ -182,7 +186,11 @@ do direction = "send", message = mock_aeotec_bulb:generate_test_message("main", capabilities.colorTemperature.colorTemperature(temp)) } + }, + { + min_api_version = 19 } + ) end 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 1610440aea..6d3f4336ec 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 @@ -98,7 +98,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -168,7 +169,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -198,7 +200,10 @@ test.register_coroutine_test( SwitchColor:Get({ color_component_id=SwitchColor.color_component_id.WARM_WHITE }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -227,7 +232,10 @@ test.register_coroutine_test( SwitchColor:Get({ color_component_id=SwitchColor.color_component_id.WARM_WHITE }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -257,6 +265,7 @@ test.register_message_test( } }, { + min_api_version = 19 } ) @@ -288,6 +297,7 @@ test.register_coroutine_test( ) end, { + min_api_version = 19 } ) @@ -309,6 +319,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_rgbw_controller:generate_test_message("main", capabilities.powerMeter.power({ value = 27, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -334,7 +347,11 @@ do direction = "send", message = mock_fibaro_rgbw_controller:generate_test_message("main", capabilities.switchLevel.level(level)) } + }, + { + min_api_version = 19 } + ) end @@ -379,7 +396,11 @@ do SwitchColor:Get({ color_component_id=SwitchColor.color_component_id.RED }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -455,7 +476,11 @@ do direction = "send", message = mock_fibaro_rgbw_controller:generate_test_message("rgb", capabilities.colorControl.saturation(sat)) } + }, + { + min_api_version = 19 } + ) end @@ -482,7 +507,11 @@ do direction = "send", message = mock_fibaro_rgbw_controller:generate_test_message("white", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } + ) end @@ -509,7 +538,11 @@ do direction = "send", message = mock_fibaro_rgbw_controller:generate_test_message("white", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } + ) end 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 9d5e199927..a5061dcfe6 100644 --- a/drivers/SmartThings/zwave-bulb/src/test/test_zwave_bulb.lua +++ b/drivers/SmartThings/zwave-bulb/src/test/test_zwave_bulb.lua @@ -99,7 +99,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -130,7 +131,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -157,6 +161,7 @@ test.register_message_test( } }, { + min_api_version = 19 } ) @@ -190,6 +195,7 @@ test.register_coroutine_test( ) end, { + min_api_version = 19 } ) @@ -222,6 +228,7 @@ test.register_coroutine_test( ) end, { + min_api_version = 19 } ) @@ -254,7 +261,11 @@ do direction = "send", message = mock_zwave_bulb:generate_test_message("main", capabilities.switchLevel.level(level)) } + }, + { + min_api_version = 19 } + ) end @@ -302,7 +313,11 @@ do SwitchColor:Get({ color_component_id=SwitchColor.color_component_id.RED }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -369,7 +384,11 @@ do direction = "send", message = mock_zwave_bulb:generate_test_message("main", capabilities.colorControl.saturation(sat)) } + }, + { + min_api_version = 19 } + ) end @@ -410,7 +429,10 @@ test.register_coroutine_test( SwitchColor:Get({ color_component_id=SwitchColor.color_component_id.WARM_WHITE }) ) ) - end + end, + { + min_api_version = 19 + } ) do @@ -455,6 +477,9 @@ do direction = "send", message = mock_zwave_bulb:generate_test_message("main", capabilities.colorTemperature.colorTemperature(temp)) } + }, + { + min_api_version = 19 } ) end diff --git a/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_minimote.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_minimote.lua index 3788947ef4..8f95ae83b2 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_minimote.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_minimote.lua @@ -67,6 +67,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_minimote:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -93,6 +96,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_minimote:generate_test_message("main", capabilities.button.button.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -120,6 +126,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_minimote:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -146,6 +155,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_minimote:generate_test_message("main", capabilities.button.button.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -172,6 +184,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_minimote:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -198,6 +213,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_minimote:generate_test_message("main", capabilities.button.button.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -224,6 +242,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_minimote:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -250,6 +271,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_minimote:generate_test_message("main", capabilities.button.button.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -307,7 +331,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number = 140, size = 4, configuration_value = 26017792}) --payload="��" )) mock_aeotec_minimote:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -345,7 +372,10 @@ test.register_coroutine_test( ) end end - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_nanomote_one.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_nanomote_one.lua index 5aad3b2547..d2866aaecd 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_nanomote_one.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_nanomote_one.lua @@ -66,7 +66,10 @@ test.register_coroutine_test( Battery:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-button/src/test/test_zwave_button.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_button.lua index 9cd4532bb1..bea1aae3e3 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_button.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_button.lua @@ -57,6 +57,9 @@ test.register_message_test( direction = "send", message = mock:generate_test_message("main", capabilities.button.button.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -73,6 +76,9 @@ test.register_message_test( direction = "send", message = mock:generate_test_message("main", capabilities.button.button.held({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -91,6 +97,9 @@ test.register_message_test( direction = "send", message = mock:generate_test_message("main", capabilities.button.button.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -109,7 +118,11 @@ test.register_message_test( direction = "send", message = mock:generate_test_message("main", capabilities.button.button.down_hold({ state_change = true })) } + }, + { + min_api_version = 19 } + ) test.register_coroutine_test( @@ -136,7 +149,11 @@ test.register_coroutine_test( Battery:Get({}) ) ) - end + end, + { + min_api_version = 19 + } + ) @@ -164,6 +181,9 @@ test.register_coroutine_test( Battery:Get({}) ) }, + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-button/src/test/test_zwave_fibaro_button.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_fibaro_button.lua index a71e136998..0ba65fc434 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_fibaro_button.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_fibaro_button.lua @@ -65,7 +65,10 @@ test.register_coroutine_test( Battery:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua index 3ddf585681..8a7613f8ef 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua @@ -116,6 +116,9 @@ test.register_message_test( direction = "send", message = mock_everspring:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -142,6 +145,9 @@ test.register_message_test( direction = "send", message = mock_everspring:generate_test_message("main", capabilities.button.button.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -165,7 +171,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_everspring:generate_test_message( "main", capabilities.button.button.double({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -191,6 +200,9 @@ test.register_message_test( direction = "send", message = mock_everspring:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -217,6 +229,9 @@ test.register_message_test( direction = "send", message = mock_everspring:generate_test_message("main", capabilities.button.button.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -240,7 +255,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_everspring:generate_test_message( "main", capabilities.button.button.double({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -266,6 +284,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_wallmote_quad:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -292,6 +313,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_wallmote_quad:generate_test_message("main", capabilities.button.button.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -318,6 +342,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_wallmote_quad:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -340,7 +367,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_aeotec_wallmote_quad:generate_test_message( "main", capabilities.button.button.held({state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -356,6 +386,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -383,6 +416,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_keyfob_button:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -409,6 +445,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_keyfob_button:generate_test_message("main", capabilities.button.button.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -435,6 +474,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_keyfob_button:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -461,6 +503,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_keyfob_button:generate_test_message("main", capabilities.button.button.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -483,7 +528,10 @@ test.register_coroutine_test( Association:Set({grouping_identifier = 1, node_ids = {}}) )) mock_aeotec_keyfob_button:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -568,7 +616,10 @@ test.register_coroutine_test( Battery:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) --configuration for fibaro keyfob @@ -606,7 +657,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number = 26, size = 1, configuration_value = 15}) )) mock_fibaro_keyfob_button:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -719,7 +773,10 @@ test.register_coroutine_test( Battery:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -804,7 +861,10 @@ test.register_coroutine_test( Battery:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -861,7 +921,10 @@ test.register_coroutine_test( Battery:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -887,6 +950,9 @@ test.register_message_test( direction = "send", message = mock_aeotec_wallmote_quad:generate_test_message("main", capabilities.button.button.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) 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 cb92a861fe..4c4241f6e6 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 @@ -52,6 +52,9 @@ test.register_message_test( direction = "send", message = mock_meter:generate_test_message("main", capabilities.powerMeter.power({ value = 27, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -71,6 +74,9 @@ test.register_message_test( direction = "send", message = mock_meter:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -100,7 +106,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -130,7 +137,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number = 103, size = 4, configuration_value = 0}) )) mock_meter:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) 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 29255434d1..c39d5b3e0e 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 @@ -52,6 +52,9 @@ test.register_message_test( direction = "send", message = mock_meter:generate_test_message("main", capabilities.powerMeter.power({ value = 27, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -71,6 +74,9 @@ test.register_message_test( direction = "send", message = mock_meter:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -100,7 +106,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -134,7 +141,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number = 13, size = 1, configuration_value = 0}) )) mock_meter:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 55f1b9e436..892bd9a10f 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 @@ -94,6 +94,9 @@ test.register_message_test( direction = "send", message = mock_meter:generate_test_message("endpointMeter3", capabilities.powerMeter.power({ value = 5, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -113,6 +116,9 @@ test.register_message_test( direction = "send", message = mock_meter:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) }, + }, + { + min_api_version = 19 } ) @@ -172,7 +178,10 @@ test.register_coroutine_test( { capability = "refresh", component = "main", command = "refresh", args = { } } }) - end + end, + { + min_api_version = 19 + } ) @@ -211,7 +220,10 @@ test.register_coroutine_test( ) )) mock_meter:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 47391c078f..648ef598c8 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 @@ -52,6 +52,9 @@ test.register_message_test( direction = "send", message = mock_meter:generate_test_message("main", capabilities.powerMeter.power({ value = 27, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -71,6 +74,9 @@ test.register_message_test( direction = "send", message = mock_meter:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -100,7 +106,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -118,7 +125,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number = 42, size = 2, configuration_value = 1800}) )) mock_meter:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 01c3227147..cc5ce67806 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 @@ -45,6 +45,9 @@ test.register_message_test( direction = "send", message = mock_meter:generate_test_message("main", capabilities.powerMeter.power({ value = 27, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -64,6 +67,9 @@ test.register_message_test( direction = "send", message = mock_meter:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -93,7 +99,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_3_speed.lua b/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_3_speed.lua index 249c0ebc14..ca46560f36 100644 --- a/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_3_speed.lua +++ b/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_3_speed.lua @@ -72,6 +72,9 @@ test.register_message_test( direction = "send", message = mock_fan:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -101,6 +104,9 @@ test.register_message_test( direction = "send", message = mock_fan:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -131,6 +137,9 @@ test.register_message_test( direction = "send", message = mock_fan:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -159,6 +168,9 @@ test.register_message_test( direction = "send", message = mock_fan:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -190,7 +202,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_4_speed.lua b/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_4_speed.lua index 18aab08c73..d41c45d6c5 100644 --- a/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_4_speed.lua +++ b/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_4_speed.lua @@ -71,6 +71,9 @@ test.register_message_test( direction = "send", message = mock_fan:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -99,6 +102,9 @@ test.register_message_test( direction = "send", message = mock_fan:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -129,6 +135,9 @@ test.register_message_test( direction = "send", message = mock_fan:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -157,6 +166,9 @@ test.register_message_test( direction = "send", message = mock_fan:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -188,7 +200,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_ecolink_garage_door_operator.lua b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_ecolink_garage_door_operator.lua index c37e99da13..d840118f70 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_ecolink_garage_door_operator.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_ecolink_garage_door_operator.lua @@ -67,6 +67,9 @@ test.register_message_test( direction = "send", message = mock_garage_door:generate_test_message("main", capabilities.doorControl.door.closed()) } + }, + { + min_api_version = 19 } ) @@ -83,6 +86,9 @@ test.register_message_test( direction = "send", message = mock_garage_door:generate_test_message("main", capabilities.doorControl.door.open()) } + }, + { + min_api_version = 19 } ) @@ -123,7 +129,8 @@ test.register_message_test( }, { test_init = test_init, - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -162,7 +169,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -183,6 +191,9 @@ test.register_message_test( direction = "send", message = mock_garage_door:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 12.2999999, unit = 'C'})) } + }, + { + min_api_version = 19 } ) @@ -203,6 +214,9 @@ test.register_message_test( direction = "send", message = mock_garage_door:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 45.6, unit = 'F'})) } + }, + { + min_api_version = 19 } ) @@ -228,7 +242,10 @@ test.register_coroutine_test( parameters = updated_params}) )) mock_garage_door:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -255,7 +272,10 @@ test.register_coroutine_test( BarrierOperator:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -282,7 +302,11 @@ test.register_coroutine_test( BarrierOperator:Get({}) ) ) - end + end, + { + min_api_version = 19 + } + ) test.register_coroutine_test( @@ -295,7 +319,11 @@ test.register_coroutine_test( ) test.wait_for_events() test.mock_time.advance_time(1) - end + end, + { + min_api_version = 19 + } + ) test.register_coroutine_test( @@ -308,7 +336,11 @@ test.register_coroutine_test( ) test.wait_for_events() test.mock_time.advance_time(1) - end + end, + { + min_api_version = 19 + } + ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_mimolite_garage_door.lua b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_mimolite_garage_door.lua index c10d3f8fa5..6e15e55895 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_mimolite_garage_door.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_mimolite_garage_door.lua @@ -67,6 +67,9 @@ test.register_message_test( direction = "send", message = mock_garage_door:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -88,6 +91,9 @@ test.register_message_test( direction = "send", message = mock_garage_door:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -109,6 +115,9 @@ test.register_message_test( direction = "send", message = mock_garage_door:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -130,6 +139,9 @@ test.register_message_test( direction = "send", message = mock_garage_door:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -151,6 +163,9 @@ test.register_message_test( direction = "send", message = mock_garage_door:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -172,6 +187,9 @@ test.register_message_test( direction = "send", message = mock_garage_door:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -203,6 +221,9 @@ test.register_message_test( direction = "send", message = mock_garage_door:generate_test_message("main", capabilities.doorControl.door.closing()) } + }, + { + min_api_version = 19 } ) @@ -234,6 +255,9 @@ test.register_message_test( direction = "send", message = mock_garage_door:generate_test_message("main", capabilities.doorControl.door.opening()) } + }, + { + min_api_version = 19 } ) @@ -261,7 +285,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -288,7 +315,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -305,7 +335,10 @@ test.register_coroutine_test( Association:Set({grouping_identifier = 3, node_ids = {}}) )) mock_garage_door:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -326,7 +359,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_zwave_garage_door_opener.lua b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_zwave_garage_door_opener.lua index 65c0256149..9e0cd7077a 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_zwave_garage_door_opener.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_zwave_garage_door_opener.lua @@ -58,6 +58,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -74,6 +77,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.doorControl.door.closing()) } + }, + { + min_api_version = 19 } ) @@ -95,6 +101,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -116,6 +125,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -132,6 +144,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.doorControl.door.unknown()) } + }, + { + min_api_version = 19 } ) @@ -150,7 +165,8 @@ test.register_message_test( } }, { - test_init = test_init + test_init = test_init, + min_api_version = 19 } ) @@ -170,7 +186,8 @@ test.register_message_test( }, { test_init = test_init, - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua index 0266391d85..b993365eef 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua @@ -44,7 +44,10 @@ test.register_coroutine_test( DoorLock:OperationReport({door_lock_mode = 0x00}) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.unlocked())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -54,7 +57,10 @@ test.register_coroutine_test( DoorLock:OperationReport({door_lock_mode = 0xFF}) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.locked())) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -86,6 +92,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({data={method="manual"}})) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua b/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua index 7667842e61..a079009f7f 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua @@ -58,7 +58,10 @@ test.register_coroutine_test( DoorLock:OperationReport({ door_lock_mode = DoorLock.door_lock_mode.DOOR_UNSECURED }) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.unlocked())) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -74,6 +77,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(50)) } + }, + { + min_api_version = 19 } ) @@ -90,6 +96,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(1)) } + }, + { + min_api_version = 19 } ) @@ -104,7 +113,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(4.2) test.socket.zwave:__expect_send(DoorLock:OperationGet({}):build_test_tx(mock_device.id)) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -122,7 +134,10 @@ test.register_coroutine_test( minute_local_time = time.min, second_local_time = time.sec }):build_test_tx(mock_device.id)) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua index 9dc1e38bcf..4e2fb75413 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua @@ -49,7 +49,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["0"] = "Master Code"}), { visibility = { displayed = false } })) ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -92,7 +95,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 set", { data = { codeName = "test"}, state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -106,7 +112,10 @@ test.register_coroutine_test( }) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("2 failed", { state_change = true }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -120,7 +129,10 @@ test.register_coroutine_test( }) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged(2 .. " failed", { state_change = true }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -162,7 +174,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["0"] = "Master Code"} ), { visibility = { displayed = false } }) )) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua index 189184f19e..1875e043d2 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua @@ -75,7 +75,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 set", { data = { codeName = "test"}, state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -92,7 +95,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -108,7 +114,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.codeLength(6)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -137,7 +146,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.codeLength(4)) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -153,7 +165,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({}), {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) local expect_reload_all_codes_messages = function() @@ -187,7 +202,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -212,7 +230,10 @@ test.register_coroutine_test( ) ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -236,7 +257,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock.lua index b105707c7d..a7f920f9bb 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock.lua @@ -72,7 +72,10 @@ test.register_coroutine_test( ) ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -82,7 +85,10 @@ test.register_coroutine_test( DoorLock:OperationReport({door_lock_mode = DoorLock.door_lock_mode.DOOR_SECURED}) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.locked())) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -98,6 +104,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -119,6 +128,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({ data = { method = "manual" } })) } + }, + { + min_api_version = 19 } ) @@ -149,7 +161,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -172,6 +185,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -186,7 +202,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(4.2) test.socket.zwave:__expect_send(DoorLock:OperationGet({}):build_test_tx(mock_device.id)) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -202,6 +221,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.lockCodes.maxCodes(16, { visibility = { displayed = false } })) } + }, + { + min_api_version = 19 } ) @@ -210,7 +232,10 @@ test.register_coroutine_test( function() test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "reloadAllCodes", args = {} } }) expect_reload_all_codes_messages() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -226,6 +251,9 @@ test.register_message_test( direction = "send", message = UserCode:Get({user_identifier = 1}):build_test_tx(mock_device.id) } + }, + { + min_api_version = 19 } ) @@ -239,7 +267,10 @@ test.register_coroutine_test( test.mock_time.advance_time(4.2) test.socket.zwave:__expect_send(UserCode:Get( {user_identifier = 1}):build_test_tx(mock_device.id)) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -256,7 +287,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 set", { data = { codeName = "test"}, state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) local function init_code_slot(slot_number, name, device) @@ -278,7 +312,10 @@ test.register_coroutine_test( )) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 renamed", {state_change = true}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -302,7 +339,10 @@ test.register_coroutine_test( test.mock_time.advance_time(2) test.socket.zwave:__expect_send(UserCode:Get({user_identifier = 4}):build_test_tx(mock_device.id)) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -324,6 +364,9 @@ test.register_message_test( capabilities.lockCodes.codeChanged("0 set", { data = { codeName = "Master Code"}, state_change = true }) ) } + }, + { + min_api_version = 19 } ) @@ -352,7 +395,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -374,7 +418,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({} ), { visibility = { displayed = false } }) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -414,7 +461,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({} ), { visibility = { displayed = false } }) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -436,7 +486,10 @@ test.register_coroutine_test( capabilities.lock.lock.unlocked({ data = { method = "keypad", codeId = "1", codeName = "Superb Owl" } }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -458,7 +511,10 @@ test.register_coroutine_test( capabilities.lock.lock.unlocked({ data = { method = "keypad", codeId = "1", codeName = "Code 1" } }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -486,7 +542,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.scanCodes("Complete", { visibility = { displayed = false } }) )) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -718,6 +777,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -740,7 +802,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("1 set", { data = { codeName = "test"}, state_change = true })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -760,7 +825,10 @@ test.register_coroutine_test( ) ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_code_migration.lua b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_code_migration.lua index a6b0b5a2a3..7b78d3f8ef 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_code_migration.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_code_migration.lua @@ -87,7 +87,10 @@ test.register_coroutine_test( assert(mock_device.state_cache.main.lockCodes.lockCodes.value == json.encode(utils.deep_copy(lockCodes))) -- Validate migration complete flag mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -116,7 +119,10 @@ test.register_coroutine_test( assert(mock_device_no_data.state_cache.main.lockCodes.lockCodes.value == json.encode({})) -- Validate migration complete flag mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "migrationComplete", nil) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -152,7 +158,10 @@ test.register_coroutine_test( assert(mock_device.state_cache.main.lockCodes.lockCodes.value == json.encode(utils.deep_copy(lockCodes))) -- Validate migration complete flag mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -197,7 +206,10 @@ test.register_coroutine_test( assert(mock_device_no_data.state_cache.main.lockCodes.lockCodes.value == json.encode(utils.deep_copy(lockCodes))) -- Validate migration complete flag mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "migrationComplete", true) - end + end, + { + min_api_version = 19 + } ) @@ -230,7 +242,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(35) -- Nothing should happen - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-mouse-trap/src/test/test_zwave_mouse_trap.lua b/drivers/SmartThings/zwave-mouse-trap/src/test/test_zwave_mouse_trap.lua index 3e973a14f8..2665af2562 100644 --- a/drivers/SmartThings/zwave-mouse-trap/src/test/test_zwave_mouse_trap.lua +++ b/drivers/SmartThings/zwave-mouse-trap/src/test/test_zwave_mouse_trap.lua @@ -65,6 +65,9 @@ test.register_message_test( direction = "send", message = mock_mouse_trap:generate_test_message("main", capabilities.pestControl.pestControl.pestExterminated()) } + }, + { + min_api_version = 19 } ) @@ -84,6 +87,9 @@ test.register_message_test( direction = "send", message = mock_mouse_trap:generate_test_message("main", capabilities.pestControl.pestControl.idle()) } + }, + { + min_api_version = 19 } ) @@ -103,6 +109,9 @@ test.register_message_test( direction = "send", message = mock_mouse_trap:generate_test_message("main", capabilities.pestControl.pestControl.trapArmed()) } + }, + { + min_api_version = 19 } ) @@ -122,6 +131,9 @@ test.register_message_test( direction = "send", message = mock_mouse_trap:generate_test_message("main", capabilities.pestControl.pestControl.trapArmed()) } + }, + { + min_api_version = 19 } ) @@ -141,6 +153,9 @@ test.register_message_test( direction = "send", message = mock_mouse_trap:generate_test_message("main", capabilities.pestControl.pestControl.trapRearmRequired()) } + }, + { + min_api_version = 19 } ) @@ -160,6 +175,9 @@ test.register_message_test( direction = "send", message = mock_mouse_trap:generate_test_message("main", capabilities.pestControl.pestControl.pestDetected()) } + }, + { + min_api_version = 19 } ) @@ -179,6 +197,9 @@ test.register_message_test( direction = "send", message = mock_mouse_trap:generate_test_message("main", capabilities.pestControl.pestControl.pestDetected()) } + }, + { + min_api_version = 19 } ) @@ -198,6 +219,9 @@ test.register_message_test( direction = "send", message = mock_mouse_trap:generate_test_message("main", capabilities.pestControl.pestControl.pestExterminated()) } + }, + { + min_api_version = 19 } ) @@ -217,6 +241,9 @@ test.register_message_test( direction = "send", message = mock_mouse_trap:generate_test_message("main", capabilities.pestControl.pestControl.idle()) } + }, + { + min_api_version = 19 } ) @@ -233,6 +260,9 @@ test.register_message_test( direction = "send", message = mock_mouse_trap:generate_test_message("main", capabilities.battery.battery(85)) } + }, + { + min_api_version = 19 } ) @@ -249,6 +279,9 @@ test.register_message_test( direction = "send", message = mock_mouse_trap:generate_test_message("main", capabilities.battery.battery(1)) } + }, + { + min_api_version = 19 } ) @@ -300,7 +333,10 @@ test.register_coroutine_test( ) ) mock_mouse_trap:expect_metadata_update({provisioning_state = "PROVISIONED"}) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua index 59435aa68a..8920de60d4 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua @@ -91,7 +91,10 @@ test.register_coroutine_test( SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.RELATIVE_HUMIDITY}) )) mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -113,6 +116,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua index f858438d9a..b47c111ccb 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua @@ -116,7 +116,10 @@ test.register_coroutine_test( SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.ULTRAVIOLET}) )) mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -164,7 +167,10 @@ test.register_coroutine_test( )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Configuration value should be updated and device refreshed, when wakeup notification received", @@ -233,7 +239,10 @@ test.register_coroutine_test( Configuration:Get({parameter_number = 9}) )) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -252,6 +261,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.ultravioletIndex.ultravioletIndex({value = 10})) } + }, + { + min_api_version = 19 } ) @@ -284,6 +296,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.dc()) } + }, + { + min_api_version = 19 } ) @@ -316,6 +331,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) } + }, + { + min_api_version = 19 } ) @@ -335,6 +353,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -356,7 +377,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -389,7 +413,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -410,7 +437,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -432,6 +462,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua index 7fd57e42b2..8e3a7bacbc 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua @@ -118,7 +118,10 @@ test.register_coroutine_test( )) mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -164,7 +167,10 @@ test.register_coroutine_test( SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.ULTRAVIOLET}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Configuration value should be updated and device refreshed, when wakeup notification received", @@ -231,7 +237,10 @@ test.register_coroutine_test( mock_sensor, SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.ULTRAVIOLET}) )) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -250,6 +259,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.ultravioletIndex.ultravioletIndex({value = 10})) } + }, + { + min_api_version = 19 } ) @@ -282,6 +294,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.dc()) } + }, + { + min_api_version = 19 } ) @@ -314,6 +329,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua index 3b73ff17c3..0bf4463038 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua @@ -106,7 +106,10 @@ test.register_coroutine_test( SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.RELATIVE_HUMIDITY}) )) mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua index 8af011f51f..9f58a62200 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua @@ -86,7 +86,10 @@ test.register_coroutine_test( Association:Set({grouping_identifier = 4, node_ids = {}}) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -102,6 +105,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -118,6 +124,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -137,6 +146,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -156,6 +168,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -175,6 +190,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -194,6 +212,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -210,6 +231,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -229,6 +253,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -248,6 +275,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -267,6 +297,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -286,6 +319,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua index a5f44ff12e..3e892f19b7 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua @@ -60,6 +60,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -79,6 +82,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -98,6 +104,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -117,6 +126,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -136,6 +148,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -161,7 +176,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(10) test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua index a90655669d..0e3e914200 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua @@ -61,7 +61,10 @@ test.register_coroutine_test( Association:Set({grouping_identifier = 1, node_ids = {}}) )) mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -81,7 +84,10 @@ test.register_coroutine_test( mock_sensor, Battery:Get({}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -100,7 +106,10 @@ test.register_coroutine_test( WakeUp:IntervalGet({}) )) - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua index 45e0672c11..0ff3acfa69 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua @@ -59,7 +59,10 @@ test.register_coroutine_test( Battery:Get({}) )) mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -93,7 +96,10 @@ test.register_coroutine_test( mock_sensor, Battery:Get({}) )) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua index 1cb2735a31..d1cbf5acc0 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua @@ -66,6 +66,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -86,6 +89,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -110,7 +116,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(10) test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -129,6 +138,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -149,6 +161,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -196,7 +211,10 @@ test.register_coroutine_test( mock_sensor, Battery:Get({}) )) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua index 097016fc81..2aa22d6ba7 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua @@ -58,7 +58,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number = 8, size = 1, configuration_value = 5}) )) mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua index 04103db0db..16ec8edf5b 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua @@ -62,7 +62,10 @@ test.register_coroutine_test( Battery:Get({}) )) mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua index 17a627a736..9de9b2c345 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua @@ -75,7 +75,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua index 74b6d6196c..62cb1a8f38 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua @@ -60,6 +60,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -86,6 +89,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -121,7 +127,11 @@ do mock_device, SwitchColor:Get({ color_component_id=SwitchColor.color_component_id.RED }) )) - end + end, + { + min_api_version = 19 + } + ) end @@ -157,7 +167,11 @@ do mock_device, SwitchColor:Get({ color_component_id=SwitchColor.color_component_id.RED }) )) - end + end, + { + min_api_version = 19 + } + ) end @@ -287,7 +301,10 @@ test.register_coroutine_test( ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua index 248c22ab60..bbd566526a 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua @@ -82,7 +82,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -104,7 +105,10 @@ test.register_coroutine_test( SensorBinary:Get({}) )) mock_fibaro_door_window_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -120,6 +124,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -139,6 +146,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -158,6 +168,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -178,6 +191,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -197,6 +213,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua index 6904f318a9..0c96a97492 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua @@ -130,7 +130,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -217,7 +218,10 @@ test.register_coroutine_test( Association:Remove({grouping_identifier = 1, node_ids = {}}) )) mock_fibaro_door_window_sensor1:expect_metadata_update({provisioning_state = "PROVISIONED"}) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -233,6 +237,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor1:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -252,6 +259,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor1:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -271,6 +281,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor1:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -295,6 +308,9 @@ test.register_message_test( { device_uuid = mock_fibaro_door_window_sensor1.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -319,6 +335,9 @@ test.register_message_test( { device_uuid = mock_fibaro_door_window_sensor1.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -338,6 +357,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor1:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -356,6 +378,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor1:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -384,6 +409,9 @@ test.register_message_test( { device_uuid = mock_fibaro_door_window_sensor1.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -411,6 +439,9 @@ test.register_message_test( { device_uuid = mock_fibaro_door_window_sensor1.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua index bc78bd02ca..1da107f5e9 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua @@ -67,6 +67,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -86,6 +89,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -105,6 +111,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -124,6 +133,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -143,6 +155,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) } + }, + { + min_api_version = 19 } ) @@ -162,6 +177,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) } + }, + { + min_api_version = 19 } ) @@ -181,6 +199,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.freeze()) } + }, + { + min_api_version = 19 } ) @@ -295,7 +316,10 @@ test.register_coroutine_test( mock_fibaro_door_window_sensor, SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.TEMPERATURE}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -333,7 +357,10 @@ test.register_coroutine_test( mock_fibaro_door_window_sensor, SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.TEMPERATURE}) )) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -366,6 +393,9 @@ test.register_message_test( direction = "receive", message = {mock_fibaro_door_window_sensor.id, "added"} } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua index 2b27d66868..9b9a83406e 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua @@ -94,7 +94,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -120,7 +121,10 @@ test.register_coroutine_test( SensorBinary:Get({}) )) mock_fibaro_door_window_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -136,6 +140,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -155,6 +162,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -174,6 +184,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -194,6 +207,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -213,6 +229,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -241,6 +260,9 @@ test.register_message_test( { device_uuid = mock_fibaro_door_window_sensor.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -268,6 +290,9 @@ test.register_message_test( { device_uuid = mock_fibaro_door_window_sensor.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -542,7 +567,10 @@ test.register_coroutine_test( mock_fibaro_door_window_sensor.id, Configuration:Report({ parameter_number = 56, configuration_value = 40 }) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua index 2d93278b47..65a8621b75 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua @@ -62,6 +62,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -78,6 +81,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -96,6 +102,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -114,6 +123,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -141,6 +153,9 @@ test.register_message_test( { device_uuid = mock_sensor.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -160,6 +175,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -179,6 +197,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -203,7 +224,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(30) test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -227,7 +251,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(30) test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -251,7 +278,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(30) test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -275,7 +305,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(30) test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - end + end, + { + min_api_version = 19 + } ) @@ -303,7 +336,10 @@ test.register_coroutine_test( ) ) mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua index 45cde29b8f..2a6a6e8074 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua @@ -90,7 +90,10 @@ test.register_coroutine_test( mock_sensor, Configuration:Set({parameter_number = 77, size = 1, configuration_value = 2}) )) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua index 24a7f31eaf..6383359cd3 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua @@ -95,7 +95,10 @@ test.register_coroutine_test( SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.LUMINANCE, scale = SensorMultilevel.scale.luminance.LUX}) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -111,6 +114,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -135,6 +141,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -154,6 +163,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -173,6 +185,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active()) } + }, + { + min_api_version = 19 } ) @@ -192,6 +207,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive()) } + }, + { + min_api_version = 19 } ) @@ -211,6 +229,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active()) } + }, + { + min_api_version = 19 } ) @@ -231,6 +252,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 400, unit = "lux" })) } + }, + { + min_api_version = 19 } ) @@ -250,6 +274,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -269,6 +296,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -293,6 +323,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -321,6 +354,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -348,6 +384,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua index e514a2f2e5..0c8413e23a 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua @@ -77,7 +77,10 @@ test.register_coroutine_test( capabilities.threeAxis.threeAxis({value = {200, 200, 400}, unit = 'mG'}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -213,7 +216,10 @@ test.register_coroutine_test( mock_sensor, SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.ACCELERATION_Z_AXIS, scale = SensorMultilevel.scale.acceleration_z_axis.METERS_PER_SQUARE_SECOND}) )) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua b/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua index d4903fc30d..7744b12ccf 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua @@ -85,7 +85,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -105,6 +106,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.firmwareUpdate.currentVersion({ value = "1.05" })) } + }, + { + min_api_version = 19 } ) @@ -118,7 +122,11 @@ test.register_coroutine_test( test.socket.zwave:__expect_send( zw_test_utils.zwave_test_build_send_command(mock_device, Version:Get({})) ) - end + end, + { + min_api_version = 19 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua index e6fff57b0d..878d36dd04 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua @@ -71,6 +71,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.atmosphericPressureMeasurement.atmosphericPressure({ value = 101.3, unit = "kPa" })) } + }, + { + min_api_version = 19 } ) @@ -91,6 +94,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.atmosphericPressureMeasurement.atmosphericPressure({ value = 30.13 * KILO_PASCAL_PER_INCH_OF_MERCURY, unit = "kPa" })) } + }, + { + min_api_version = 19 } ) @@ -110,6 +116,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.bodyWeightMeasurement.bodyWeightMeasurement({ value = 60, unit = "kg" })) } + }, + { + min_api_version = 19 } ) @@ -130,6 +139,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.bodyWeightMeasurement.bodyWeightMeasurement({ value = 120, unit = "lbs" })) } + }, + { + min_api_version = 19 } ) @@ -150,6 +162,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 700, unit = "lux" })) } + }, + { + min_api_version = 19 } ) @@ -169,6 +184,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 70 })) } + }, + { + min_api_version = 19 } ) @@ -197,6 +215,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -225,6 +246,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -244,6 +268,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.voltageMeasurement.voltage({ value = 5, unit = "V" })) } + }, + { + min_api_version = 19 } ) @@ -264,6 +291,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.voltageMeasurement.voltage({ value = 0.005, unit = "V" })) } + }, + { + min_api_version = 19 } ) @@ -291,6 +321,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -319,6 +352,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -338,6 +374,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 50, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -357,6 +396,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 50, unit = "kVAh" })) } + }, + { + min_api_version = 19 } ) @@ -378,6 +420,9 @@ test.register_message_test( direction = "send", message = zw_test_utils.zwave_test_build_send_command(mock_device, Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS })) } + }, + { + min_api_version = 19 } ) @@ -419,6 +464,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -460,6 +508,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -479,6 +530,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -498,6 +552,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -522,6 +579,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -546,6 +606,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -637,6 +700,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -728,6 +794,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) }, + }, + { + min_api_version = 19 } ) @@ -747,6 +816,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -766,6 +838,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -785,6 +860,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.tested()) } + }, + { + min_api_version = 19 } ) @@ -804,6 +882,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) } + }, + { + min_api_version = 19 } ) @@ -823,6 +904,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) } + }, + { + min_api_version = 19 } ) @@ -842,6 +926,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) } + }, + { + min_api_version = 19 } ) @@ -861,6 +948,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -880,6 +970,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) } + }, + { + min_api_version = 19 } ) @@ -899,6 +992,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -918,6 +1014,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) } + }, + { + min_api_version = 19 } ) @@ -937,6 +1036,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -956,6 +1058,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -975,6 +1080,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -1006,7 +1114,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1026,6 +1135,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -1045,6 +1157,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -1064,6 +1179,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -1090,7 +1208,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1110,6 +1229,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -1129,6 +1251,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -1148,6 +1273,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -1167,6 +1295,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -1186,6 +1317,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -1205,6 +1339,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -1224,6 +1361,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -1243,6 +1383,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -1262,6 +1405,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -1281,6 +1427,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -1300,6 +1449,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -1319,6 +1471,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -1338,6 +1493,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -1356,6 +1514,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(55)) } + }, + { + min_api_version = 19 } ) @@ -1374,6 +1535,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(1)) } + }, + { + min_api_version = 19 } ) @@ -1393,7 +1557,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(10) test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command(mock_device, Battery:Get({}))) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -1412,6 +1579,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(1)) } + }, + { + min_api_version = 19 } ) @@ -1431,6 +1601,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) } + }, + { + min_api_version = 19 } ) @@ -1559,7 +1732,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua index 747c4cbce1..b4a78440be 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua @@ -78,6 +78,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) } + }, + { + min_api_version = 19 } ) @@ -100,6 +103,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.battery()) } + }, + { + min_api_version = 19 } ) @@ -122,6 +128,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) } + }, + { + min_api_version = 19 } ) @@ -144,6 +153,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(1)) } + }, + { + min_api_version = 19 } ) @@ -166,6 +178,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 19 } ) @@ -189,6 +204,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -212,6 +230,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua index f57bfe7950..c368d47649 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua @@ -59,6 +59,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -75,6 +78,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -94,6 +100,9 @@ test.register_message_test( WakeUp:IntervalSet({node_id = 0x00, seconds = 1200}) ) } + }, + { + min_api_version = 19 } ) @@ -136,7 +145,10 @@ test.register_coroutine_test( mock_sensor, SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.LUMINANCE}, {dst_channels={2}}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Receiving wakeup notification should generate proper messages", @@ -164,6 +176,9 @@ test.register_coroutine_test( mock_sensor, Battery:Get({}) )) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua b/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua index 9818f1422c..f6de3d99fc 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua @@ -109,8 +109,10 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() + diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua b/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua index 873909df23..583f33825f 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua @@ -69,7 +69,10 @@ test.register_coroutine_test( Configuration:Get({ parameter_number = 12 }) )) mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -77,7 +80,10 @@ test.register_coroutine_test( function() test.socket.zwave:__queue_receive({mock_sensor.id, Configuration:Report( { configuration_value = 0x00, parameter_number = 12 } )}) mock_sensor:expect_metadata_update({ profile = "illuminance-temperature" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -96,7 +102,10 @@ test.register_coroutine_test( test.socket.zwave:__queue_receive({mock_sensor.id, Configuration:Report( { configuration_value = 0x00, parameter_number = 12 } )}) mock_sensor:expect_metadata_update({ profile = "illuminance-temperature" }) test.socket.zwave:__queue_receive({mock_sensor.id, WakeUp:Notification({})}) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua index 2321b5bd09..b7bda766f8 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua @@ -67,7 +67,10 @@ test.register_coroutine_test( SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.TEMPERATURE, scale = SensorMultilevel.scale.temperature.CELSIUS }) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -86,6 +89,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -105,6 +111,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -124,6 +133,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -143,6 +155,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -162,6 +177,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -181,6 +199,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -209,6 +230,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -237,6 +261,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -253,6 +280,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -269,6 +299,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -285,6 +318,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua b/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua index 866508e3d6..12bb282ea6 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua @@ -65,6 +65,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -89,6 +92,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -106,7 +112,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected())) test.mock_time.advance_time(10) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua b/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua index e1b82a6b38..f415cae9e8 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua @@ -66,6 +66,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -85,6 +88,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -104,6 +110,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -123,6 +132,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -160,7 +172,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -182,7 +195,10 @@ test.register_coroutine_test( SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.TEMPERATURE}) )) mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua index 66cca4afe4..c2970daac4 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua @@ -61,6 +61,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -81,6 +84,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -105,7 +111,10 @@ test.register_coroutine_test( test.wait_for_events() test.mock_time.advance_time(10) test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -124,6 +133,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -144,6 +156,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -162,6 +177,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({value = 131, unit = "lux"})) } + }, + { + min_api_version = 19 } ) @@ -182,6 +200,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 80.25, unit = "F"})) } + }, + { + min_api_version = 19 } ) @@ -201,6 +222,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 53 })) } + }, + { + min_api_version = 19 } ) @@ -224,7 +248,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -243,6 +268,9 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({value = 0, unit = "lux"})) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua index 4ff11090c9..282ddd9f38 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua @@ -67,6 +67,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -91,6 +94,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -110,6 +116,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -128,6 +137,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -147,6 +159,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -166,6 +181,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 400, unit = "lux" })) } + }, + { + min_api_version = 19 } ) @@ -186,6 +204,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -205,6 +226,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -229,6 +253,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -274,7 +301,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -287,7 +315,10 @@ test.register_coroutine_test( test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command(mock_device, SensorBinary:Get({sensor_type = SensorBinary.sensor_type.MOTION}))) test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command(mock_device, SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.LUMINANCE, scale = SensorMultilevel.scale.luminance.LUX}))) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua index d918f47179..d9351267dc 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua @@ -63,6 +63,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -87,6 +90,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -106,6 +112,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -125,6 +134,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -144,6 +156,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 400, unit = "lux" })) } + }, + { + min_api_version = 19 } ) @@ -163,6 +178,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -182,6 +200,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -206,6 +227,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -231,7 +255,10 @@ test.register_coroutine_test( SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.LUMINANCE, scale = SensorMultilevel.scale.luminance.LUX}) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua index c15daeb188..97fc162405 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua @@ -139,6 +139,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -241,7 +244,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -276,6 +280,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -295,6 +302,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -314,6 +324,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -333,6 +346,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -351,6 +367,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -369,6 +388,9 @@ test.register_message_test( direction = "send", message = mock_motion_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -397,6 +419,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -421,6 +446,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -440,6 +468,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -459,6 +490,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 22 })) } + }, + { + min_api_version = 19 } ) @@ -478,6 +512,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 400, unit = "lux" })) } + }, + { + min_api_version = 19 } ) @@ -497,6 +534,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -516,6 +556,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -535,6 +578,9 @@ test.register_message_test( direction = "send", message = mock_contact_device:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -564,6 +610,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -583,6 +632,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -602,6 +654,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -626,6 +681,9 @@ test.register_message_test( { device_uuid = mock_motion_device.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -650,6 +708,9 @@ test.register_message_test( { device_uuid = mock_water_device.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -674,6 +735,9 @@ test.register_message_test( { device_uuid = mock_contact_device.id, capability_id = "switch", capability_attr_id = "switch" } } } + }, + { + min_api_version = 19 } ) @@ -693,6 +757,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.unhealthy()) } + }, + { + min_api_version = 19 } ) @@ -712,6 +779,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) } + }, + { + min_api_version = 19 } ) @@ -732,6 +802,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.dewPoint.dewpoint({value = 8, unit = "C"})) } + }, + { + min_api_version = 19 } ) @@ -750,6 +823,9 @@ test.register_message_test( direction = "send", message = mock_contact_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -768,6 +844,9 @@ test.register_message_test( direction = "send", message = mock_motion_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua index e6caf1f07c..e3bff3fc83 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua @@ -65,7 +65,10 @@ test.register_coroutine_test( WakeUp:IntervalSet({ seconds = 14400, node_id = 0}) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -81,6 +84,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -97,6 +103,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -116,6 +125,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -135,6 +147,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -154,6 +169,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -173,6 +191,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -189,6 +210,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -208,6 +232,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -227,6 +254,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -246,6 +276,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -265,6 +298,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) 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 34cdc4ceea..c00f572e25 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_aeon_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_aeon_siren.lua @@ -54,6 +54,9 @@ test.register_message_test( direction = "send", message = mock_siren:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -77,6 +80,9 @@ test.register_message_test( direction = "send", message = mock_siren:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -105,7 +111,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -133,7 +142,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) @@ -162,7 +174,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -190,7 +205,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -218,7 +236,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -242,7 +263,10 @@ test.register_coroutine_test( mock_siren, Basic:Set({value=0x00}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -267,7 +291,10 @@ test.register_coroutine_test( mock_siren, Basic:Set({value=0x00}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -293,7 +320,10 @@ test.register_coroutine_test( mock_siren, Basic:Set({value=0x00}) )) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 924ef4e4f2..b307e921f7 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 @@ -111,7 +111,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_siren:generate_test_message("main", capabilities.chime.chime.off()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -146,7 +149,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_siren:generate_test_message("sound2", capabilities.chime.chime.off()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -181,7 +187,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_siren:generate_test_message("sound3", capabilities.chime.chime.off()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -216,7 +225,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_siren:generate_test_message("sound4", capabilities.chime.chime.off()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -251,7 +263,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_siren:generate_test_message("sound5", capabilities.chime.chime.off()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -286,7 +301,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_siren:generate_test_message("sound6", capabilities.chime.chime.off()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -321,7 +339,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_siren:generate_test_message("sound7", capabilities.chime.chime.off()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -356,7 +377,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_siren:generate_test_message("sound8", capabilities.chime.chime.off()) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -399,7 +423,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -443,7 +468,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -487,7 +513,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -531,7 +558,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -575,7 +603,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -619,7 +648,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -663,7 +693,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -707,7 +738,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -745,7 +777,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -782,7 +817,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -819,7 +857,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -856,7 +897,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -893,7 +937,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -930,7 +977,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -967,7 +1017,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1004,7 +1057,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1041,7 +1097,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1078,7 +1137,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1115,7 +1177,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1152,7 +1217,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1189,7 +1257,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1226,7 +1297,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1263,7 +1337,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1300,7 +1377,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1337,7 +1417,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1374,7 +1457,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1411,7 +1497,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1448,7 +1537,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1485,7 +1577,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1522,7 +1617,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1559,7 +1657,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1596,7 +1697,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1633,7 +1737,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1670,7 +1777,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1707,7 +1817,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1744,7 +1857,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1781,7 +1897,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1818,7 +1937,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1855,7 +1977,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1892,7 +2017,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1929,7 +2057,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1966,7 +2097,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2003,7 +2137,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2040,7 +2177,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2077,7 +2217,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2114,7 +2257,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2151,7 +2297,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2188,7 +2337,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2225,7 +2377,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2262,7 +2417,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2299,7 +2457,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2336,7 +2497,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2373,7 +2537,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2410,7 +2577,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2447,7 +2617,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2484,7 +2657,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -2505,7 +2681,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -2527,7 +2704,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -2558,7 +2736,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2571,7 +2752,10 @@ test.register_coroutine_test( _preferences.configureSoundAndVolume = false test.socket.device_lifecycle:__queue_receive(mock_siren:generate_info_changed({ preferences = _preferences})) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2593,7 +2777,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2604,7 +2791,10 @@ test.register_coroutine_test( _preferences.triggerButtonUnpairing = false test.socket.device_lifecycle:__queue_receive(mock_siren:generate_info_changed({ preferences = _preferences})) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2626,7 +2816,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2637,7 +2830,10 @@ test.register_coroutine_test( _preferences.triggerButtonPairing = false test.socket.device_lifecycle:__queue_receive(mock_siren:generate_info_changed({ preferences = _preferences})) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -2662,6 +2858,9 @@ test.register_message_test( direction = "send", message = mock_siren_with_buttons:generate_test_message("sound3", capabilities.battery.battery(BUTTON_BATTERY_LOW)) } + }, + { + min_api_version = 19 } ) @@ -2687,6 +2886,9 @@ test.register_message_test( direction = "send", message = mock_siren_with_buttons:generate_test_message("sound3", capabilities.battery.battery(BUTTON_BATTERY_NORMAL)) } + }, + { + min_api_version = 19 } ) @@ -2712,6 +2914,9 @@ test.register_message_test( direction = "send", message = mock_siren_with_buttons:generate_test_message("sound4", capabilities.battery.battery(BUTTON_BATTERY_LOW)) } + }, + { + min_api_version = 19 } ) @@ -2737,6 +2942,9 @@ test.register_message_test( direction = "send", message = mock_siren_with_buttons:generate_test_message("sound4", capabilities.battery.battery(BUTTON_BATTERY_NORMAL)) } + }, + { + min_api_version = 19 } ) @@ -2762,6 +2970,9 @@ test.register_message_test( direction = "send", message = mock_siren_with_buttons:generate_test_message("sound5", capabilities.battery.battery(BUTTON_BATTERY_LOW)) } + }, + { + min_api_version = 19 } ) @@ -2787,6 +2998,9 @@ test.register_message_test( direction = "send", message = mock_siren_with_buttons:generate_test_message("sound5", capabilities.battery.battery(BUTTON_BATTERY_NORMAL)) } + }, + { + min_api_version = 19 } ) @@ -2820,7 +3034,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -2837,7 +3054,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_siren:generate_test_message("sound5", capabilities.battery.battery(BUTTON_BATTERY_LOW)) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 54b4243331..74f25a0aaf 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 @@ -66,6 +66,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.alarm.alarm.off()) } + }, + { + min_api_version = 19 } ) @@ -101,6 +104,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("siren1", capabilities.alarm.alarm.off()) } + }, + { + min_api_version = 19 } ) @@ -136,6 +142,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("siren2", capabilities.alarm.alarm.both()) } + }, + { + min_api_version = 19 } ) @@ -161,6 +170,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.alarm.alarm.both()) } + }, + { + min_api_version = 19 } ) @@ -196,6 +208,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("siren1", capabilities.alarm.alarm.both()) } + }, + { + min_api_version = 19 } ) @@ -231,6 +246,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("siren2", capabilities.alarm.alarm.both()) } + }, + { + min_api_version = 19 } ) @@ -266,7 +284,10 @@ test.register_coroutine_test( SwitchBinary:Get({}, { encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = { 4 } }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -290,7 +311,10 @@ test.register_coroutine_test( ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Receiving the both command from siren1 component should generate the correct commands including delayed commands", @@ -321,7 +345,10 @@ test.register_coroutine_test( ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -345,7 +372,10 @@ test.register_coroutine_test( ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -368,7 +398,10 @@ test.register_coroutine_test( SwitchBinary:Get({}, {encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = {4}}) ) ) - end + end, + { + min_api_version = 19 + } ) @@ -392,7 +425,10 @@ test.register_coroutine_test( SwitchBinary:Get({}, {dst_channels = {1}}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Receiving the off command from siren1 should generate the correct commands", @@ -414,7 +450,10 @@ test.register_coroutine_test( SwitchBinary:Get({}, {encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = {2}}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Receiving the off command from siren2 should generate the correct commands", @@ -436,7 +475,10 @@ test.register_coroutine_test( SwitchBinary:Get({}, {encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = {3}}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( "Receiving the off command from siren3 should generate the correct commands", @@ -458,7 +500,10 @@ test.register_coroutine_test( SwitchBinary:Get({}, {encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = {4}}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 e99cfce77c..cfc42e2a63 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_fortrezz_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_fortrezz_siren.lua @@ -59,7 +59,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_siren:generate_test_message("main", capabilities.alarm.alarm.both({}))) test.socket.capability:__expect_send(mock_siren:generate_test_message("main", capabilities.switch.switch.on({}))) - end + end, + { + min_api_version = 19 + } ) @@ -90,7 +93,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_siren:generate_test_message("main", capabilities.alarm.alarm.siren({}))) test.socket.capability:__expect_send(mock_siren:generate_test_message("main", capabilities.switch.switch.on({}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -120,7 +126,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_siren:generate_test_message("main", capabilities.alarm.alarm.strobe({}))) test.socket.capability:__expect_send(mock_siren:generate_test_message("main", capabilities.switch.switch.on({}))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -150,7 +159,10 @@ test.register_coroutine_test( }) test.socket.capability:__expect_send(mock_siren:generate_test_message("main", capabilities.alarm.alarm.off({}))) test.socket.capability:__expect_send(mock_siren:generate_test_message("main", capabilities.switch.switch.off({}))) - end + end, + { + min_api_version = 19 + } ) 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 d825be6cec..3f3146ee5a 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 @@ -63,7 +63,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -93,7 +94,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -133,7 +135,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -164,7 +167,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -196,7 +200,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -218,7 +223,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -240,7 +246,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -272,7 +279,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_siren:generate_test_message("main", capabilities.chime.chime.off()) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -296,7 +306,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -318,7 +331,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -342,7 +358,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -366,7 +385,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -390,7 +412,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -414,7 +439,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -432,7 +460,10 @@ test.register_coroutine_test( Basic:Set({value=0x00}) ) ) - end + end, + { + min_api_version = 19 + } ) do @@ -451,7 +482,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -496,7 +531,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -518,7 +557,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 e62141ebcf..71451e3740 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_utilitech_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_utilitech_siren.lua @@ -41,6 +41,9 @@ test.register_message_test( direction = "receive", message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(Battery:Report({ battery_level = 0x00 })) } } + }, + { + min_api_version = 19 } ) @@ -58,7 +61,10 @@ test.register_coroutine_test( mock_siren, Battery:Get({}) )) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 a7b7123f38..c3643a7b82 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_yale_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_yale_siren.lua @@ -52,6 +52,9 @@ test.register_message_test( direction = "send", message = mock_siren:generate_test_message("main", capabilities.alarm.alarm.off()) } + }, + { + min_api_version = 19 } ) @@ -70,6 +73,9 @@ test.register_message_test( direction = "send", message = mock_siren:generate_test_message("main", capabilities.alarm.alarm.both()) } + }, + { + min_api_version = 19 } ) @@ -105,7 +111,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) @@ -141,7 +150,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -176,7 +188,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -210,7 +225,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -243,7 +261,10 @@ test.register_coroutine_test( mock_siren, Basic:Set({value=0x00}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -288,7 +309,10 @@ test.register_coroutine_test( mock_siren, Configuration:Get({parameter_number = 4}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -305,7 +329,10 @@ test.register_coroutine_test( mock_siren, Battery:Get({}) )) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 6b912be8bb..551eb0b90a 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_zipato_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_zipato_siren.lua @@ -61,7 +61,10 @@ test.register_coroutine_test( mock_siren, Basic:Set({value=0x00}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -89,7 +92,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -106,7 +112,10 @@ test.register_coroutine_test( mock_siren, Battery:Get({}) )) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -124,6 +133,9 @@ test.register_message_test( direction = "send", message = mock_siren:generate_test_message("main", capabilities.alarm.alarm.off()) } + }, + { + min_api_version = 19 } ) @@ -142,6 +154,9 @@ test.register_message_test( direction = "send", message = mock_siren:generate_test_message("main", capabilities.alarm.alarm.both()) } + }, + { + min_api_version = 19 } ) 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 a40b510b49..c1ff4cbb31 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 @@ -50,6 +50,9 @@ test.register_message_test( direction = "send", message = mock_siren:generate_test_message("main", capabilities.alarm.alarm.off()) } + }, + { + min_api_version = 19 } ) @@ -69,6 +72,9 @@ test.register_message_test( direction = "send", message = mock_siren:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -88,6 +94,9 @@ test.register_message_test( direction = "send", message = mock_siren:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({value = 25})) } + }, + { + min_api_version = 19 } ) @@ -107,6 +116,9 @@ test.register_message_test( direction = "send", message = mock_siren:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 25, unit = 'C'})) } + }, + { + min_api_version = 19 } ) @@ -132,7 +144,10 @@ test.register_coroutine_test( Basic:Get({}) )) mock_siren:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 0e0f407523..2cc31d91c6 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 @@ -49,6 +49,9 @@ test.register_message_test( direction = "send", message = mock_siren_notification:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -73,6 +76,9 @@ test.register_message_test( direction = "send", message = mock_siren_notification:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -92,6 +98,9 @@ test.register_message_test( direction = "send", message = mock_siren_notification:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -116,6 +125,9 @@ test.register_message_test( direction = "send", message = mock_siren_notification:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) 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 121f6170e2..9f9f8cc9d3 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_zwave_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_zwave_siren.lua @@ -82,6 +82,9 @@ test.register_message_test( direction = "send", message = mock_siren_basic:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -105,6 +108,9 @@ test.register_message_test( direction = "send", message = mock_siren_basic:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -128,6 +134,9 @@ test.register_message_test( direction = "send", message = mock_siren_basic:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -151,6 +160,9 @@ test.register_message_test( direction = "send", message = mock_siren_basic:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -174,6 +186,9 @@ test.register_message_test( direction = "send", message = mock_siren_switch_binary:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -197,6 +212,9 @@ test.register_message_test( direction = "send", message = mock_siren_switch_binary:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -220,6 +238,9 @@ test.register_message_test( direction = "send", message = mock_siren_switch_binary:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -243,6 +264,9 @@ test.register_message_test( direction = "send", message = mock_siren_switch_binary:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -271,7 +295,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -299,7 +326,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) @@ -328,7 +358,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -356,7 +389,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -384,7 +420,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -422,7 +461,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_siren_basic.id, "added" }) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 10595c7137..4ed0e53e90 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 @@ -48,6 +48,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.soundSensor.sound.detected()) } + }, + { + min_api_version = 19 } ) @@ -67,6 +70,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.soundSensor.sound.not_detected()) } + }, + { + min_api_version = 19 } ) @@ -83,6 +89,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.soundSensor.sound.not_detected()) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_co_sensor_zw5.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_co_sensor_zw5.lua index dc14f1c51b..2759e456c0 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_co_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_co_sensor_zw5.lua @@ -68,7 +68,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number = ACOUSTIC_SIGNALS, configuration_value = EXCEEDING_THE_TEMPERATURE}) )) mock_fibaro_CO_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -84,6 +87,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_CO_sensor:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -109,6 +115,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_CO_sensor:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' })) } + }, + { + min_api_version = 19 } ) @@ -134,6 +143,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_CO_sensor:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 70.7, unit = 'F' })) } + }, + { + min_api_version = 19 } ) @@ -158,6 +170,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_CO_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) } + }, + { + min_api_version = 19 } ) @@ -182,6 +197,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_CO_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) } + }, + { + min_api_version = 19 } ) @@ -206,6 +224,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_CO_sensor:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.detected()) } + }, + { + min_api_version = 19 } ) @@ -230,6 +251,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_CO_sensor:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.clear()) } + }, + { + min_api_version = 19 } ) @@ -253,6 +277,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_CO_sensor:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.tested()) } + }, + { + min_api_version = 19 } ) @@ -278,6 +305,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_CO_sensor:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.clear()) } + }, + { + min_api_version = 19 } ) @@ -302,6 +332,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_CO_sensor:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) } + }, + { + min_api_version = 19 } ) @@ -326,6 +359,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_CO_sensor:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) } + }, + { + min_api_version = 19 } ) @@ -355,7 +391,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -462,7 +499,10 @@ test.register_coroutine_test( Configuration:Report({ parameter_number = 2, configuration_value = 3 }) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_smoke_sensor.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_smoke_sensor.lua index 0ec85f00f2..8dc52eb471 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_smoke_sensor.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_smoke_sensor.lua @@ -148,7 +148,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number=32, size=2, configuration_value=4320}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -181,7 +184,10 @@ test.register_coroutine_test( Battery:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -201,7 +207,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_alarm_v1.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_alarm_v1.lua index aa1ebb27f0..e87ecfa22a 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_alarm_v1.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_alarm_v1.lua @@ -52,6 +52,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -71,6 +74,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) } + }, + { + min_api_version = 19 } ) @@ -90,6 +96,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.detected()) } + }, + { + min_api_version = 19 } ) @@ -109,6 +118,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.clear()) } + }, + { + min_api_version = 19 } ) @@ -151,6 +163,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.clear()) }, + }, + { + min_api_version = 19 } ) @@ -170,6 +185,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) } + }, + { + min_api_version = 19 } ) @@ -189,6 +207,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_co_detector.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_co_detector.lua index 1523bf2420..63d81ae54f 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_co_detector.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_co_detector.lua @@ -53,6 +53,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.detected()) } + }, + { + min_api_version = 19 } ) @@ -72,6 +75,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.detected()) }, + }, + { + min_api_version = 19 } ) test.register_message_test( @@ -90,6 +96,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.clear()) }, + }, + { + min_api_version = 19 } ) @@ -109,6 +118,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.detected()) } + }, + { + min_api_version = 19 } ) @@ -128,6 +140,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.tested()) } + }, + { + min_api_version = 19 } ) @@ -147,6 +162,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.carbonMonoxideDetector.carbonMonoxide.clear()) } + }, + { + min_api_version = 19 } ) @@ -161,6 +179,9 @@ test.register_message_test( alarm_level = 1 })) } }, + }, + { + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_smoke_detector.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_smoke_detector.lua index 4464a10fda..1d2826bd97 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_smoke_detector.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_smoke_detector.lua @@ -54,6 +54,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -73,6 +76,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) }, + }, + { + min_api_version = 19 } ) @@ -92,6 +98,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) }, + }, + { + min_api_version = 19 } ) @@ -111,6 +120,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -130,6 +142,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -149,6 +164,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.tested()) } + }, + { + min_api_version = 19 } ) @@ -168,6 +186,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) } + }, + { + min_api_version = 19 } ) @@ -182,6 +203,9 @@ test.register_message_test( alarm_level = 1 })) } }, + }, + { + min_api_version = 19 } ) @@ -228,7 +252,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -248,6 +273,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) } + }, + { + min_api_version = 19 } ) @@ -267,6 +295,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) } + }, + { + min_api_version = 19 } ) @@ -286,6 +317,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) } + }, + { + min_api_version = 19 } ) @@ -305,6 +339,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) } + }, + { + min_api_version = 19 } ) @@ -324,6 +361,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) } + }, + { + min_api_version = 19 } ) @@ -366,7 +406,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 f11a379666..d4317636be 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 @@ -150,6 +150,9 @@ test.register_message_test( {encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = {6}}) ) } + }, + { + min_api_version = 19 } ) @@ -236,6 +239,9 @@ test.register_message_test( {encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = {6}}) ) } + }, + { + min_api_version = 19 } ) @@ -284,6 +290,9 @@ test.register_message_test( {encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = {3}}) ) } + }, + { + min_api_version = 19 } ) @@ -332,6 +341,9 @@ test.register_message_test( {encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = {4}}) ) } + }, + { + min_api_version = 19 } ) @@ -363,6 +375,9 @@ test.register_message_test( direction = "send", message = mock_metering_switch:generate_test_message("switch1", capabilities.energyMeter.energy({ value = 50.0, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -394,6 +409,9 @@ test.register_message_test( direction = "send", message = mock_metering_switch:generate_test_message("switch2", capabilities.energyMeter.energy({ value = 50.0, unit = "kVAh" })) } + }, + { + min_api_version = 19 } ) @@ -425,6 +443,9 @@ test.register_message_test( direction = "send", message = mock_metering_switch:generate_test_message("switch3", capabilities.powerMeter.power({ value = 50, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -492,6 +513,9 @@ test.register_message_test( {encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = {6}}) ) } + }, + { + min_api_version = 19 } ) @@ -523,6 +547,9 @@ test.register_message_test( direction = "send", message = mock_metering_switch:generate_test_message("main", capabilities.powerMeter.power({ value = 50, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -592,7 +619,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number = 112, size = 4, configuration_value = 90}) )) mock_metering_switch:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -723,7 +753,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -854,7 +887,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -942,7 +978,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1030,7 +1069,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1118,7 +1160,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1206,7 +1251,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 6fb6c9c5ef..052516689d 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 @@ -58,7 +58,10 @@ test.register_coroutine_test( Configuration:Set({ parameter_number=111, size=4, configuration_value=300 }) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -95,7 +98,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -133,6 +137,9 @@ test.register_message_test( Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) ) } + }, + { + min_api_version = 19 } ) @@ -170,6 +177,9 @@ test.register_message_test( Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) ) } + }, + { + min_api_version = 19 } ) @@ -211,6 +221,9 @@ test.register_message_test( Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) ) } + }, + { + min_api_version = 19 } ) @@ -265,6 +278,9 @@ test.register_message_test( Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) ) } + }, + { + min_api_version = 19 } ) @@ -294,6 +310,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -315,6 +334,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -381,7 +403,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -448,7 +473,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -480,7 +508,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 272321f53f..a1abd8c4d4 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 @@ -111,7 +111,10 @@ test.register_coroutine_test( }) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 a11fe3abcb..5b40b8ee2e 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 @@ -55,6 +55,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 27, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -76,6 +79,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -116,7 +122,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -127,7 +134,10 @@ test.register_coroutine_test( test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command(mock_device, Meter:Reset({}))) test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command(mock_device, Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -155,7 +165,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 5000 })) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -178,7 +191,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -201,7 +217,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -225,7 +244,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -249,7 +271,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -273,7 +298,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -297,7 +325,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -321,7 +352,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -345,7 +379,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -369,7 +406,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -393,7 +433,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -417,7 +460,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -441,7 +487,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -465,7 +514,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) 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 2ebeb664bb..83262067e7 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 @@ -63,7 +63,10 @@ test.register_coroutine_test( }) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 db1e4c2498..f5d81db100 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 @@ -82,7 +82,10 @@ test.register_coroutine_test( Configuration:Set({ parameter_number=113, size=4, configuration_value=300 }) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -119,7 +122,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -157,6 +161,9 @@ test.register_message_test( Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) ) } + }, + { + min_api_version = 19 } ) @@ -194,6 +201,9 @@ test.register_message_test( Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) ) } + }, + { + min_api_version = 19 } ) @@ -235,6 +245,9 @@ test.register_message_test( Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) ) } + }, + { + min_api_version = 19 } ) @@ -289,6 +302,9 @@ test.register_message_test( Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) ) } + }, + { + min_api_version = 19 } ) @@ -318,6 +334,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -339,6 +358,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -405,7 +427,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -472,7 +497,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -504,7 +532,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 a8206a0052..679815ef97 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 @@ -57,7 +57,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 fdba32d9cb..5da5ef2739 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 @@ -58,7 +58,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -85,7 +88,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 079616dba4..7e9d8a8064 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 @@ -64,7 +64,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -91,7 +94,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -112,6 +118,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 55, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -133,6 +142,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -161,7 +173,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 5000 })) ) - end + end, + { + min_api_version = 19 + } ) do @@ -205,7 +220,11 @@ do SwitchColor:Get({ color_component_id=SwitchColor.color_component_id.RED }) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 0ecb71e59d..c23d236239 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 @@ -52,7 +52,10 @@ test.register_coroutine_test( }) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -79,7 +82,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -106,7 +112,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -127,6 +136,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 55, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -148,6 +160,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -176,7 +191,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 5000 })) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 4c2b76fdf6..29e491fab7 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 @@ -53,7 +53,10 @@ test.register_coroutine_test( Configuration:Set({ parameter_number = 103, configuration_value = 0, size = 4 }) )) mock_switch:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 cbfe5f42a0..22fb2bd00a 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 @@ -47,6 +47,9 @@ test.register_message_test( direction = "send", message = mock_metering_switch:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -66,6 +69,9 @@ test.register_message_test( direction = "send", message = mock_metering_switch:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) 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 ee48f99494..ce3d0d172b 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 @@ -75,6 +75,9 @@ test.register_message_test( direction = "send", message = mock_multi_switch:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 22 })) } + }, + { + min_api_version = 19 } ) @@ -101,6 +104,9 @@ test.register_message_test( direction = "send", message = mock_multi_switch:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25, unit = 'C' })) } + }, + { + min_api_version = 19 } ) @@ -130,6 +136,9 @@ test.register_message_test( direction = "send", message = mock_multi_switch:generate_test_message("switch1", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -159,6 +168,9 @@ test.register_message_test( direction = "send", message = mock_multi_switch:generate_test_message("switch1", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -188,6 +200,9 @@ test.register_message_test( direction = "send", message = mock_multi_switch:generate_test_message("switch2", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -217,6 +232,9 @@ test.register_message_test( direction = "send", message = mock_multi_switch:generate_test_message("switch2", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -246,6 +264,9 @@ test.register_message_test( direction = "send", message = mock_multi_switch:generate_test_message("switch3", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -275,6 +296,9 @@ test.register_message_test( direction = "send", message = mock_multi_switch:generate_test_message("switch3", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -295,7 +319,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number = 1, size = 2, configuration_value = 10 * 60}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -357,7 +384,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 d50b7cdaa5..ce422f61d0 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 @@ -74,7 +74,10 @@ test.register_coroutine_test( capabilities.switch.switch.off() ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -113,7 +116,10 @@ test.register_coroutine_test( capabilities.switch.switch.on() ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -139,7 +145,10 @@ test.register_coroutine_test( ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -224,7 +233,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -259,7 +271,10 @@ test.register_coroutine_test( ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -316,7 +331,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -361,7 +379,10 @@ test.register_coroutine_test( } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -375,7 +396,10 @@ test.register_coroutine_test( ) } ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -432,7 +456,10 @@ test.register_coroutine_test( } ) -- if group_id and scene_id are same, do nothing. - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -454,7 +481,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -469,7 +499,10 @@ test.register_coroutine_test( } ) -- driver should do nothing. - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -486,7 +519,10 @@ test.register_coroutine_test( Indicator:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -503,7 +539,10 @@ test.register_coroutine_test( Indicator:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) 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 8b5e96b161..e40913a9ae 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 @@ -42,6 +42,9 @@ test.register_message_test( direction = "receive", message = { mock_device.id, zw_test_utils.zwave_test_build_receive_command(Basic:Report({ value = 0xFF })) } } + }, + { + min_api_version = 19 } ) @@ -63,6 +66,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.level(0)) } + }, + { + min_api_version = 19 } ) @@ -87,6 +93,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -119,7 +128,11 @@ do direction = "send", message = mock_device:generate_test_message("main", capabilities.switchLevel.level(level)) } + }, + { + min_api_version = 19 } + ) end @@ -161,6 +174,9 @@ test.register_message_test( SwitchMultilevel:Get({}) ) } + }, + { + min_api_version = 19 } ) end @@ -185,7 +201,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -208,7 +227,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -233,7 +255,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 6815a408da..4e5dfb1a8a 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 @@ -71,6 +71,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -114,6 +117,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -134,7 +140,10 @@ test.register_coroutine_test( Basic:Report({value=0xFF}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -154,7 +163,10 @@ test.register_coroutine_test( Basic:Report({value=0x00}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -170,6 +182,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -186,6 +201,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) 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 99c6ae4068..0a5e2baf2a 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 @@ -41,7 +41,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number=7, configuration_value=1, size=1}) )) mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 4f5557f9a3..83e1aa9354 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_ecolink_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_ecolink_switch.lua @@ -62,7 +62,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -92,7 +93,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -109,6 +111,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -138,6 +143,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -167,6 +175,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -204,6 +215,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -233,6 +247,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -256,7 +273,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -280,7 +298,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 59934e6439..2b531b3c2d 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 @@ -133,6 +133,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) @@ -182,6 +185,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) test.register_message_test( @@ -229,6 +235,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) @@ -277,6 +286,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) @@ -314,7 +326,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -351,7 +366,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -388,7 +406,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -425,7 +446,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -575,7 +599,10 @@ test.register_coroutine_test( ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -624,7 +651,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 0b40d6be33..aeabf6c725 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 @@ -53,7 +53,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -71,7 +74,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -101,6 +107,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -131,6 +140,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -161,6 +173,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -191,6 +206,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -218,6 +236,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -244,6 +265,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -264,6 +288,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.button.held({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -290,6 +317,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -310,6 +340,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.button.down_hold({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -336,6 +369,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -356,6 +392,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.button.double({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -382,6 +421,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -402,6 +444,9 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.button.button.pushed_3x({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -428,6 +473,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -447,6 +495,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -473,6 +524,9 @@ test.register_message_test( ) } }, + }, + { + min_api_version = 19 } ) @@ -492,6 +546,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 27, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -518,6 +575,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -629,7 +689,10 @@ test.register_coroutine_test( ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 58ef03fc3f..37a567ce5e 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 @@ -106,7 +106,10 @@ test.register_coroutine_test( ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 44aae0f2fa..262fb111d4 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 @@ -55,7 +55,10 @@ test.register_coroutine_test( }) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 8aa815e829..248e565e01 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 @@ -69,6 +69,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}, {dst_channels = {1}}) ) }, + }, + { + min_api_version = 19 } ) @@ -103,6 +106,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}, {dst_channels = {1}}) ) }, + }, + { + min_api_version = 19 } ) @@ -134,6 +140,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 55, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -165,6 +174,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("smartplug1", capabilities.powerMeter.power({ value = 89, unit = "W" })) } + }, + { + min_api_version = 19 } ) @@ -196,6 +208,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -227,6 +242,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("smartplug1", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -238,7 +256,10 @@ test.register_coroutine_test( mock_device, SwitchBinary:Set({target_value=0xFF},{dst_channels={1}}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -317,7 +338,10 @@ test.register_coroutine_test( ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 db69dc9845..412b137c14 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 @@ -51,7 +51,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -71,7 +75,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -91,7 +99,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -111,7 +123,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -131,7 +147,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -152,7 +172,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -172,7 +196,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 f4e41b5bee..10c27f89ba 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 @@ -115,6 +115,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -168,6 +171,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -212,6 +218,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -243,6 +252,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -296,6 +308,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -349,6 +364,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -393,6 +411,9 @@ test.register_message_test( { device_uuid = mock_child.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -429,6 +450,9 @@ test.register_message_test( direction = "send", message = mock_child:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -496,7 +520,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -562,7 +589,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -626,7 +656,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -692,7 +725,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -733,7 +769,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -765,7 +804,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -797,7 +839,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 2771cbc3a7..83743d1e3a 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 @@ -51,7 +51,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -68,7 +71,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -85,7 +91,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -102,7 +111,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -119,7 +131,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -136,7 +151,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 309d2c79b0..26a8261d9b 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 @@ -44,7 +44,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -69,7 +70,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -110,6 +112,9 @@ test.register_message_test( { device_uuid = mock_zwave_device1.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -137,6 +142,9 @@ test.register_message_test( { device_uuid = mock_zwave_device1.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -177,6 +185,9 @@ test.register_message_test( { device_uuid = mock_zwave_device1.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -203,7 +214,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -229,7 +243,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) local level = 49 @@ -256,7 +273,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 9b72af072e..3b322000ab 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 @@ -47,7 +47,10 @@ test.register_coroutine_test( }) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 4125fc6f00..2dc8087d3d 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_honeywell_dimmer.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_honeywell_dimmer.lua @@ -53,7 +53,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number=10, configuration_value=1, size=2}) )) mock_dimmer:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 bc752ec6f1..bf707557da 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 @@ -95,6 +95,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) @@ -156,6 +159,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) @@ -196,6 +202,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -236,6 +245,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -276,6 +288,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -316,6 +331,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -385,6 +403,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -455,6 +476,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -512,6 +536,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) @@ -569,6 +596,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) @@ -630,6 +660,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -691,6 +724,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -736,6 +772,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) @@ -781,6 +820,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) @@ -818,6 +860,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) @@ -856,6 +901,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) @@ -893,6 +941,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) @@ -931,6 +982,9 @@ test.register_message_test( }) ) } + }, + { + min_api_version = 19 } ) 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 0863f9eb43..0a570a6263 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_button.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_button.lua @@ -129,7 +129,10 @@ test.register_coroutine_test( ) ) end - end + end, + { + min_api_version = 19 + } ) @@ -154,6 +157,9 @@ test.register_message_test( direction = "send", message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -178,6 +184,9 @@ test.register_message_test( direction = "send", message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_4x({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -202,6 +211,9 @@ test.register_message_test( direction = "send", message = mock_inovelli_dimmer:generate_test_message("button3", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) 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 d7dfe35b24..0f8aa8b1c1 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer.lua @@ -46,7 +46,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -71,7 +72,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -99,6 +101,7 @@ test.register_message_test( } }, { + min_api_version = 19 } ) @@ -131,7 +134,11 @@ do direction = "send", message = mock_inovelli_dimmer:generate_test_message("main", capabilities.switchLevel.level(level)) } + }, + { + min_api_version = 19 } + ) end @@ -160,7 +167,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -188,7 +198,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) do @@ -218,7 +231,10 @@ do SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) end 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 6df8b97048..63bdb67d50 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 @@ -97,7 +97,11 @@ do Configuration:Get({ parameter_number=LED_COLOR_CONTROL_PARAMETER_NUMBER }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -129,7 +133,11 @@ do direction = "send", message = mock_inovelli_dimmer:generate_test_message(LED_BAR_COMPONENT_NAME, capabilities.colorControl.saturation(LED_GENERIC_SATURATION)) } + }, + { + min_api_version = 19 } + ) end 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 eab58c99f9..add536f97a 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 @@ -69,7 +69,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -110,7 +111,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -132,7 +134,11 @@ do direction = "send", message = mock_inovelli_dimmer:generate_test_message("main", capabilities.energyMeter.energy({ value = energy, unit = "kWh" })) } + }, + { + min_api_version = 19 } + ) end @@ -154,7 +160,11 @@ do direction = "send", message = mock_inovelli_dimmer:generate_test_message("main", capabilities.powerMeter.power({ value = power, unit = "W" })) } + }, + { + min_api_version = 19 } + ) end @@ -188,6 +198,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -228,7 +241,11 @@ do Meter:Get({scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } + ) end @@ -257,7 +274,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -285,7 +305,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) do @@ -315,7 +338,11 @@ do SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 43dd38ee7f..a473ddda1b 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 @@ -54,7 +54,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -78,7 +82,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -102,7 +110,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -126,7 +138,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -151,7 +167,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 316b9b1254..681bb514a0 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 @@ -57,6 +57,9 @@ test.register_message_test( message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -77,6 +80,9 @@ test.register_message_test( message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_2x({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -97,6 +103,9 @@ test.register_message_test( message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_3x({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -117,6 +126,9 @@ test.register_message_test( message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_4x({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -137,6 +149,9 @@ test.register_message_test( message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_5x({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -157,6 +172,9 @@ test.register_message_test( message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -177,6 +195,9 @@ test.register_message_test( message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_2x({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -197,6 +218,9 @@ test.register_message_test( message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_3x({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -217,6 +241,9 @@ test.register_message_test( message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_4x({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -237,6 +264,9 @@ test.register_message_test( message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_5x({ state_change = true })) } + }, + { + min_api_version = 19 } ) @@ -257,6 +287,9 @@ test.register_message_test( message = mock_inovelli_dimmer:generate_test_message("button3", capabilities.button.button.pushed({ state_change = true })) } + }, + { + min_api_version = 19 } ) 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 index 5593ced043..7163c70a17 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua @@ -128,7 +128,10 @@ test.register_coroutine_test( Notification:Get({notification_type = Notification.notification_type.HOME_SECURITY, event = Notification.event.home_security.MOTION_DETECTION}) ) ) - end + end, + { + min_api_version = 19 + } ) -- Test switch on command @@ -154,7 +157,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) -- Test switch off command @@ -180,7 +186,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) -- Test switch level command @@ -211,7 +220,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) -- Test central scene notifications @@ -235,7 +247,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -260,7 +273,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -318,7 +332,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 index 40213851c7..d8dfbc89a9 100644 --- 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 @@ -89,7 +89,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -142,7 +143,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) -- Test child device switch off command @@ -173,7 +177,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) -- Test child device level command @@ -229,7 +236,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) -- Test child device color command @@ -289,7 +299,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) -- Test child device color temperature command @@ -329,7 +342,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) 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 index 8cccb5726e..8d3e25bd9e 100644 --- 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 @@ -58,7 +58,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -80,7 +84,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -102,7 +110,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -124,7 +136,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -144,8 +160,13 @@ do parent_device_id = mock_inovelli_vzw32_sn.id, parent_assigned_child_key = "notification" }) - end + end, + { + min_api_version = 19 + } + ) 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_multi_metering_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_multi_metering_switch.lua index 11a79bc12d..1f761dc11c 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 @@ -114,7 +114,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -163,7 +166,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -212,7 +218,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -237,6 +246,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -262,6 +274,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -287,6 +302,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -312,6 +330,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -358,7 +379,10 @@ test.register_coroutine_test( )) mock_parent_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -398,6 +422,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -438,6 +465,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -465,7 +495,11 @@ do message = mock_parent_device:generate_test_message( "main", capabilities.energyMeter.energy({ value = energy, unit = "kWh" })) } + }, + { + min_api_version = 19 } + ) end @@ -495,7 +529,11 @@ do "main", capabilities.energyMeter.energy({ value = energy, unit = "kWh" }) ) } + }, + { + min_api_version = 19 } + ) end @@ -523,7 +561,11 @@ do message = mock_parent_device:generate_test_message( "main", capabilities.powerMeter.power({ value = power, unit = "W" })) } + }, + { + min_api_version = 19 } + ) end @@ -551,7 +593,11 @@ do message = mock_child_device:generate_test_message( "main", capabilities.powerMeter.power({ value = power, unit = "W" })) } + }, + { + min_api_version = 19 } + ) end @@ -582,7 +628,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -612,7 +661,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) do @@ -703,7 +755,11 @@ do ) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -716,7 +772,10 @@ test.register_coroutine_test( current_value = 0xFF }) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 7f584e3125..0884e87c58 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_multichannel_device.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_multichannel_device.lua @@ -167,6 +167,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -201,6 +204,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -235,6 +241,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -269,6 +278,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -316,6 +328,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -358,6 +373,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -400,6 +418,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -447,6 +468,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -494,6 +518,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -536,6 +563,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -578,6 +608,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -625,6 +658,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -665,6 +701,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -705,6 +744,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -745,6 +787,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -785,6 +830,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -838,6 +886,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -878,6 +929,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -931,6 +985,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -998,6 +1055,9 @@ test.register_message_test( SwitchMultilevel:Get({}, { dst_channels = { 5 } }) ) }, + }, + { + min_api_version = 19 } ) @@ -1038,7 +1098,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1079,7 +1140,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1120,7 +1182,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1164,6 +1227,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -1215,6 +1281,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -1241,6 +1310,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.contactSensor.contact.open()) } + }, + { + min_api_version = 19 } ) @@ -1267,6 +1339,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -1298,6 +1373,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.motionSensor.motion.active()) } + }, + { + min_api_version = 19 } ) @@ -1329,6 +1407,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -1450,6 +1531,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.contactSensor.contact.closed()) } + }, + { + min_api_version = 19 } ) @@ -1571,6 +1655,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.motionSensor.motion.inactive()) } + }, + { + min_api_version = 19 } ) @@ -1697,6 +1784,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) } + }, + { + min_api_version = 19 } ) @@ -1723,6 +1813,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -1749,6 +1842,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) } + }, + { + min_api_version = 19 } ) @@ -1775,6 +1871,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) } + }, + { + min_api_version = 19 } ) @@ -1801,6 +1900,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) } + }, + { + min_api_version = 19 } ) @@ -1827,6 +1929,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -1853,6 +1958,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -1879,6 +1987,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -1905,6 +2016,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -1931,6 +2045,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -1957,6 +2074,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -1983,6 +2103,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.waterSensor.water.wet()) } + }, + { + min_api_version = 19 } ) @@ -2009,6 +2132,9 @@ test.register_message_test( direction = "send", message = mock_child_4:generate_test_message("main", capabilities.waterSensor.water.dry()) } + }, + { + min_api_version = 19 } ) @@ -2044,6 +2170,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -2070,6 +2199,9 @@ test.register_message_test( direction = "send", message = mock_child_5:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 70 })) } + }, + { + min_api_version = 19 } ) @@ -2097,6 +2229,9 @@ test.register_message_test( direction = "send", message = mock_child_5:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 400, unit = "lux" })) } + }, + { + min_api_version = 19 } ) @@ -2145,7 +2280,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -2200,7 +2336,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -2327,7 +2464,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -2370,7 +2508,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -2390,6 +2529,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -2406,7 +2548,10 @@ test.register_coroutine_test( base_parent:expect_device_create( prepare_metadata(base_parent, 1, "metering-switch") ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -2425,6 +2570,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -2441,7 +2589,10 @@ test.register_coroutine_test( base_parent:expect_device_create( prepare_metadata(base_parent, 3, "metering-dimmer") ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -2460,6 +2611,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -2476,7 +2630,10 @@ test.register_coroutine_test( base_parent:expect_device_create( prepare_metadata(base_parent, 4, "generic-sensor") ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -2495,6 +2652,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -2511,7 +2671,10 @@ test.register_coroutine_test( base_parent:expect_device_create( prepare_metadata(base_parent, 5, "generic-multi-sensor") ) - end + end, + { + min_api_version = 19 + } ) 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 165dab059e..6190872abd 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 @@ -47,7 +47,10 @@ test.register_coroutine_test( }) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 9cec4ecd4e..9a69692271 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 @@ -68,7 +68,10 @@ test.register_coroutine_test( }) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -121,7 +124,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -159,6 +163,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -197,6 +204,9 @@ test.register_message_test( {scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -238,6 +248,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -292,6 +305,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -321,6 +337,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -342,6 +361,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -358,7 +380,10 @@ test.register_coroutine_test( } ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -424,7 +449,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -491,7 +519,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -522,7 +553,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 920c6c5b2f..f80ebcbca4 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 @@ -54,7 +54,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -74,7 +78,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -94,7 +102,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -114,7 +126,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -134,7 +150,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -155,7 +175,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -175,7 +199,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 d44eb8f7cb..c88d4fbd88 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 @@ -46,7 +46,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -66,7 +70,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -86,7 +94,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -106,7 +118,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -126,7 +142,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 3436ccba26..a927de9f78 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 @@ -46,7 +46,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -66,7 +70,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -86,7 +94,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 61a62ef0d7..1da5aa3446 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 @@ -104,7 +104,10 @@ test.register_coroutine_test( { dst_channels = { 1 } } ) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -133,7 +136,10 @@ test.register_coroutine_test( { dst_channels = { 2 } } ) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -151,7 +157,10 @@ test.register_coroutine_test( { encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = { 3 } } ) )) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -177,7 +186,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -204,7 +214,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -228,7 +239,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -252,7 +264,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -277,6 +290,9 @@ test.register_message_test( direction = "send", message = mock_parent_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -301,6 +317,9 @@ test.register_message_test( direction = "send", message = mock_child_2_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -333,6 +352,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -365,6 +387,9 @@ test.register_message_test( { device_uuid = mock_parent_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -391,6 +416,9 @@ test.register_message_test( "main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' }) ) } + }, + { + min_api_version = 19 } ) @@ -430,7 +458,10 @@ test.register_coroutine_test( mock_parent_device, Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }, { dst_channels = { 1 } }) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -469,7 +500,10 @@ test.register_coroutine_test( mock_parent_device, Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }, { dst_channels = { 1 } }) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -508,7 +542,10 @@ test.register_coroutine_test( mock_parent_device, Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }, { dst_channels = { 2 } }) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -547,7 +584,10 @@ test.register_coroutine_test( mock_parent_device, Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }, { dst_channels = { 2 } }) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -597,7 +637,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 3ac1fda4b4..60f2e457ae 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 @@ -46,7 +46,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -66,7 +70,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -86,7 +94,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -106,7 +118,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -126,7 +142,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 cfe8dccde7..92d259df4b 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 @@ -59,7 +59,10 @@ test.register_coroutine_test( }) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -112,7 +115,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -150,6 +154,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -188,6 +195,9 @@ test.register_message_test( {scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -229,6 +239,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -283,6 +296,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -312,6 +328,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -333,6 +352,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -347,7 +369,10 @@ test.register_coroutine_test( } ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' }))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -413,7 +438,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -480,7 +508,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -511,7 +542,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 db28991f2d..88b7f06726 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 @@ -50,7 +50,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -70,7 +74,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -90,7 +98,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -110,7 +122,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -130,7 +146,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -154,7 +174,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -178,7 +202,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 d88d6c39b1..3addd1ec20 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 @@ -49,7 +49,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -69,7 +73,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -89,7 +97,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -109,7 +121,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -129,7 +145,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -149,7 +169,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -169,7 +193,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -190,7 +218,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -210,7 +242,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 f385e0be33..0d1668fdd1 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 @@ -49,7 +49,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -69,7 +73,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -89,7 +97,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -109,7 +121,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -129,7 +145,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -150,7 +170,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -170,7 +194,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -190,7 +218,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 ccaf9c7aec..80ba20105a 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 @@ -57,7 +57,10 @@ test.register_coroutine_test( ) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -94,6 +97,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -132,6 +138,9 @@ test.register_message_test( {scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -161,6 +170,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -182,6 +194,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -204,6 +219,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' })) } + }, + { + min_api_version = 19 } ) @@ -270,7 +288,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -337,7 +358,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -378,7 +402,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' })) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 c06f516249..bacd11020e 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 @@ -58,6 +58,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -87,6 +90,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -109,6 +115,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' })) } + }, + { + min_api_version = 19 } ) @@ -159,7 +168,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.switch.switch.on()) ) mock_device:expect_native_attr_handler_registration("switch", "switch") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -210,7 +222,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.switch.switch.off()) ) mock_device:expect_native_attr_handler_registration("switch", "switch") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -251,7 +266,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' })) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 331640b1be..8cacbcf26e 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 @@ -113,7 +113,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -162,7 +165,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -211,7 +217,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -236,6 +245,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -261,6 +273,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -286,6 +301,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -311,6 +329,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -351,6 +372,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -391,6 +415,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -418,7 +445,11 @@ do message = mock_parent_device:generate_test_message( "main", capabilities.energyMeter.energy({ value = energy, unit = "kWh" })) } + }, + { + min_api_version = 19 } + ) end @@ -448,7 +479,11 @@ do "main", capabilities.energyMeter.energy({ value = energy, unit = "kWh" }) ) } + }, + { + min_api_version = 19 } + ) end @@ -476,7 +511,11 @@ do message = mock_parent_device:generate_test_message( "main", capabilities.powerMeter.power({ value = power, unit = "W" })) } + }, + { + min_api_version = 19 } + ) end @@ -504,7 +543,11 @@ do message = mock_child_device:generate_test_message( "main", capabilities.powerMeter.power({ value = power, unit = "W" })) } + }, + { + min_api_version = 19 } + ) end @@ -535,7 +578,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -565,7 +611,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) do @@ -656,7 +705,11 @@ do ) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -669,7 +722,10 @@ test.register_coroutine_test( current_value = 0xFF }) }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 9bc1387f2b..efd75fe27c 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_wyfy_touch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_wyfy_touch.lua @@ -69,7 +69,10 @@ test.register_coroutine_test( Configuration:Set({parameter_number = 2, size = 1, configuration_value = 1}) )) mock_parent_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -94,6 +97,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -119,6 +125,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -144,6 +153,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -169,6 +181,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -199,6 +214,9 @@ test.register_message_test( direction = "send", message = mock_parent_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -229,6 +247,9 @@ test.register_message_test( direction = "send", message = mock_child_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -260,7 +281,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -290,7 +314,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) local function prepare_metadata(device, endpoint, profile) @@ -321,7 +348,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -337,7 +367,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 010607fc2d..3e1d6012e8 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 @@ -46,7 +46,10 @@ test.register_coroutine_test( }) )) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 abf41946cc..ef5c00c7e1 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 @@ -71,7 +71,10 @@ test.register_coroutine_test( }) )) mock_parent:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -119,7 +122,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -166,7 +172,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -190,6 +199,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -215,6 +227,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -239,6 +254,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -264,6 +282,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -303,6 +324,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -342,6 +366,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -381,6 +408,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -420,6 +450,9 @@ test.register_message_test( ) ) } + }, + { + min_api_version = 19 } ) @@ -441,6 +474,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -462,6 +498,9 @@ test.register_message_test( direction = "send", message = mock_child:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -491,6 +530,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -520,6 +562,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) 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 477886d76e..281b75d6a2 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 @@ -83,6 +83,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -119,6 +122,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -155,6 +161,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -191,6 +200,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -227,6 +239,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -263,6 +278,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -299,6 +317,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -336,6 +357,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -373,6 +397,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -410,6 +437,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -451,6 +481,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -491,6 +524,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -531,6 +567,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -571,6 +610,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -611,6 +653,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -651,6 +696,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -691,6 +739,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -731,6 +782,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -771,6 +825,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -811,6 +868,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -870,7 +930,10 @@ test.register_coroutine_test( ) test.socket.capability:__expect_send(mock_device:generate_test_message("switch1", capabilities.switch.switch.off())) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -947,7 +1010,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1025,7 +1089,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1060,7 +1125,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1095,7 +1161,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1130,7 +1197,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1165,7 +1233,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1200,7 +1269,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1235,7 +1305,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1270,7 +1341,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1305,7 +1377,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1340,7 +1413,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -1375,7 +1449,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 b3a18291e7..2d9f2a86a4 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 @@ -92,7 +92,10 @@ test.register_coroutine_test( Version:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -109,7 +112,10 @@ test.register_coroutine_test( SwitchBinary:Get({}, { dst_channels = { 1 } }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -138,6 +144,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -167,6 +176,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -204,6 +216,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -246,6 +261,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -292,6 +310,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -325,6 +346,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -354,6 +378,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -383,6 +410,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -411,7 +441,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}, { dst_channels = { 0 } }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -439,7 +472,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}, { dst_channels = { 0 } }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -467,7 +503,10 @@ test.register_coroutine_test( SwitchBinary:Get({}, { dst_channels = { 1 } }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -495,7 +534,10 @@ test.register_coroutine_test( SwitchBinary:Get({}, { dst_channels = { 1 } }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -528,7 +570,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}, { dst_channels = { 0 } }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -574,6 +619,9 @@ test.register_message_test( { device_uuid = mock_parent.id, capability_id = "switchLevel", capability_attr_id = "level" } } }, + }, + { + min_api_version = 19 } ) @@ -598,6 +646,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.up({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -622,6 +673,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.down({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -646,6 +700,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -670,6 +727,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.up_2x({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -694,6 +754,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.down_2x({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -718,6 +781,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.pushed_2x({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -742,6 +808,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.up_3x({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -766,6 +835,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.down_3x({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -790,6 +862,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.pushed_3x({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -814,6 +889,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.up_4x({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -838,6 +916,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.down_4x({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -862,6 +943,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.pushed_4x({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -886,6 +970,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.up_5x({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -910,6 +997,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.down_5x({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -934,6 +1024,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.pushed_5x({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -958,6 +1051,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.up_hold({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -982,6 +1078,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.down_hold({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -1006,6 +1105,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.button.button.held({state_change = true})) } + }, + { + min_api_version = 19 } ) @@ -1025,6 +1127,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -1050,7 +1155,10 @@ test.register_coroutine_test( } ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1075,7 +1183,10 @@ test.register_coroutine_test( } ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1111,7 +1222,10 @@ test.register_coroutine_test( }) }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1156,7 +1270,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -1172,7 +1289,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 c186f9d36b..f7a59d38b4 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 @@ -77,7 +77,11 @@ do }) ) test.wait_for_events() - end + end, + { + min_api_version = 19 + } + ) end 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 215cc0675d..a958fea2a6 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 @@ -70,6 +70,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -108,6 +111,9 @@ test.register_message_test( {scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -149,6 +155,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -203,6 +212,9 @@ test.register_message_test( Meter:Get({scale = Meter.scale.electric_meter.WATTS}) ) } + }, + { + min_api_version = 19 } ) @@ -232,6 +244,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -253,6 +268,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -319,7 +337,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -385,7 +406,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -415,7 +439,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 84502e9835..9fe7a588f3 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 @@ -80,6 +80,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -111,6 +114,9 @@ test.register_message_test( direction = "send", message = mock_child:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -142,6 +148,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -173,6 +182,9 @@ test.register_message_test( direction = "send", message = mock_child:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -204,6 +216,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -235,6 +250,9 @@ test.register_message_test( direction = "send", message = mock_child:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -266,6 +284,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -297,6 +318,9 @@ test.register_message_test( direction = "send", message = mock_child:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -334,6 +358,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -371,6 +398,9 @@ test.register_message_test( direction = "send", message = mock_child:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -408,6 +438,9 @@ test.register_message_test( direction = "send", message = mock_parent:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -445,6 +478,9 @@ test.register_message_test( direction = "send", message = mock_child:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -482,7 +518,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -519,7 +558,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -556,7 +598,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -593,7 +638,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -614,7 +662,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -635,7 +686,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 ac396439f6..6e616bcb38 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 @@ -72,7 +72,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -96,7 +99,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 effb5845b4..76e0031806 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch.lua @@ -76,6 +76,9 @@ test.register_message_test( { device_uuid = mock_switch_binary.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -100,6 +103,9 @@ test.register_message_test( { device_uuid = mock_switch_binary.id, capability_id = "switch", capability_attr_id = "switch" } } }, + }, + { + min_api_version = 19 } ) @@ -122,7 +128,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -147,7 +154,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -169,7 +177,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -194,7 +203,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -226,7 +236,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -257,7 +270,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -288,7 +304,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -319,7 +338,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 26a72eb4e6..a5c4c24a95 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 @@ -43,6 +43,9 @@ test.register_message_test( direction = "send", message = mock_switch:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -72,7 +75,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -105,7 +109,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 935e4b63e8..0882b0a253 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 @@ -55,6 +55,9 @@ test.register_message_test( { device_uuid = mock_switch.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -74,6 +77,9 @@ test.register_message_test( direction = "send", message = mock_switch:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -111,7 +117,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -152,7 +159,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 016cb56ef1..ffdbd50f8a 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 @@ -61,6 +61,9 @@ test.register_message_test( { device_uuid = mock_switch.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -80,6 +83,9 @@ test.register_message_test( direction = "send", message = mock_switch:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -109,7 +115,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -142,7 +149,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -152,7 +160,10 @@ test.register_coroutine_test( test.socket.capability:__queue_receive({mock_switch.id, { capability = "energyMeter", component = "main", command = "resetEnergyMeter", args = {}}}) test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( mock_switch, Meter:Reset({}))) test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( mock_switch, Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}))) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 c8cca92dfb..cb8d35a953 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 @@ -45,7 +45,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -70,7 +71,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) 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 edec7aefba..f377c003ca 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 @@ -66,6 +66,9 @@ test.register_message_test( { device_uuid = mock_switch.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -80,6 +83,9 @@ test.register_message_test( meter_value = 5}) )} }, + }, + { + min_api_version = 19 } ) @@ -109,7 +115,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -142,7 +149,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_aeotec_radiator_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_aeotec_radiator_thermostat.lua index 69034f9b5a..4df09a847a 100755 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_aeotec_radiator_thermostat.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_aeotec_radiator_thermostat.lua @@ -94,7 +94,8 @@ test.register_message_test( -- }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -111,6 +112,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -127,6 +131,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(1)) } + }, + { + min_api_version = 19 } ) @@ -155,6 +162,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -172,6 +182,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode({ value = "heat" })) } + }, + { + min_api_version = 19 } ) @@ -192,6 +205,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 21.5, unit = 'C' })) } + }, + { + min_api_version = 19 } ) @@ -217,7 +233,10 @@ test.register_coroutine_test( ThermostatMode:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -247,7 +266,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_ct100_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_ct100_thermostat.lua index 55a35dd2a9..cbc269824f 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_ct100_thermostat.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_ct100_thermostat.lua @@ -136,7 +136,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -162,7 +163,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 68, unit = "C"}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -187,7 +191,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({value = 68, unit = "C"}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -212,7 +219,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({value = 45}) )) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -268,6 +278,9 @@ test.register_message_test( ThermostatSetpoint:Get({setpoint_type = ThermostatSetpoint.setpoint_type.COOLING_1}) ) } + }, + { + min_api_version = 19 } ) @@ -324,6 +337,9 @@ test.register_message_test( ThermostatSetpoint:Get({setpoint_type = ThermostatSetpoint.setpoint_type.HEATING_1}) ) } + }, + { + min_api_version = 19 } ) @@ -386,7 +402,10 @@ test.register_coroutine_test( ThermostatOperatingState:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -452,7 +471,10 @@ test.register_coroutine_test( ThermostatOperatingState:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) -- these next two tests are based on actual messages from a real device @@ -475,7 +497,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 60.0, unit = 'F'}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -497,7 +522,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({value = 48}) )) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_fibaro_heat_controller.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_fibaro_heat_controller.lua index cac8b883e4..58d263cd6e 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_fibaro_heat_controller.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_fibaro_heat_controller.lua @@ -115,7 +115,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -172,7 +173,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -189,6 +191,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -209,6 +214,9 @@ test.register_message_test( direction = "send", message = mock_device_extended:generate_test_message("extraTemperatureSensor", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -225,6 +233,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(1)) } + }, + { + min_api_version = 19 } ) @@ -253,6 +264,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -270,6 +284,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode({ value = "heat" })) } + }, + { + min_api_version = 19 } ) @@ -290,6 +307,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 21.5, unit = 'C' })) } + }, + { + min_api_version = 19 } ) @@ -333,7 +353,10 @@ test.register_coroutine_test( Configuration:Get({parameter_number = 3}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -358,7 +381,10 @@ test.register_coroutine_test( ThermostatMode:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -388,7 +414,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_popp_radiator_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_popp_radiator_thermostat.lua index 3671cd4029..5aea3afd5a 100755 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_popp_radiator_thermostat.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_popp_radiator_thermostat.lua @@ -60,6 +60,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -76,6 +79,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(1)) } + }, + { + min_api_version = 19 } ) @@ -104,6 +110,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -124,6 +133,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 21.5, unit = 'C' })) } + }, + { + min_api_version = 19 } ) @@ -145,7 +157,10 @@ test.register_coroutine_test( ) test.mock_time.advance_time(200) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -186,7 +201,10 @@ test.register_coroutine_test( mock_device, ThermostatSetpoint:Get({setpoint_type = ThermostatSetpoint.setpoint_type.HEATING_1})) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_qubino_flush_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_qubino_flush_thermostat.lua index 2c9b04d82d..15ccb6ee9a 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_qubino_flush_thermostat.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_qubino_flush_thermostat.lua @@ -122,7 +122,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -144,7 +145,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -168,6 +170,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' })) } + }, + { + min_api_version = 19 } ) @@ -186,6 +191,9 @@ test.register_message_test( ) } } + }, + { + min_api_version = 19 } ) @@ -203,6 +211,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode({ value = "heat" })) } + }, + { + min_api_version = 19 } ) @@ -227,6 +238,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 21.5, unit = 'C' })) } + }, + { + min_api_version = 19 } ) @@ -251,6 +265,9 @@ test.register_message_test( direction = "send", message = mock_device_cooling:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 21.5, unit = 'C' })) } + }, + { + min_api_version = 19 } ) @@ -305,7 +322,10 @@ test.register_coroutine_test( Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -354,7 +374,10 @@ test.register_coroutine_test( Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -379,7 +402,10 @@ test.register_coroutine_test( ThermostatMode:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -409,7 +435,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -434,6 +463,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState.heating()) } + }, + { + min_api_version = 19 } ) @@ -453,6 +485,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) } + }, + { + min_api_version = 19 } ) @@ -480,6 +515,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } } } + }, + { + min_api_version = 19 } ) @@ -530,7 +568,10 @@ test.register_coroutine_test( Configuration:Get({ parameter_number = 59 }) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_stelpro_ki_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_stelpro_ki_thermostat.lua index 2c9c370bec..0222443eb7 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_stelpro_ki_thermostat.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_stelpro_ki_thermostat.lua @@ -66,7 +66,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -102,6 +105,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 33, unit = 'C'})) } + }, + { + min_api_version = 19 } ) @@ -138,6 +144,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 55, unit = 'F'})) } + }, + { + min_api_version = 19 } ) @@ -179,6 +188,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 30, unit = 'F'})) } + }, + { + min_api_version = 19 } ) @@ -220,6 +232,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 122, unit = 'F'})) } + }, + { + min_api_version = 19 } ) @@ -256,6 +271,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.freeze()) } + }, + { + min_api_version = 19 } ) @@ -292,6 +310,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) } + }, + { + min_api_version = 19 } ) @@ -307,6 +328,9 @@ test.register_message_test( energy_save_heat = true })) } } + }, + { + min_api_version = 19 } ) @@ -325,6 +349,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode({ value = "eco" })) } + }, + { + min_api_version = 19 } ) @@ -348,7 +375,10 @@ test.register_coroutine_test( ThermostatMode:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_thermostat_heating_battery.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_thermostat_heating_battery.lua index 9680419946..827dc87b57 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_thermostat_heating_battery.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_thermostat_heating_battery.lua @@ -102,7 +102,10 @@ test.register_coroutine_test( "doConfigure() should generate WakeUp:IntervalSet", function() do_initial_setup() - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -118,6 +121,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -147,7 +153,10 @@ test.register_coroutine_test( mock_device, WakeUp:IntervalGet({}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -206,7 +215,10 @@ test.register_coroutine_test( zw_test_utilities.zwave_test_build_send_command(mock_device, Clock:Set({hour=now.hour, minute=now.min, weekday=WEEK[now.wday]})) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -225,6 +237,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 25, unit = "C"})) }, + }, + { + min_api_version = 19 } ) @@ -254,6 +269,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 4, unit = "C"})) }, + }, + { + min_api_version = 19 } ) @@ -283,6 +301,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 28, unit = "C"})) }, + }, + { + min_api_version = 19 } ) @@ -309,7 +330,10 @@ test.register_coroutine_test( capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 50, unit = "F"}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -399,7 +423,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -510,7 +537,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -610,7 +640,10 @@ test.register_coroutine_test( {mock_device.id, zw_test_utilities.zwave_test_build_receive_command(WakeUp:Notification({}))} ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -693,7 +726,10 @@ test.register_coroutine_test( }) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -732,7 +768,10 @@ test.register_coroutine_test( mock_device, WakeUp:IntervalGet({}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -769,7 +808,10 @@ test.register_coroutine_test( WakeUp:IntervalGet({}) )) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_zwave_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_zwave_thermostat.lua index ea14555a0a..f2a96b5c26 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_zwave_thermostat.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_zwave_thermostat.lua @@ -143,7 +143,8 @@ test.register_message_test( table.unpack(refresh_commands) }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -161,7 +162,8 @@ test.register_message_test( table.unpack(refresh_commands) }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -178,6 +180,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(99)) } + }, + { + min_api_version = 19 } ) @@ -194,6 +199,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.battery.battery(1)) } + }, + { + min_api_version = 19 } ) @@ -216,6 +224,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({ "off", "heat", "cool", "auto" }, {visibility={displayed=false}})) } + }, + { + min_api_version = 19 } ) @@ -237,6 +248,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatFanMode.supportedThermostatFanModes({ "on", "auto", "circulate" }, {visibility={displayed=false}})) } + }, + { + min_api_version = 19 } ) @@ -265,6 +279,9 @@ test.register_message_test( { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } } } + }, + { + min_api_version = 19 } ) @@ -285,6 +302,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 22 })) } + }, + { + min_api_version = 19 } ) @@ -302,6 +322,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode({ value = "heat" })) } + }, + { + min_api_version = 19 } ) @@ -319,6 +342,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatFanMode.thermostatFanMode({ value = "circulate" })) } + }, + { + min_api_version = 19 } ) @@ -339,6 +365,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 21.5, unit = 'C' })) } + }, + { + min_api_version = 19 } ) @@ -359,6 +388,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 68, unit = 'F' })) } + }, + { + min_api_version = 19 } ) @@ -384,6 +416,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.thermostatOperatingState.thermostatOperatingState.heating()) } + }, + { + min_api_version = 19 } ) @@ -409,7 +444,10 @@ test.register_coroutine_test( ThermostatFanMode:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -434,7 +472,10 @@ test.register_coroutine_test( ThermostatFanMode:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -459,7 +500,10 @@ test.register_coroutine_test( ThermostatFanMode:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -484,7 +528,10 @@ test.register_coroutine_test( ThermostatMode:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -509,7 +556,10 @@ test.register_coroutine_test( ThermostatMode:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -539,7 +589,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -590,7 +643,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -640,7 +696,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -692,7 +751,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-valve/src/test/test_inverse.valve.lua b/drivers/SmartThings/zwave-valve/src/test/test_inverse.valve.lua index 68c563061b..51314225e3 100644 --- a/drivers/SmartThings/zwave-valve/src/test/test_inverse.valve.lua +++ b/drivers/SmartThings/zwave-valve/src/test/test_inverse.valve.lua @@ -47,6 +47,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.closed()) } + }, + { + min_api_version = 19 } ) @@ -65,6 +68,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.open()) } + }, + { + min_api_version = 19 } ) @@ -83,6 +89,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.open()) } + }, + { + min_api_version = 19 } ) @@ -101,6 +110,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.closed()) } + }, + { + min_api_version = 19 } ) @@ -131,7 +143,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -161,7 +176,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-valve/src/test/test_zwave_valve.lua b/drivers/SmartThings/zwave-valve/src/test/test_zwave_valve.lua index efa0c42599..add1f0b77d 100644 --- a/drivers/SmartThings/zwave-valve/src/test/test_zwave_valve.lua +++ b/drivers/SmartThings/zwave-valve/src/test/test_zwave_valve.lua @@ -69,6 +69,9 @@ test.register_message_test( direction = "send", message = mock_valve_binary:generate_test_message("main", capabilities.valve.valve.open()) } + }, + { + min_api_version = 19 } ) @@ -85,6 +88,9 @@ test.register_message_test( direction = "send", message = mock_valve_binary:generate_test_message("main", capabilities.valve.valve.closed()) } + }, + { + min_api_version = 19 } ) @@ -114,7 +120,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -144,7 +151,8 @@ test.register_message_test( }, }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -175,7 +183,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -205,7 +216,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -235,7 +249,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -265,7 +282,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-virtual-momentary-switch/src/test/test_zwave_virtual_momentary_switch.lua b/drivers/SmartThings/zwave-virtual-momentary-switch/src/test/test_zwave_virtual_momentary_switch.lua index dffe6b2f38..9f46989f3e 100644 --- a/drivers/SmartThings/zwave-virtual-momentary-switch/src/test/test_zwave_virtual_momentary_switch.lua +++ b/drivers/SmartThings/zwave-virtual-momentary-switch/src/test/test_zwave_virtual_momentary_switch.lua @@ -65,6 +65,9 @@ test.register_message_test( direction = "send", message = mock_momentary_switch:generate_test_message("main", capabilities.switch.switch.on()) } + }, + { + min_api_version = 19 } ) @@ -81,6 +84,9 @@ test.register_message_test( direction = "send", message = mock_momentary_switch:generate_test_message("main", capabilities.switch.switch.off()) } + }, + { + min_api_version = 19 } ) @@ -106,7 +112,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -128,7 +135,8 @@ test.register_message_test( } }, { - inner_block_ordering = "relaxed" + inner_block_ordering = "relaxed", + min_api_version = 19 } ) @@ -176,7 +184,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) @@ -223,7 +234,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -250,7 +264,10 @@ test.register_coroutine_test( SwitchBinary:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 c53e314f3a..651f6d9281 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 @@ -66,6 +66,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_roller_shutter:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) } + }, + { + min_api_version = 19 } ) @@ -89,6 +92,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_roller_shutter:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50)) } + }, + { + min_api_version = 19 } ) @@ -112,6 +118,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_roller_shutter:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) } + }, + { + min_api_version = 19 } ) @@ -142,6 +151,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_roller_shutter:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) } + }, + { + min_api_version = 19 } ) @@ -172,6 +184,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_roller_shutter:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50)) } + }, + { + min_api_version = 19 } ) @@ -202,6 +217,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_roller_shutter:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) } + }, + { + min_api_version = 19 } ) @@ -233,7 +251,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -264,7 +285,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -283,7 +307,10 @@ test.register_coroutine_test( SwitchMultilevel:StopLevelChange({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -314,7 +341,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -345,7 +375,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -370,6 +403,9 @@ test.register_message_test( direction = "send", message = mock_fibaro_roller_shutter_venetian:generate_test_message("venetianBlind", capabilities.windowShadeLevel.shadeLevel(50)) } + }, + { + min_api_version = 19 } ) @@ -393,7 +429,10 @@ test.register_coroutine_test( test.socket.zwave:__queue_receive({mock_fibaro_roller_shutter.id, Configuration:Report({ parameter_number = 150, configuration_value = 1 }) }) test.wait_for_events() assert(mock_fibaro_roller_shutter:get_field("calibration") == "done", "Calibration should be done") - end + end, + { + min_api_version = 19 + } ) do @@ -415,7 +454,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -438,7 +481,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -461,7 +508,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -484,7 +535,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -507,7 +562,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -530,7 +589,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -553,7 +616,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -576,7 +643,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -599,7 +670,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -622,7 +697,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -645,7 +724,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -668,7 +751,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -691,7 +778,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -714,7 +805,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -737,7 +832,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end test.register_coroutine_test( @@ -750,7 +849,10 @@ test.register_coroutine_test( ) }) mock_fibaro_roller_shutter_venetian:expect_metadata_update({ profile = "fibaro-roller-shutter" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -763,7 +865,10 @@ test.register_coroutine_test( ) }) mock_fibaro_roller_shutter_venetian:expect_metadata_update({ profile = "fibaro-roller-shutter-venetian" }) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 d9bd11e01f..0449cc759b 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 @@ -74,6 +74,9 @@ test.register_message_test( direction = "send", message = mock_qubino_flush_shutter:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) } + }, + { + min_api_version = 19 } ) @@ -104,6 +107,9 @@ test.register_message_test( direction = "send", message = mock_qubino_flush_shutter:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50)) } + }, + { + min_api_version = 19 } ) @@ -134,6 +140,9 @@ test.register_message_test( direction = "send", message = mock_qubino_flush_shutter:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) } + }, + { + min_api_version = 19 } ) @@ -164,7 +173,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -194,7 +206,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -213,7 +228,10 @@ test.register_coroutine_test( SwitchMultilevel:StopLevelChange({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -244,7 +262,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -274,7 +295,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -299,6 +323,9 @@ test.register_message_test( direction = "send", message = mock_qubino_flush_shutter_venetian:generate_test_message("venetianBlind", capabilities.windowShadeLevel.shadeLevel(50)) } + }, + { + min_api_version = 19 } ) @@ -329,7 +356,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}, {encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = {2}}) ) ) - end + end, + { + min_api_version = 19 + } ) do @@ -351,7 +381,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -374,7 +408,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -397,7 +435,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -420,7 +462,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -443,7 +489,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -466,7 +516,11 @@ do }) ) ) - end + end, + { + min_api_version = 19 + } + ) end @@ -507,7 +561,10 @@ test.register_coroutine_test( capabilities.windowShade.supportedWindowShadeCommands({"open", "close", "pause"}, { visibility = { displayed = false }}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -523,7 +580,10 @@ test.register_coroutine_test( }) }) mock_qubino_flush_shutter:expect_metadata_update({ profile = "qubino-flush-shutter-venetian" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -539,7 +599,10 @@ test.register_coroutine_test( }) }) mock_qubino_flush_shutter:expect_metadata_update({ profile = "qubino-flush-shutter" }) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -558,7 +621,10 @@ test.register_coroutine_test( local actualCachedEvent = utils.stringify_table(mock_qubino_flush_shutter.transient_store.blinds_last_command) assert(expectedCachedEvent == actualCachedEvent, "driver should cache 'opening' event when targetLevel > currentLevel") assert(targetValue == mock_qubino_flush_shutter.transient_store.shade_target, "driver should chache correct level value") - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -585,7 +651,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_qubino_flush_shutter:generate_test_message("main", capabilities.powerMeter.power({value = 10, unit = "W"})) ) - end + end, + { + min_api_version = 19 + } ) @@ -615,7 +684,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_qubino_flush_shutter:generate_test_message("main", capabilities.powerMeter.power({value = 0, unit = "W"})) ) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -639,6 +711,9 @@ test.register_message_test( direction = "send", message = mock_qubino_flush_shutter:generate_test_message("main", capabilities.energyMeter.energy({value = 50, unit = "kWh"})) } + }, + { + min_api_version = 19 } ) @@ -662,7 +737,10 @@ test.register_coroutine_test( Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) ) ) - end + end, + { + min_api_version = 19 + } ) do @@ -692,7 +770,11 @@ do Configuration:Get({ parameter_number = 71 }) ) ) - end + end, + { + min_api_version = 19 + } + ) end 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 089fc29bac..5337d9d22b 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 @@ -46,7 +46,10 @@ test.register_coroutine_test( Basic:Set({ value = 0x00 }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -64,7 +67,10 @@ test.register_coroutine_test( Basic:Set({ value = 0xFF }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -94,7 +100,10 @@ test.register_coroutine_test( Basic:Set({ value = 0x00 }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -130,7 +139,10 @@ test.register_coroutine_test( Basic:Set({ value = 0xFF }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -156,7 +168,10 @@ test.register_coroutine_test( Basic:Set({ value = 0xFF }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -182,7 +197,10 @@ test.register_coroutine_test( Basic:Set({ value = 0x00 }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -220,7 +238,10 @@ test.register_coroutine_test( Basic:Set({ value = 0xFF }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -264,7 +285,10 @@ test.register_coroutine_test( Basic:Set({ value = 0x00 }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -275,7 +299,10 @@ test.register_coroutine_test( test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command(mock_window_button, Configuration:Set({parameter_number = 80, size = 1, configuration_value = 1}))) test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command(mock_window_button, Configuration:Set({parameter_number = 85, size = 1, configuration_value = 1}))) mock_window_button:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 19 + } ) test.register_message_test( @@ -305,6 +332,9 @@ test.register_message_test( Basic:Get({}) ) }, + }, + { + min_api_version = 19 } ) @@ -317,7 +347,10 @@ test.register_coroutine_test( capabilities.statelessCurtainPowerButton.availableCurtainPowerButtons({"open", "close", "pause"}, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -334,7 +367,10 @@ test.register_coroutine_test( mock_window_button, Configuration:Set({parameter_number = 35, size = 1, configuration_value = 100}) )) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 8913a42e53..909dcda0c6 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 @@ -75,7 +75,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -102,7 +105,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -139,7 +145,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -166,7 +175,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -203,7 +215,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -230,7 +245,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -269,7 +287,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -296,7 +317,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -324,7 +348,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -365,7 +392,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -392,7 +422,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -433,7 +466,10 @@ test.register_coroutine_test( }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -472,7 +508,10 @@ test.register_coroutine_test( mock_blind_v3, Configuration:Set({parameter_number = 6, size = 1, configuration_value = 50}) )) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -496,7 +535,10 @@ test.register_coroutine_test( SwitchMultilevel:Set({ value = 0 }) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -520,7 +562,10 @@ test.register_coroutine_test( SwitchMultilevel:Set({ value = 0 }) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 843b26e407..86f847fd45 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 @@ -63,7 +63,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() 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 be25647509..af86878d95 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 @@ -79,6 +79,9 @@ test.register_message_test( direction = "send", message = mock_window_shade_basic:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) } + }, + { + min_api_version = 19 } ) @@ -102,6 +105,9 @@ test.register_message_test( direction = "send", message = mock_window_shade_basic:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50)) } + }, + { + min_api_version = 19 } ) @@ -125,6 +131,9 @@ test.register_message_test( direction = "send", message = mock_window_shade_basic:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) } + }, + { + min_api_version = 19 } ) @@ -155,6 +164,9 @@ test.register_message_test( direction = "send", message = mock_window_shade_switch_multilevel:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) } + }, + { + min_api_version = 19 } ) @@ -185,6 +197,9 @@ test.register_message_test( direction = "send", message = mock_window_shade_switch_multilevel:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50)) } + }, + { + min_api_version = 19 } ) @@ -215,6 +230,9 @@ test.register_message_test( direction = "send", message = mock_window_shade_switch_multilevel:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) } + }, + { + min_api_version = 19 } ) @@ -246,7 +264,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -277,7 +298,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -296,7 +320,10 @@ test.register_coroutine_test( SwitchMultilevel:StopLevelChange({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -327,7 +354,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -358,7 +388,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -384,7 +417,10 @@ test.register_coroutine_test( ) test.socket.capability:__expect_send(mock_window_shade_switch_multilevel:generate_test_message("main", capabilities.windowShade.windowShade.open())) test.socket.capability:__expect_send(mock_window_shade_switch_multilevel:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -410,7 +446,10 @@ test.register_coroutine_test( ) test.socket.capability:__expect_send(mock_window_shade_switch_multilevel:generate_test_message("main", capabilities.windowShade.windowShade.closed())) test.socket.capability:__expect_send(mock_window_shade_switch_multilevel:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0))) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -449,7 +488,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) @@ -489,7 +531,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -528,7 +573,10 @@ test.register_coroutine_test( SwitchMultilevel:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -555,7 +603,10 @@ test.register_coroutine_test( Basic:Get({}) ) ) - end + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( @@ -567,7 +618,10 @@ test.register_coroutine_test( {"open", "close", "pause"}, { visibility = { displayed = false } } )) ) - end + end, + { + min_api_version = 19 + } ) test.run_registered_tests() From 5f57d3a7fd72bd285eb1f824afac03e59f783257 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Fri, 6 Mar 2026 10:54:30 -0600 Subject: [PATCH 146/538] Remove keying off client clusters in matter-camera --- .../sub_drivers/camera/camera_utils/device_configuration.lua | 3 +-- .../SmartThings/matter-switch/src/test/test_matter_camera.lua | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) 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 index 4f467cbd07..f709d25420 100644 --- 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 @@ -110,8 +110,7 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr 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 + elseif ep_cluster.cluster_id == clusters.WebRTCTransportProvider.ID and has_server_cluster_type(ep_cluster) then table.insert(main_component_capabilities, capabilities.webrtc.ID) end end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 81a366624c..ed856bd5da 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -66,10 +66,6 @@ local mock_device = test.mock_device.build_test_matter_device({ cluster_id = clusters.WebRTCTransportProvider.ID, cluster_type = "SERVER" }, - { - cluster_id = clusters.WebRTCTransportRequestor.ID, - cluster_type = "CLIENT" - }, { cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER" From 7963cfe24944e6bc4a077fff83ae1d5829a75473 Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon, 9 Mar 2026 12:16:16 -0500 Subject: [PATCH 147/538] Matter Camera: Support additional setStream attributes (#2786) The camera sub-driver lacks handling for the resolution, type, label, and viewport aguments of the videoStreamSettings.setStream command. This change checks if any of these attributes have changed and deallocates the stream before reallocating a new stream with the updated parameters. Additionally, the viewport aspect of videoStreamSettings, which was not previously being set correctly, is now set based on the DPTZStreams attribute of the CAVSULM cluster. --- .../camera_handlers/attribute_handlers.lua | 94 ++- .../camera_handlers/capability_handlers.lua | 148 +++- .../camera/camera_utils/fields.lua | 16 +- .../sub_drivers/camera/camera_utils/utils.lua | 3 +- .../src/sub_drivers/camera/init.lua | 5 +- .../src/test/test_matter_camera.lua | 713 +++++++++++++++++- 6 files changed, 937 insertions(+), 42 deletions(-) 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 index df99d50f94..140ba6d313 100644 --- 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 @@ -182,14 +182,35 @@ end function CameraAttributeHandlers.allocated_video_streams_handler(driver, device, ib, response) if not ib.data.elements then return end + + local dptz_viewports = device:get_field(camera_fields.DPTZ_VIEWPORTS) or {} local streams = {} + + local previous_streams = device:get_latest_state( + camera_fields.profile_components.main, + capabilities.videoStreamSettings.ID, + capabilities.videoStreamSettings.videoStreams.NAME + ) or {} + + local previous_stream_labels = {} + + for _, stream in pairs(previous_streams) do + previous_stream_labels[stream.streamId] = stream.data.label + end + for i, v in ipairs(ib.data.elements) do local stream = v.elements + local stream_id = stream.video_stream_id.value + + -- Use label from existing capability state, if available + local capability_label = previous_stream_labels[stream_id] + local video_stream = { - streamId = stream.video_stream_id.value, + streamId = stream_id, data = { - label = "Stream " .. i, - type = stream.stream_usage.value == clusters.Global.types.StreamUsageEnum.LIVE_VIEW and "liveStream" or "clipRecording", + label = capability_label or "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, @@ -197,21 +218,31 @@ function CameraAttributeHandlers.allocated_video_streams_handler(driver, device, } } } - local viewport = device:get_field(camera_fields.VIEWPORT) - if viewport then - video_stream.data.viewport = viewport + + if dptz_viewports[stream_id] ~= nil then + video_stream.data.viewport = dptz_viewports[stream_id] + else + video_stream.data.viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { + x = stream.min_resolution.elements.width.value, + y = stream.min_resolution.elements.height.value + } + } end - if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.WATERMARK) then + + 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 + 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 + + device:emit_event_for_endpoint(ib, capabilities.videoStreamSettings.videoStreams(streams)) end function CameraAttributeHandlers.viewport_handler(driver, device, ib, response) @@ -221,6 +252,45 @@ function CameraAttributeHandlers.viewport_handler(driver, device, ib, response) })) end +function CameraAttributeHandlers.dptz_streams_handler(driver, device, ib, response) + if not ib.data.elements then return end + + local dptz_viewports = {} + for _, v in ipairs(ib.data.elements) do + local dptz_struct = v.elements + local stream_id = dptz_struct.video_stream_id.value + local viewport = dptz_struct.viewport.elements + + dptz_viewports[stream_id] = { + upperLeftVertex = { x = viewport.x1.value, y = viewport.y1.value }, + lowerRightVertex = { x = viewport.x2.value, y = viewport.y2.value } + } + end + + device:set_field(camera_fields.DPTZ_VIEWPORTS, dptz_viewports) + + local current_streams = device:get_latest_state( + camera_fields.profile_components.main, + capabilities.videoStreamSettings.ID, + capabilities.videoStreamSettings.videoStreams.NAME + ) or {} + local updated_streams = {} + for _, stream in pairs(current_streams) do + local updated_stream = { + streamId = stream.streamId, + data = stream.data + } + if dptz_viewports[stream.streamId] ~= nil then + updated_stream.data.viewport = dptz_viewports[stream.streamId] + end + table.insert(updated_streams, updated_stream) + end + + if #updated_streams > 0 then + device:emit_event_for_endpoint(ib, capabilities.videoStreamSettings.videoStreams(updated_streams)) + end +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) @@ -399,4 +469,4 @@ function CameraAttributeHandlers.camera_av_stream_management_attribute_list_hand camera_cfg.match_profile(device, status_light_enabled_present, status_light_brightness_present) end -return CameraAttributeHandlers \ No newline at end of file +return CameraAttributeHandlers 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 index 09134a9757..443c83956b 100644 --- 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 @@ -336,17 +336,153 @@ function CameraCapabilityHandlers.handle_set_selected_sound(driver, device, cmd) device:send(clusters.Chime.attributes.SelectedChime:write(device, endpoint_id, cmd.args.id)) end +local function update_viewport_or_label(device, current_streams, streamId, label, viewport) + current_streams = current_streams or {} + for _, stream in ipairs(current_streams) do + if stream.streamId == streamId then + if label ~= nil then stream.data.label = label end + if viewport ~= nil then stream.data.viewport = viewport end + break + end + end + local endpoint_id = device:component_to_endpoint() + device:emit_event_for_endpoint(endpoint_id, capabilities.videoStreamSettings.videoStreams(current_streams)) +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" + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, + clusters.CameraAvStreamManagement.types.Feature.WATERMARK) then + if cmd.args.watermark ~= nil then + watermark_enabled = cmd.args.watermark == "enabled" + end 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" + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, + clusters.CameraAvStreamManagement.types.Feature.ON_SCREEN_DISPLAY) then + if cmd.args.onScreenDisplay ~= nil then + on_screen_display_enabled = cmd.args.onScreenDisplay == "enabled" + end end - device:send(clusters.CameraAvStreamManagement.server.commands.VideoStreamModify(device, endpoint_id, - cmd.args.streamId, watermark_enabled, on_screen_display_enabled + + local current_streams = device:get_latest_state( + camera_fields.profile_components.main, + capabilities.videoStreamSettings.ID, + capabilities.videoStreamSettings.videoStreams.NAME + ) or {} + local current_stream + for _, stream in pairs(current_streams) do + if stream.streamId == cmd.args.streamId then + current_stream = stream.data + break + end + end + + local needs_reallocation = false + if current_stream ~= nil and current_stream.type ~= cmd.args.type then + needs_reallocation = true + elseif current_stream ~= nil and cmd.args.resolution ~= nil then + if current_stream.resolution.width ~= cmd.args.resolution.width or + current_stream.resolution.height ~= cmd.args.resolution.height or + current_stream.resolution.fps ~= cmd.args.resolution.fps then + needs_reallocation = true + end + elseif current_stream == nil and (cmd.args.type ~= nil or cmd.args.resolution ~= nil) then + needs_reallocation = true + end + + local viewport_changed = false + if cmd.args.viewport ~= nil and + camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, + clusters.CameraAvSettingsUserLevelManagement.types.Feature.DIGITALPTZ) then + if current_stream ~= nil and current_stream.viewport ~= nil then + if current_stream.viewport.upperLeftVertex.x ~= cmd.args.viewport.upperLeftVertex.x or + current_stream.viewport.upperLeftVertex.y ~= cmd.args.viewport.upperLeftVertex.y or + current_stream.viewport.lowerRightVertex.x ~= cmd.args.viewport.lowerRightVertex.x or + current_stream.viewport.lowerRightVertex.y ~= cmd.args.viewport.lowerRightVertex.y then + viewport_changed = true + end + elseif current_stream == nil or current_stream.viewport == nil then + viewport_changed = true + end + + if viewport_changed then + device:send(clusters.CameraAvSettingsUserLevelManagement.server.commands.DPTZSetViewport(device, endpoint_id, + cmd.args.streamId, + clusters.Global.types.ViewportStruct({ + x1 = cmd.args.viewport.upperLeftVertex.x, + x2 = cmd.args.viewport.lowerRightVertex.x, + y1 = cmd.args.viewport.upperLeftVertex.y, + y2 = cmd.args.viewport.lowerRightVertex.y + }) + )) + end + end + + local label_changed = cmd.args.label ~= nil and cmd.args.label ~= current_stream.label + + if viewport_changed or label_changed then + update_viewport_or_label(device, current_streams, cmd.args.streamId, cmd.args.label, cmd.args.viewport) + end + + if not needs_reallocation then + local watermark_changed = watermark_enabled ~= nil and current_stream.watermark ~= nil and + ((watermark_enabled and current_stream.watermark == "disabled") or + (not watermark_enabled and current_stream.watermark == "enabled")) + local on_screen_display_changed = on_screen_display_enabled ~= nil and current_stream.onScreenDisplay ~= nil and + ((on_screen_display_enabled and current_stream.onScreenDisplay == "disabled") or + (not on_screen_display_enabled and current_stream.onScreenDisplay == "enabled")) + if watermark_changed or on_screen_display_changed then + device:send(clusters.CameraAvStreamManagement.server.commands.VideoStreamModify( + device, endpoint_id, cmd.args.streamId, watermark_enabled, on_screen_display_enabled + )) + end + return + end + + device:send(clusters.CameraAvStreamManagement.server.commands.VideoStreamDeallocate( + device, endpoint_id, cmd.args.streamId + )) + + local stream_usage = cmd.args.type == "liveStream" and + clusters.Global.types.StreamUsageEnum.LIVE_VIEW or clusters.Global.types.StreamUsageEnum.RECORDING + + local min_resolution, max_resolution + if cmd.args.resolution ~= nil then + min_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({ + width = cmd.args.resolution.width, + height = cmd.args.resolution.height + }) + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({ + width = cmd.args.resolution.width, + height = cmd.args.resolution.height + }) + end + + if watermark_enabled == nil then + watermark_enabled = camera_fields.video_stream_defaults.watermark_enabled + end + + if on_screen_display_enabled == nil then + on_screen_display_enabled = camera_fields.video_stream_defaults.on_screen_display_enabled + end + + -- Use the same resolution (if available) for MinResolution and MaxResolution to force the server to allocate the + -- stream with the desired resolution. + device:send(clusters.CameraAvStreamManagement.server.commands.VideoStreamAllocate(device, endpoint_id, + stream_usage, + camera_fields.video_stream_defaults.codec, + camera_fields.video_stream_defaults.min_frame_rate, + math.min(device:get_field(camera_fields.MAX_FRAMES_PER_SECOND) or camera_fields.video_stream_defaults.max_frame_rate, + camera_fields.video_stream_defaults.max_frame_rate), + min_resolution or device:get_field(camera_fields.MIN_RESOLUTION) or camera_fields.video_stream_defaults.min_resolution, + max_resolution or device:get_field(camera_fields.MAX_RESOLUTION) or camera_fields.video_stream_defaults.max_resolution, + camera_fields.video_stream_defaults.min_bitrate, + camera_fields.video_stream_defaults.max_bitrate, + camera_fields.video_stream_defaults.key_frame_interval, + watermark_enabled, + on_screen_display_enabled )) end 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 index 7598b89893..000008fa51 100644 --- 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 @@ -13,7 +13,7 @@ CameraFields.SUPPORTED_RESOLUTIONS = "__supported_resolutions" CameraFields.MAX_RESOLUTION = "__max_resolution" CameraFields.MIN_RESOLUTION = "__min_resolution" CameraFields.TRIGGERED_ZONES = "__triggered_zones" -CameraFields.VIEWPORT = "__viewport" +CameraFields.DPTZ_VIEWPORTS = "__dptz_viewports" CameraFields.PAN_IDX = "PAN" CameraFields.TILT_IDX = "TILT" @@ -47,4 +47,18 @@ CameraFields.ABS_ZOOM_MIN = 1 CameraFields.ABS_VOL_MAX = 254.0 CameraFields.ABS_VOL_MIN = 0.0 +-- Define defaults for allocating new streams. Note that these are the same values use by the hub. +CameraFields.video_stream_defaults = { + codec = clusters.CameraAvStreamManagement.types.VideoCodecEnum.H264, + min_frame_rate = 30, + max_frame_rate = 60, + min_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 320, height = 240}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1920, height = 1080}), + min_bitrate = 10000, + max_bitrate = 2000000, + key_frame_interval = 4000, + watermark_enabled = false, + on_screen_display_enabled = false +} + 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 index 1caa9737bb..12341f493e 100644 --- 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 @@ -253,7 +253,8 @@ function CameraUtils.subscribe(device) clusters.CameraAvStreamManagement.attributes.RateDistortionTradeOffPoints, clusters.CameraAvStreamManagement.attributes.MaxEncodedPixelRate, clusters.CameraAvStreamManagement.attributes.VideoSensorParams, - clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams + clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams, + clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams }, [capabilities.zoneManagement.ID] = { clusters.ZoneManagement.attributes.MaxZones, diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index f13589ff41..179ed54742 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -108,14 +108,15 @@ local camera_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.CameraAvSettingsUserLevelManagement.attributes.TiltMin.ID] = attribute_handlers.pt_range_handler_factory(capabilities.mechanicalPanTiltZoom.tiltRange, camera_fields.pt_range_fields[camera_fields.TILT_IDX].min), + [clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams.ID] = attribute_handlers.dptz_streams_handler }, [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.ZoneManagement.attributes.Sensitivity.ID] = attribute_handlers.sensitivity_handler }, [clusters.Chime.ID] = { [clusters.Chime.attributes.InstalledChimeSounds.ID] = attribute_handlers.installed_chime_sounds_handler, diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index ed856bd5da..2eee0b778e 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -46,7 +46,8 @@ local mock_device = test.mock_device.build_test_matter_device({ }, { cluster_id = clusters.CameraAvSettingsUserLevelManagement.ID, - feature_map = clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN | + feature_map = clusters.CameraAvSettingsUserLevelManagement.types.Feature.DIGITALPTZ | + clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN | clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_TILT | clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_ZOOM | clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PRESETS, @@ -201,6 +202,7 @@ local additional_subscribed_attributes = { clusters.CameraAvSettingsUserLevelManagement.attributes.PanMin, clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMax, clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMin, + clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams, clusters.Chime.attributes.InstalledChimeSounds, clusters.Chime.attributes.SelectedChime, clusters.ZoneManagement.attributes.MaxZones, @@ -1967,38 +1969,117 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "Stream management commands should send the appropriate commands", + "setStream with label and viewport changes should emit capability event", function() update_device_profile() test.wait_for_events() + -- Set up an existing stream + 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 = 3, + 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 = 1920, height = 1080}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1920, height = 1080}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = false, + reference_count = 0 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 3, + data = { + label = "Stream 1", + type = "liveStream", + resolution = { + width = 1920, + height = 1080, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 1920, y = 1080 } + }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.wait_for_events() + -- Change label and viewport only test.socket.capability:__queue_receive({ mock_device.id, - { capability = "videoStreamSettings", component = "main", command = "setStream", args = { + { + 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" + "liveStream", -- type + "My Stream", -- label + { width = 1920, height = 1080, fps = 30 }, -- resolution + { upperLeftVertex = {x = 100, y = 100}, lowerRightVertex = {x = 1820, y = 980} }, -- viewport + "disabled", -- watermark + "disabled" -- onScreenDisplay }} }) + -- Should send DPTZSetViewport command test.socket.matter:__expect_send({ - mock_device.id, clusters.CameraAvStreamManagement.server.commands.VideoStreamModify(mock_device, CAMERA_EP, - 3, true, false + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.DPTZSetViewport(mock_device, CAMERA_EP, + 3, + clusters.Global.types.ViewportStruct({ + x1 = 100, + x2 = 1820, + y1 = 100, + y2 = 980 + }) ) }) + -- Should emit updated capability directly, no stream reallocation + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 3, + data = { + label = "My Stream", + type = "liveStream", + resolution = { + width = 1920, + height = 1080, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 100, y = 100 }, + lowerRightVertex = { x = 1820, y = 980 } + }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) end, { - min_api_version = 19 + min_api_version = 19 } ) test.register_coroutine_test( - "Stream management setStream command should modify an existing stream", + "setStream with only watermark/OSD changes should use VideoStreamModify", function() update_device_profile() test.wait_for_events() + -- Set up an existing stream test.socket.matter:__queue_receive({ mock_device.id, clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams:build_test_report_data( @@ -2033,22 +2114,29 @@ test.register_coroutine_test( height = 360, fps = 30 }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 640, y = 360 } + }, watermark = "enabled", onScreenDisplay = "disabled" } } })) ) + test.wait_for_events() + -- Change watermark and OSD only test.socket.capability:__queue_receive({ mock_device.id, - { capability = "videoStreamSettings", component = "main", command = "setStream", args = { + { + 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" + "liveStream", -- type + "Stream 1", -- label + { width = 640, height = 360, fps = 30 }, -- resolution + { upperLeftVertex = {x = 0, y = 0}, lowerRightVertex = {x = 640, y = 360} }, -- viewport + "disabled", -- watermark + "enabled" -- onScreenDisplay }} }) test.socket.matter:__expect_send({ @@ -2062,6 +2150,591 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "setStream with only label change should emit capability event", + function() + update_device_profile() + test.wait_for_events() + -- Set up existing stream + 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 = 2, + stream_usage = clusters.Global.types.StreamUsageEnum.RECORDING, + video_codec = clusters.CameraAvStreamManagement.types.VideoCodecEnum.H264, + min_frame_rate = 15, + max_frame_rate = 30, + min_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1280, height = 720}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1280, height = 720}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = false, + reference_count = 0 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 2, + data = { + label = "Stream 1", + type = "clipRecording", + resolution = { + width = 1280, + height = 720, + fps = 15 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 1280, y = 720 } + }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.wait_for_events() + -- Change label only + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "videoStreamSettings", component = "main", command = "setStream", args = { + 2, + "clipRecording", -- type + "Recording Stream", -- label + { width = 1280, height = 720, fps = 15 }, -- resolution + { upperLeftVertex = {x = 0, y = 0}, lowerRightVertex = {x = 1280, y = 720} }, -- viewport + "disabled", -- watermark + "disabled" -- onScreenDisplay + }} + }) + -- Should emit updated capability directly, no stream reallocation + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 2, + data = { + label = "Recording Stream", + type = "clipRecording", + resolution = { + width = 1280, + height = 720, + fps = 15 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 1280, y = 720 } + }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + end +) + +test.register_coroutine_test( + "setStream with only viewport change should send DPTZSetViewport command", + function() + update_device_profile() + test.wait_for_events() + -- Set up existing stream + 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 = 5, + 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 = 3840, height = 2160}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 3840, height = 2160}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = true, + reference_count = 0 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 5, + data = { + label = "Stream 1", + type = "liveStream", + resolution = { + width = 3840, + height = 2160, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 3840, y = 2160 } + }, + watermark = "disabled", + onScreenDisplay = "enabled" + } + } + })) + ) + test.wait_for_events() + -- Change only viewport + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "videoStreamSettings", component = "main", command = "setStream", args = { + 5, + "liveStream", -- type + "Stream 1", -- label + { width = 3840, height = 2160, fps = 30 }, -- resolution + { upperLeftVertex = {x = 500, y = 500}, lowerRightVertex = {x = 3340, y = 1660} }, -- viewport + "disabled", -- watermark + "enabled" -- onScreenDisplay + }} + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.DPTZSetViewport(mock_device, CAMERA_EP, + 5, + clusters.Global.types.ViewportStruct({ + x1 = 500, + x2 = 3340, + y1 = 500, + y2 = 1660 + }) + ) + }) + -- Should emit updated capability directly, no stream reallocation + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 5, + data = { + label = "Stream 1", + type = "liveStream", + resolution = { + width = 3840, + height = 2160, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 500, y = 500 }, + lowerRightVertex = { x = 3340, y = 1660 } + }, + watermark = "disabled", + onScreenDisplay = "enabled" + } + } + })) + ) + end +) + +test.register_coroutine_test( + "setStream with resolution change should trigger reallocation", + function() + update_device_profile() + test.wait_for_events() + -- Set up existing stream + 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 = 1280, height = 720}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1280, height = 720}), + 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 = 1280, + height = 720, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 1280, y = 720 } + }, + watermark = "enabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.wait_for_events() + -- Change resolution and reallocate stream + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "videoStreamSettings", component = "main", command = "setStream", args = { + 1, + "liveStream", -- type + "HD Stream", -- label + { width = 1920, height = 1080, fps = 30 }, -- resolution + { upperLeftVertex = {x = 0, y = 0}, lowerRightVertex = {x = 1280, y = 720} }, -- viewport + "enabled", -- watermark + "disabled" -- onScreenDisplay + }} + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 1, + data = { + label = "HD Stream", + type = "liveStream", + resolution = { + width = 1280, + height = 720, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 1280, y = 720 } + }, + watermark = "enabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.server.commands.VideoStreamDeallocate(mock_device, CAMERA_EP, 1) + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.server.commands.VideoStreamAllocate(mock_device, CAMERA_EP, + clusters.Global.types.StreamUsageEnum.LIVE_VIEW, + clusters.CameraAvStreamManagement.types.VideoCodecEnum.H264, + 30, + 60, + clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1920, height = 1080}), + clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1920, height = 1080}), + 10000, + 2000000, + 4000, + true, + false + ) + }) + 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 = 1920, height = 1080}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1920, height = 1080}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = false, + reference_count = 0 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 1, + data = { + label = "HD Stream", + type = "liveStream", + resolution = { + width = 1920, + height = 1080, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 1920, y = 1080 } + }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + end +) + +test.register_coroutine_test( + "Stream label should persist across attribute reports", + function() + update_device_profile() + test.wait_for_events() + -- Set up existing stream + 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 = 3, + 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 = 480}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 640, height = 480}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = false, + reference_count = 0 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 3, + data = { + label = "Stream 1", + type = "liveStream", + resolution = { width = 640, height = 480, fps = 30 }, + viewport = { upperLeftVertex = { x = 0, y = 0 }, lowerRightVertex = { x = 640, y = 480 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.wait_for_events() + -- Change label + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "videoStreamSettings", component = "main", command = "setStream", args = { + 3, + "liveStream", -- type + "My Camera", -- label + { width = 640, height = 480, fps = 30 }, -- resolution + { upperLeftVertex = {x = 0, y = 0}, lowerRightVertex = {x = 640, y = 480} }, -- viewport + "disabled", -- watermark + "disabled" -- onScreenDisplay + }} + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 3, + data = { + label = "My Camera", + type = "liveStream", + resolution = { width = 640, height = 480, fps = 30 }, + viewport = { upperLeftVertex = { x = 0, y = 0 }, lowerRightVertex = { x = 640, y = 480 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.wait_for_events() + -- Simulate another AllocatedVideoStreams report + 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 = 3, + 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 = 480}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 640, height = 480}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = false, + reference_count = 0 + }) + } + ) + }) + -- Should preserve the custom label from capability state + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 3, + data = { + label = "My Camera", + type = "liveStream", + resolution = { width = 640, height = 480, fps = 30 }, + viewport = { upperLeftVertex = { x = 0, y = 0 }, lowerRightVertex = { x = 640, y = 480 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + end +) + +test.register_coroutine_test( + "DPTZStreams attribute should update viewports in capability", + function() + update_device_profile() + test.wait_for_events() + -- Set up multiple existing streams + 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 = 1920, height = 1080}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1920, height = 1080}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = false, + reference_count = 0 + }), + clusters.CameraAvStreamManagement.types.VideoStreamStruct({ + video_stream_id = 2, + stream_usage = clusters.Global.types.StreamUsageEnum.RECORDING, + video_codec = clusters.CameraAvStreamManagement.types.VideoCodecEnum.H264, + min_frame_rate = 15, + max_frame_rate = 30, + min_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1280, height = 720}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1280, height = 720}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + 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 = 1920, height = 1080, fps = 30 }, + viewport = { upperLeftVertex = { x = 0, y = 0 }, lowerRightVertex = { x = 1920, y = 1080 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + }, + { + streamId = 2, + data = { + label = "Stream 2", + type = "clipRecording", + resolution = { width = 1280, height = 720, fps = 15 }, + viewport = { upperLeftVertex = { x = 0, y = 0 }, lowerRightVertex = { x = 1280, y = 720 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams:build_test_report_data( + mock_device, CAMERA_EP, { + clusters.CameraAvSettingsUserLevelManagement.types.DPTZStruct({ + video_stream_id = 1, + viewport = clusters.Global.types.ViewportStruct({ + x1 = 200, + x2 = 1720, + y1 = 100, + y2 = 980 + }) + }), + clusters.CameraAvSettingsUserLevelManagement.types.DPTZStruct({ + video_stream_id = 2, + viewport = clusters.Global.types.ViewportStruct({ + x1 = 50, + x2 = 1230, + y1 = 50, + y2 = 670 + }) + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 1, + data = { + label = "Stream 1", + type = "liveStream", + resolution = { width = 1920, height = 1080, fps = 30 }, + viewport = { upperLeftVertex = { x = 200, y = 100 }, lowerRightVertex = { x = 1720, y = 980 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + }, + { + streamId = 2, + data = { + label = "Stream 2", + type = "clipRecording", + resolution = { width = 1280, height = 720, fps = 15 }, + viewport = { upperLeftVertex = { x = 50, y = 50 }, lowerRightVertex = { x = 1230, y = 670 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + end +) + test.register_coroutine_test( "Camera profile should not update for an unchanged Status Light AttributeList report", function() From 94441f9304a63f64894ca676c2bd0d6d8c647935 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 24 Feb 2026 14:20:56 -0800 Subject: [PATCH 148/538] Merge pull request #2806 from SmartThingsCommunity/new_device/WWSTCERT-10480 WWSTCERT-10480 GELUBU Door Lock G30 --- drivers/SmartThings/zigbee-lock/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/zigbee-lock/fingerprints.yml b/drivers/SmartThings/zigbee-lock/fingerprints.yml index 5c04d78ce5..083dd3a11e 100644 --- a/drivers/SmartThings/zigbee-lock/fingerprints.yml +++ b/drivers/SmartThings/zigbee-lock/fingerprints.yml @@ -5,6 +5,11 @@ zigbeeManufacturer: manufacturer: GELUBU model: S93 deviceProfileName: lock-battery + - id: "GELUBU/G30" + deviceLabel: GELUBU Door Lock G30 + manufacturer: GELUBU + model: G30 + deviceProfileName: lock-battery # YALE - id: "Yale YRD220/240" deviceLabel: "Yale Door Lock" From b7354573113067eadd4549634c42d95b72cbd0f3 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 24 Feb 2026 14:23:29 -0800 Subject: [PATCH 149/538] Merge pull request #2591 from SmartThingsCommunity/new_device/WWSTCERT-9153 WWSTCERT-9153 Leviton Decora Smart Wi-Fi (3rd Gen) 15A Switch --- drivers/SmartThings/matter-switch/fingerprints.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 948279c3f3..33f1b6f1d4 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -972,6 +972,16 @@ matterManufacturer: vendorId: 0x109B productId: 0x1007 deviceProfileName: 3-button + - id: "4251/4113" + deviceLabel: "Decora Smart Wi-Fi (3rd Gen) 15A Switch" + vendorId: 0x109B + productId: 0x1011 + deviceProfileName: switch-binary + - id: "4251/4112" + deviceLabel: "Decora Smart Wi-Fi (3rd Gen) 600W Dimmer" + vendorId: 0x109B + productId: 0x1010 + deviceProfileName: switch-level #LeTianPai - id: "5163/4097" deviceLabel: LeTianPai Smart Light Bulb From d81a16e213b0e1eb0cf7f1248c0e185dacd7155a Mon Sep 17 00:00:00 2001 From: Konrad K <33450498+KKlimczukS@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:39:44 +0100 Subject: [PATCH 150/538] fingerprints for Zooz sensors: ZSE11 (WWSTCERT-10463) , ZSE41 (WWSTCERT-10451), ZSE44 (WWSTCERT-10455) (#2794) * fingerprints for Zooz sensors: ZSE11, ZSE41, ZSE44 * removes redundant white space * cleanup * decimal -> hex --- .../SmartThings/zwave-sensor/fingerprints.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/zwave-sensor/fingerprints.yml b/drivers/SmartThings/zwave-sensor/fingerprints.yml index 502e5bd5a7..0810b20a38 100644 --- a/drivers/SmartThings/zwave-sensor/fingerprints.yml +++ b/drivers/SmartThings/zwave-sensor/fingerprints.yml @@ -446,11 +446,23 @@ zwaveManufacturer: productId: 0x000B deviceProfileName: contact-battery-tamperalert - id: 027A/7000/E001 - deviceLabel: Zooz Open/Closed Sensor + deviceLabel: Open Close XS Sensor manufacturerId: 0x027A productType: 0x7000 productId: 0xE001 - deviceProfileName: contact-battery-tamperalert + deviceProfileName: base-contact + - id: 027A/0201/0006 + deviceLabel: Q Sensor + manufacturerId: 0x027A + productType: 0x0201 + productId: 0x0006 + deviceProfileName: multi-functional-motion + - id: 027A/7000/E004 + deviceLabel: Temperature Humidity XS Sensor + manufacturerId: 0x027A + productType: 0x7000 + productId: 0xE004 + deviceProfileName: humidity-temperature-battery - id: "aeotec/multisensor/7" deviceLabel: Aeotec Multipurpose Sensor manufacturerId: 0x0371 From 12496097b1806d66658a1db92e9982121d6750b5 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:11:53 -0600 Subject: [PATCH 151/538] Merge pull request #2814 from inasail/CHAD-17680 CHAD-17680 - [Matter] Change the icon for the Ikea knob and dual button --- .../matter-switch/fingerprints.yml | 2 +- .../profiles/ikea-2-button-battery.yml | 53 +++++++++++++++++++ .../matter-switch/profiles/ikea-scroll.yml | 46 ++++++++++++++++ .../src/switch_utils/device_configuration.lua | 3 ++ .../matter-switch/src/switch_utils/fields.lua | 3 +- 5 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 33f1b6f1d4..1c8adddff4 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -867,7 +867,7 @@ matterManufacturer: deviceLabel: BILRESA dual button vendorId: 0x117C productId: 0x8001 - deviceProfileName: 2-button-battery + deviceProfileName: ikea-2-button-battery #Innovation Matters - id: "4978/1" deviceLabel: M2D Bridge diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml b/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml new file mode 100644 index 0000000000..0b256f3b65 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml @@ -0,0 +1,53 @@ +name: ikea-2-button-battery +components: + - id: main + label: Button 1 + capabilities: + - id: button + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController + - id: button2 + label: Button 2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController +deviceConfig: + icons: + - group: main + iconUrl: 'icon://button_multi' + dashboard: + states: + - component: main + capability: button + version: 1 + detailView: + - component: main + capability: button + version: 1 + - component: main + capability: battery + version: 1 + - component: button2 + capability: button + version: 1 + automation: + conditions: + - component: main + capability: button + version: 1 + - component: main + capability: battery + version: 1 + - component: button2 + capability: button + version: 1 + actions: [] diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml index e07006ce9d..166b0a62f1 100644 --- a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml +++ b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml @@ -33,3 +33,49 @@ components: version: 1 categories: - name: Button +deviceConfig: + icons: + - group: main + iconUrl: 'icon://button_wheel' + dashboard: + states: + - component: main + capability: button + version: 1 + detailView: + - component: main + capability: button + version: 1 + - component: main + capability: knob + version: 1 + - component: main + capability: battery + version: 1 + - component: group2 + capability: button + version: 1 + - component: group2 + capability: knob + version: 1 + - component: group3 + capability: button + version: 1 + - component: group3 + capability: knob + version: 1 + automation: + conditions: + - component: main + capability: button + version: 1 + - component: main + capability: battery + version: 1 + - component: group2 + capability: button + version: 1 + - component: group3 + capability: button + version: 1 + actions: [] diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 8542972320..18219ef459 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -132,6 +132,9 @@ function ButtonDeviceConfiguration.update_button_profile(device, default_endpoin if switch_utils.get_product_override_field(device, "is_climate_sensor_w100") then profile_name = "3-button-battery-temperature-humidity" end + if switch_utils.get_product_override_field(device, "is_ikea_dual_button") then + profile_name = "ikea-2-button-battery" + end return profile_name end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 6eb03b1472..2b315c6528 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -101,7 +101,8 @@ SwitchFields.vendor_overrides = { [0x2004] = { is_climate_sensor_w100 = true }, -- Climate Sensor W100, requires unique profile }, [0x117C] = { -- IKEA_MANUFACTURER_ID - [0x8000] = { is_ikea_scroll = true } + [0x8000] = { is_ikea_scroll = true }, -- BILRESA scroll wheel + [0x8001] = { is_ikea_dual_button = true}, -- BILRESA dual button }, [0x1189] = { -- LEDVANCE_MANUFACTURER_ID [0x0891] = { target_profile = "switch-binary", initial_profile = "light-binary" }, From c436b67e2fbae08e383317ec59d61817e8c24e8c Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon, 9 Mar 2026 12:16:16 -0500 Subject: [PATCH 152/538] Matter Camera: Support additional setStream attributes (#2786) The camera sub-driver lacks handling for the resolution, type, label, and viewport aguments of the videoStreamSettings.setStream command. This change checks if any of these attributes have changed and deallocates the stream before reallocating a new stream with the updated parameters. Additionally, the viewport aspect of videoStreamSettings, which was not previously being set correctly, is now set based on the DPTZStreams attribute of the CAVSULM cluster. --- .../camera_handlers/attribute_handlers.lua | 94 ++- .../camera_handlers/capability_handlers.lua | 148 +++- .../camera/camera_utils/fields.lua | 16 +- .../sub_drivers/camera/camera_utils/utils.lua | 3 +- .../src/sub_drivers/camera/init.lua | 5 +- .../src/test/test_matter_camera.lua | 716 +++++++++++++++++- 6 files changed, 940 insertions(+), 42 deletions(-) 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 index df99d50f94..140ba6d313 100644 --- 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 @@ -182,14 +182,35 @@ end function CameraAttributeHandlers.allocated_video_streams_handler(driver, device, ib, response) if not ib.data.elements then return end + + local dptz_viewports = device:get_field(camera_fields.DPTZ_VIEWPORTS) or {} local streams = {} + + local previous_streams = device:get_latest_state( + camera_fields.profile_components.main, + capabilities.videoStreamSettings.ID, + capabilities.videoStreamSettings.videoStreams.NAME + ) or {} + + local previous_stream_labels = {} + + for _, stream in pairs(previous_streams) do + previous_stream_labels[stream.streamId] = stream.data.label + end + for i, v in ipairs(ib.data.elements) do local stream = v.elements + local stream_id = stream.video_stream_id.value + + -- Use label from existing capability state, if available + local capability_label = previous_stream_labels[stream_id] + local video_stream = { - streamId = stream.video_stream_id.value, + streamId = stream_id, data = { - label = "Stream " .. i, - type = stream.stream_usage.value == clusters.Global.types.StreamUsageEnum.LIVE_VIEW and "liveStream" or "clipRecording", + label = capability_label or "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, @@ -197,21 +218,31 @@ function CameraAttributeHandlers.allocated_video_streams_handler(driver, device, } } } - local viewport = device:get_field(camera_fields.VIEWPORT) - if viewport then - video_stream.data.viewport = viewport + + if dptz_viewports[stream_id] ~= nil then + video_stream.data.viewport = dptz_viewports[stream_id] + else + video_stream.data.viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { + x = stream.min_resolution.elements.width.value, + y = stream.min_resolution.elements.height.value + } + } end - if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.WATERMARK) then + + 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 + 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 + + device:emit_event_for_endpoint(ib, capabilities.videoStreamSettings.videoStreams(streams)) end function CameraAttributeHandlers.viewport_handler(driver, device, ib, response) @@ -221,6 +252,45 @@ function CameraAttributeHandlers.viewport_handler(driver, device, ib, response) })) end +function CameraAttributeHandlers.dptz_streams_handler(driver, device, ib, response) + if not ib.data.elements then return end + + local dptz_viewports = {} + for _, v in ipairs(ib.data.elements) do + local dptz_struct = v.elements + local stream_id = dptz_struct.video_stream_id.value + local viewport = dptz_struct.viewport.elements + + dptz_viewports[stream_id] = { + upperLeftVertex = { x = viewport.x1.value, y = viewport.y1.value }, + lowerRightVertex = { x = viewport.x2.value, y = viewport.y2.value } + } + end + + device:set_field(camera_fields.DPTZ_VIEWPORTS, dptz_viewports) + + local current_streams = device:get_latest_state( + camera_fields.profile_components.main, + capabilities.videoStreamSettings.ID, + capabilities.videoStreamSettings.videoStreams.NAME + ) or {} + local updated_streams = {} + for _, stream in pairs(current_streams) do + local updated_stream = { + streamId = stream.streamId, + data = stream.data + } + if dptz_viewports[stream.streamId] ~= nil then + updated_stream.data.viewport = dptz_viewports[stream.streamId] + end + table.insert(updated_streams, updated_stream) + end + + if #updated_streams > 0 then + device:emit_event_for_endpoint(ib, capabilities.videoStreamSettings.videoStreams(updated_streams)) + end +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) @@ -399,4 +469,4 @@ function CameraAttributeHandlers.camera_av_stream_management_attribute_list_hand camera_cfg.match_profile(device, status_light_enabled_present, status_light_brightness_present) end -return CameraAttributeHandlers \ No newline at end of file +return CameraAttributeHandlers 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 index fb85eb863f..0a7fcca312 100644 --- 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 @@ -327,17 +327,153 @@ function CameraCapabilityHandlers.handle_set_selected_sound(driver, device, cmd) device:send(clusters.Chime.attributes.SelectedChime:write(device, endpoint_id, cmd.args.id)) end +local function update_viewport_or_label(device, current_streams, streamId, label, viewport) + current_streams = current_streams or {} + for _, stream in ipairs(current_streams) do + if stream.streamId == streamId then + if label ~= nil then stream.data.label = label end + if viewport ~= nil then stream.data.viewport = viewport end + break + end + end + local endpoint_id = device:component_to_endpoint() + device:emit_event_for_endpoint(endpoint_id, capabilities.videoStreamSettings.videoStreams(current_streams)) +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" + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, + clusters.CameraAvStreamManagement.types.Feature.WATERMARK) then + if cmd.args.watermark ~= nil then + watermark_enabled = cmd.args.watermark == "enabled" + end + end + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, + clusters.CameraAvStreamManagement.types.Feature.ON_SCREEN_DISPLAY) then + if cmd.args.onScreenDisplay ~= nil then + on_screen_display_enabled = cmd.args.onScreenDisplay == "enabled" + end + end + + local current_streams = device:get_latest_state( + camera_fields.profile_components.main, + capabilities.videoStreamSettings.ID, + capabilities.videoStreamSettings.videoStreams.NAME + ) or {} + local current_stream + for _, stream in pairs(current_streams) do + if stream.streamId == cmd.args.streamId then + current_stream = stream.data + break + end + end + + local needs_reallocation = false + if current_stream ~= nil and current_stream.type ~= cmd.args.type then + needs_reallocation = true + elseif current_stream ~= nil and cmd.args.resolution ~= nil then + if current_stream.resolution.width ~= cmd.args.resolution.width or + current_stream.resolution.height ~= cmd.args.resolution.height or + current_stream.resolution.fps ~= cmd.args.resolution.fps then + needs_reallocation = true + end + elseif current_stream == nil and (cmd.args.type ~= nil or cmd.args.resolution ~= nil) then + needs_reallocation = true + end + + local viewport_changed = false + if cmd.args.viewport ~= nil and + camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, + clusters.CameraAvSettingsUserLevelManagement.types.Feature.DIGITALPTZ) then + if current_stream ~= nil and current_stream.viewport ~= nil then + if current_stream.viewport.upperLeftVertex.x ~= cmd.args.viewport.upperLeftVertex.x or + current_stream.viewport.upperLeftVertex.y ~= cmd.args.viewport.upperLeftVertex.y or + current_stream.viewport.lowerRightVertex.x ~= cmd.args.viewport.lowerRightVertex.x or + current_stream.viewport.lowerRightVertex.y ~= cmd.args.viewport.lowerRightVertex.y then + viewport_changed = true + end + elseif current_stream == nil or current_stream.viewport == nil then + viewport_changed = true + end + + if viewport_changed then + device:send(clusters.CameraAvSettingsUserLevelManagement.server.commands.DPTZSetViewport(device, endpoint_id, + cmd.args.streamId, + clusters.Global.types.ViewportStruct({ + x1 = cmd.args.viewport.upperLeftVertex.x, + x2 = cmd.args.viewport.lowerRightVertex.x, + y1 = cmd.args.viewport.upperLeftVertex.y, + y2 = cmd.args.viewport.lowerRightVertex.y + }) + )) + end 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" + + local label_changed = cmd.args.label ~= nil and cmd.args.label ~= current_stream.label + + if viewport_changed or label_changed then + update_viewport_or_label(device, current_streams, cmd.args.streamId, cmd.args.label, cmd.args.viewport) + end + + if not needs_reallocation then + local watermark_changed = watermark_enabled ~= nil and current_stream.watermark ~= nil and + ((watermark_enabled and current_stream.watermark == "disabled") or + (not watermark_enabled and current_stream.watermark == "enabled")) + local on_screen_display_changed = on_screen_display_enabled ~= nil and current_stream.onScreenDisplay ~= nil and + ((on_screen_display_enabled and current_stream.onScreenDisplay == "disabled") or + (not on_screen_display_enabled and current_stream.onScreenDisplay == "enabled")) + if watermark_changed or on_screen_display_changed then + device:send(clusters.CameraAvStreamManagement.server.commands.VideoStreamModify( + device, endpoint_id, cmd.args.streamId, watermark_enabled, on_screen_display_enabled + )) + end + return end - device:send(clusters.CameraAvStreamManagement.server.commands.VideoStreamModify(device, endpoint_id, - cmd.args.streamId, watermark_enabled, on_screen_display_enabled + + device:send(clusters.CameraAvStreamManagement.server.commands.VideoStreamDeallocate( + device, endpoint_id, cmd.args.streamId + )) + + local stream_usage = cmd.args.type == "liveStream" and + clusters.Global.types.StreamUsageEnum.LIVE_VIEW or clusters.Global.types.StreamUsageEnum.RECORDING + + local min_resolution, max_resolution + if cmd.args.resolution ~= nil then + min_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({ + width = cmd.args.resolution.width, + height = cmd.args.resolution.height + }) + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({ + width = cmd.args.resolution.width, + height = cmd.args.resolution.height + }) + end + + if watermark_enabled == nil then + watermark_enabled = camera_fields.video_stream_defaults.watermark_enabled + end + + if on_screen_display_enabled == nil then + on_screen_display_enabled = camera_fields.video_stream_defaults.on_screen_display_enabled + end + + -- Use the same resolution (if available) for MinResolution and MaxResolution to force the server to allocate the + -- stream with the desired resolution. + device:send(clusters.CameraAvStreamManagement.server.commands.VideoStreamAllocate(device, endpoint_id, + stream_usage, + camera_fields.video_stream_defaults.codec, + camera_fields.video_stream_defaults.min_frame_rate, + math.min(device:get_field(camera_fields.MAX_FRAMES_PER_SECOND) or camera_fields.video_stream_defaults.max_frame_rate, + camera_fields.video_stream_defaults.max_frame_rate), + min_resolution or device:get_field(camera_fields.MIN_RESOLUTION) or camera_fields.video_stream_defaults.min_resolution, + max_resolution or device:get_field(camera_fields.MAX_RESOLUTION) or camera_fields.video_stream_defaults.max_resolution, + camera_fields.video_stream_defaults.min_bitrate, + camera_fields.video_stream_defaults.max_bitrate, + camera_fields.video_stream_defaults.key_frame_interval, + watermark_enabled, + on_screen_display_enabled )) end 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 index 7598b89893..000008fa51 100644 --- 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 @@ -13,7 +13,7 @@ CameraFields.SUPPORTED_RESOLUTIONS = "__supported_resolutions" CameraFields.MAX_RESOLUTION = "__max_resolution" CameraFields.MIN_RESOLUTION = "__min_resolution" CameraFields.TRIGGERED_ZONES = "__triggered_zones" -CameraFields.VIEWPORT = "__viewport" +CameraFields.DPTZ_VIEWPORTS = "__dptz_viewports" CameraFields.PAN_IDX = "PAN" CameraFields.TILT_IDX = "TILT" @@ -47,4 +47,18 @@ CameraFields.ABS_ZOOM_MIN = 1 CameraFields.ABS_VOL_MAX = 254.0 CameraFields.ABS_VOL_MIN = 0.0 +-- Define defaults for allocating new streams. Note that these are the same values use by the hub. +CameraFields.video_stream_defaults = { + codec = clusters.CameraAvStreamManagement.types.VideoCodecEnum.H264, + min_frame_rate = 30, + max_frame_rate = 60, + min_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 320, height = 240}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1920, height = 1080}), + min_bitrate = 10000, + max_bitrate = 2000000, + key_frame_interval = 4000, + watermark_enabled = false, + on_screen_display_enabled = false +} + 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 index 1caa9737bb..12341f493e 100644 --- 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 @@ -253,7 +253,8 @@ function CameraUtils.subscribe(device) clusters.CameraAvStreamManagement.attributes.RateDistortionTradeOffPoints, clusters.CameraAvStreamManagement.attributes.MaxEncodedPixelRate, clusters.CameraAvStreamManagement.attributes.VideoSensorParams, - clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams + clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams, + clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams }, [capabilities.zoneManagement.ID] = { clusters.ZoneManagement.attributes.MaxZones, diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index f13589ff41..179ed54742 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -108,14 +108,15 @@ local camera_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.CameraAvSettingsUserLevelManagement.attributes.TiltMin.ID] = attribute_handlers.pt_range_handler_factory(capabilities.mechanicalPanTiltZoom.tiltRange, camera_fields.pt_range_fields[camera_fields.TILT_IDX].min), + [clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams.ID] = attribute_handlers.dptz_streams_handler }, [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.ZoneManagement.attributes.Sensitivity.ID] = attribute_handlers.sensitivity_handler }, [clusters.Chime.ID] = { [clusters.Chime.attributes.InstalledChimeSounds.ID] = attribute_handlers.installed_chime_sounds_handler, diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 930cb069dc..24a7f3306c 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -46,7 +46,8 @@ local mock_device = test.mock_device.build_test_matter_device({ }, { cluster_id = clusters.CameraAvSettingsUserLevelManagement.ID, - feature_map = clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN | + feature_map = clusters.CameraAvSettingsUserLevelManagement.types.Feature.DIGITALPTZ | + clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN | clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_TILT | clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_ZOOM | clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PRESETS, @@ -205,6 +206,7 @@ local additional_subscribed_attributes = { clusters.CameraAvSettingsUserLevelManagement.attributes.PanMin, clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMax, clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMin, + clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams, clusters.Chime.attributes.InstalledChimeSounds, clusters.Chime.attributes.SelectedChime, clusters.ZoneManagement.attributes.MaxZones, @@ -1774,35 +1776,117 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "Stream management commands should send the appropriate commands", + "setStream with label and viewport changes should emit capability event", function() update_device_profile() test.wait_for_events() + -- Set up an existing stream + 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 = 3, + 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 = 1920, height = 1080}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1920, height = 1080}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = false, + reference_count = 0 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 3, + data = { + label = "Stream 1", + type = "liveStream", + resolution = { + width = 1920, + height = 1080, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 1920, y = 1080 } + }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.wait_for_events() + -- Change label and viewport only test.socket.capability:__queue_receive({ mock_device.id, - { capability = "videoStreamSettings", component = "main", command = "setStream", args = { + { + 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" + "liveStream", -- type + "My Stream", -- label + { width = 1920, height = 1080, fps = 30 }, -- resolution + { upperLeftVertex = {x = 100, y = 100}, lowerRightVertex = {x = 1820, y = 980} }, -- viewport + "disabled", -- watermark + "disabled" -- onScreenDisplay }} }) + -- Should send DPTZSetViewport command test.socket.matter:__expect_send({ - mock_device.id, clusters.CameraAvStreamManagement.server.commands.VideoStreamModify(mock_device, CAMERA_EP, - 3, true, false + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.DPTZSetViewport(mock_device, CAMERA_EP, + 3, + clusters.Global.types.ViewportStruct({ + x1 = 100, + x2 = 1820, + y1 = 100, + y2 = 980 + }) ) }) - end + -- Should emit updated capability directly, no stream reallocation + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 3, + data = { + label = "My Stream", + type = "liveStream", + resolution = { + width = 1920, + height = 1080, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 100, y = 100 }, + lowerRightVertex = { x = 1820, y = 980 } + }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + end, + { + min_api_version = 19 + } ) test.register_coroutine_test( - "Stream management setStream command should modify an existing stream", + "setStream with only watermark/OSD changes should use VideoStreamModify", function() update_device_profile() test.wait_for_events() + -- Set up an existing stream test.socket.matter:__queue_receive({ mock_device.id, clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams:build_test_report_data( @@ -1837,22 +1921,29 @@ test.register_coroutine_test( height = 360, fps = 30 }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 640, y = 360 } + }, watermark = "enabled", onScreenDisplay = "disabled" } } })) ) + test.wait_for_events() + -- Change watermark and OSD only test.socket.capability:__queue_receive({ mock_device.id, - { capability = "videoStreamSettings", component = "main", command = "setStream", args = { + { + 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" + "liveStream", -- type + "Stream 1", -- label + { width = 640, height = 360, fps = 30 }, -- resolution + { upperLeftVertex = {x = 0, y = 0}, lowerRightVertex = {x = 640, y = 360} }, -- viewport + "disabled", -- watermark + "enabled" -- onScreenDisplay }} }) test.socket.matter:__expect_send({ @@ -1863,6 +1954,591 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "setStream with only label change should emit capability event", + function() + update_device_profile() + test.wait_for_events() + -- Set up existing stream + 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 = 2, + stream_usage = clusters.Global.types.StreamUsageEnum.RECORDING, + video_codec = clusters.CameraAvStreamManagement.types.VideoCodecEnum.H264, + min_frame_rate = 15, + max_frame_rate = 30, + min_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1280, height = 720}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1280, height = 720}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = false, + reference_count = 0 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 2, + data = { + label = "Stream 1", + type = "clipRecording", + resolution = { + width = 1280, + height = 720, + fps = 15 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 1280, y = 720 } + }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.wait_for_events() + -- Change label only + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "videoStreamSettings", component = "main", command = "setStream", args = { + 2, + "clipRecording", -- type + "Recording Stream", -- label + { width = 1280, height = 720, fps = 15 }, -- resolution + { upperLeftVertex = {x = 0, y = 0}, lowerRightVertex = {x = 1280, y = 720} }, -- viewport + "disabled", -- watermark + "disabled" -- onScreenDisplay + }} + }) + -- Should emit updated capability directly, no stream reallocation + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 2, + data = { + label = "Recording Stream", + type = "clipRecording", + resolution = { + width = 1280, + height = 720, + fps = 15 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 1280, y = 720 } + }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + end +) + +test.register_coroutine_test( + "setStream with only viewport change should send DPTZSetViewport command", + function() + update_device_profile() + test.wait_for_events() + -- Set up existing stream + 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 = 5, + 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 = 3840, height = 2160}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 3840, height = 2160}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = true, + reference_count = 0 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 5, + data = { + label = "Stream 1", + type = "liveStream", + resolution = { + width = 3840, + height = 2160, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 3840, y = 2160 } + }, + watermark = "disabled", + onScreenDisplay = "enabled" + } + } + })) + ) + test.wait_for_events() + -- Change only viewport + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "videoStreamSettings", component = "main", command = "setStream", args = { + 5, + "liveStream", -- type + "Stream 1", -- label + { width = 3840, height = 2160, fps = 30 }, -- resolution + { upperLeftVertex = {x = 500, y = 500}, lowerRightVertex = {x = 3340, y = 1660} }, -- viewport + "disabled", -- watermark + "enabled" -- onScreenDisplay + }} + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.DPTZSetViewport(mock_device, CAMERA_EP, + 5, + clusters.Global.types.ViewportStruct({ + x1 = 500, + x2 = 3340, + y1 = 500, + y2 = 1660 + }) + ) + }) + -- Should emit updated capability directly, no stream reallocation + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 5, + data = { + label = "Stream 1", + type = "liveStream", + resolution = { + width = 3840, + height = 2160, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 500, y = 500 }, + lowerRightVertex = { x = 3340, y = 1660 } + }, + watermark = "disabled", + onScreenDisplay = "enabled" + } + } + })) + ) + end +) + +test.register_coroutine_test( + "setStream with resolution change should trigger reallocation", + function() + update_device_profile() + test.wait_for_events() + -- Set up existing stream + 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 = 1280, height = 720}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1280, height = 720}), + 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 = 1280, + height = 720, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 1280, y = 720 } + }, + watermark = "enabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.wait_for_events() + -- Change resolution and reallocate stream + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "videoStreamSettings", component = "main", command = "setStream", args = { + 1, + "liveStream", -- type + "HD Stream", -- label + { width = 1920, height = 1080, fps = 30 }, -- resolution + { upperLeftVertex = {x = 0, y = 0}, lowerRightVertex = {x = 1280, y = 720} }, -- viewport + "enabled", -- watermark + "disabled" -- onScreenDisplay + }} + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 1, + data = { + label = "HD Stream", + type = "liveStream", + resolution = { + width = 1280, + height = 720, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 1280, y = 720 } + }, + watermark = "enabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.server.commands.VideoStreamDeallocate(mock_device, CAMERA_EP, 1) + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.server.commands.VideoStreamAllocate(mock_device, CAMERA_EP, + clusters.Global.types.StreamUsageEnum.LIVE_VIEW, + clusters.CameraAvStreamManagement.types.VideoCodecEnum.H264, + 30, + 60, + clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1920, height = 1080}), + clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1920, height = 1080}), + 10000, + 2000000, + 4000, + true, + false + ) + }) + 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 = 1920, height = 1080}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1920, height = 1080}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = false, + reference_count = 0 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 1, + data = { + label = "HD Stream", + type = "liveStream", + resolution = { + width = 1920, + height = 1080, + fps = 30 + }, + viewport = { + upperLeftVertex = { x = 0, y = 0 }, + lowerRightVertex = { x = 1920, y = 1080 } + }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + end +) + +test.register_coroutine_test( + "Stream label should persist across attribute reports", + function() + update_device_profile() + test.wait_for_events() + -- Set up existing stream + 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 = 3, + 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 = 480}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 640, height = 480}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = false, + reference_count = 0 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 3, + data = { + label = "Stream 1", + type = "liveStream", + resolution = { width = 640, height = 480, fps = 30 }, + viewport = { upperLeftVertex = { x = 0, y = 0 }, lowerRightVertex = { x = 640, y = 480 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.wait_for_events() + -- Change label + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "videoStreamSettings", component = "main", command = "setStream", args = { + 3, + "liveStream", -- type + "My Camera", -- label + { width = 640, height = 480, fps = 30 }, -- resolution + { upperLeftVertex = {x = 0, y = 0}, lowerRightVertex = {x = 640, y = 480} }, -- viewport + "disabled", -- watermark + "disabled" -- onScreenDisplay + }} + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 3, + data = { + label = "My Camera", + type = "liveStream", + resolution = { width = 640, height = 480, fps = 30 }, + viewport = { upperLeftVertex = { x = 0, y = 0 }, lowerRightVertex = { x = 640, y = 480 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.wait_for_events() + -- Simulate another AllocatedVideoStreams report + 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 = 3, + 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 = 480}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 640, height = 480}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = false, + reference_count = 0 + }) + } + ) + }) + -- Should preserve the custom label from capability state + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 3, + data = { + label = "My Camera", + type = "liveStream", + resolution = { width = 640, height = 480, fps = 30 }, + viewport = { upperLeftVertex = { x = 0, y = 0 }, lowerRightVertex = { x = 640, y = 480 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + end +) + +test.register_coroutine_test( + "DPTZStreams attribute should update viewports in capability", + function() + update_device_profile() + test.wait_for_events() + -- Set up multiple existing streams + 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 = 1920, height = 1080}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1920, height = 1080}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + osd_enabled = false, + reference_count = 0 + }), + clusters.CameraAvStreamManagement.types.VideoStreamStruct({ + video_stream_id = 2, + stream_usage = clusters.Global.types.StreamUsageEnum.RECORDING, + video_codec = clusters.CameraAvStreamManagement.types.VideoCodecEnum.H264, + min_frame_rate = 15, + max_frame_rate = 30, + min_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1280, height = 720}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 1280, height = 720}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = false, + 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 = 1920, height = 1080, fps = 30 }, + viewport = { upperLeftVertex = { x = 0, y = 0 }, lowerRightVertex = { x = 1920, y = 1080 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + }, + { + streamId = 2, + data = { + label = "Stream 2", + type = "clipRecording", + resolution = { width = 1280, height = 720, fps = 15 }, + viewport = { upperLeftVertex = { x = 0, y = 0 }, lowerRightVertex = { x = 1280, y = 720 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams:build_test_report_data( + mock_device, CAMERA_EP, { + clusters.CameraAvSettingsUserLevelManagement.types.DPTZStruct({ + video_stream_id = 1, + viewport = clusters.Global.types.ViewportStruct({ + x1 = 200, + x2 = 1720, + y1 = 100, + y2 = 980 + }) + }), + clusters.CameraAvSettingsUserLevelManagement.types.DPTZStruct({ + video_stream_id = 2, + viewport = clusters.Global.types.ViewportStruct({ + x1 = 50, + x2 = 1230, + y1 = 50, + y2 = 670 + }) + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 1, + data = { + label = "Stream 1", + type = "liveStream", + resolution = { width = 1920, height = 1080, fps = 30 }, + viewport = { upperLeftVertex = { x = 200, y = 100 }, lowerRightVertex = { x = 1720, y = 980 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + }, + { + streamId = 2, + data = { + label = "Stream 2", + type = "clipRecording", + resolution = { width = 1280, height = 720, fps = 15 }, + viewport = { upperLeftVertex = { x = 50, y = 50 }, lowerRightVertex = { x = 1230, y = 670 } }, + watermark = "disabled", + onScreenDisplay = "disabled" + } + } + })) + ) + end +) + test.register_coroutine_test( "Camera profile should not update for an unchanged Status Light AttributeList report", function() From fee90b0ec7d9718edcc450bae3644b2ee195be41 Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:40:00 -0600 Subject: [PATCH 153/538] Matter Camera: fix gating of audioRecording capability (#2820) audioRecording should only be included in the camera profile if the AUDIO feature of CAVSM is available and if the device supports the PushAvStreamTransport cluster (previously only the former was being checked). --- .../sub_drivers/camera/camera_utils/device_configuration.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 index 20641ebd33..4f467cbd07 100644 --- 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 @@ -72,7 +72,9 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr 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) + if switch_utils.find_cluster_on_ep(camera_ep, clusters.PushAvStreamTransport.ID, "SERVER") then + table.insert(main_component_capabilities, capabilities.audioRecording.ID) + end table.insert(microphone_component_capabilities, capabilities.audioMute.ID) table.insert(microphone_component_capabilities, capabilities.audioVolume.ID) end From f1ffe32f394796d5908370772b0e245d47908b61 Mon Sep 17 00:00:00 2001 From: Konrad K <33450498+KKlimczukS@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:40:50 +0100 Subject: [PATCH 154/538] NodOn fingerprint ( WWSTCERT-9497) (#2713) * NodOn fingerprints * Fingerprint for NodOn IRB-4-1-00 * removes 2 fingerprints * removes fingerprint from the PR due to issues with the device * removes fingerprint from the PR due to issues with the device --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 6bb32c5978..8765be8aeb 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2405,6 +2405,11 @@ zigbeeManufacturer: manufacturer: NodOn model: SIN-4-1-20 deviceProfileName: basic-switch + - id: "NodOn/SIN-4-1-20-PRO" + deviceLabel: Zigbee Multifunction Relay Switch PRO + manufacturer: NodOn + model: SIN-4-1-20-PRO + deviceProfileName: basic-switch - id: "NodOn/SIN-4-2-20" deviceLabel: Zigbee ON/OFF Lighting Relay Switch manufacturer: NodOn From b82b7e18c890490cdafc140b8fedee350e03ec78 Mon Sep 17 00:00:00 2001 From: Marcin Krystian Tyminski <81477027+marcintyminski@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:51:50 +0100 Subject: [PATCH 155/538] WWSTCERT-10559/10562/10565/10568 add driver to frient EMI devices (#2730) * add driver * removed unused variables * Changes according to pr comments * More changes based on pr comments * date changes * Requested changes to categories in profiles and updated tests * fix conflict * remove extra closing parenthesis --- .../zigbee-power-meter/fingerprints.yml | 19 +- ...ower-energy-battery-consumption-report.yml | 37 ++ ...frient-power-energy-consumption-report.yml | 35 ++ .../frient-power-energy-current-voltage.yml | 48 +++ .../frient-power-meter-consumption-report.yml | 16 + .../src/frient/EMIZB-151/can_handle.lua | 15 + .../src/frient/EMIZB-151/fingerprints.lua | 8 + .../src/frient/EMIZB-151/init.lua | 258 +++++++++++++ .../src/frient/fingerprints.lua | 6 +- .../zigbee-power-meter/src/frient/init.lua | 172 ++++++++- .../src/frient/sub_drivers.lua | 9 + .../zigbee-power-meter/src/frient/utils.lua | 10 + .../zigbee-power-meter/src/init.lua | 1 + ...ower_energy_battery_consumption_report.lua | 299 +++++++++++++++ ...frient_power_energy_consumption_report.lua | 272 +++++++++++++ ...st_frient_power_energy_current_voltage.lua | 361 ++++++++++++++++++ .../test/test_zigbee_power_meter_frient.lua | 138 +++++-- 17 files changed, 1669 insertions(+), 35 deletions(-) create mode 100644 drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-battery-consumption-report.yml create mode 100644 drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-consumption-report.yml create mode 100644 drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-current-voltage.yml create mode 100644 drivers/SmartThings/zigbee-power-meter/profiles/frient-power-meter-consumption-report.yml create mode 100644 drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/init.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/frient/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/frient/utils.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_battery_consumption_report.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_consumption_report.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_current_voltage.lua diff --git a/drivers/SmartThings/zigbee-power-meter/fingerprints.yml b/drivers/SmartThings/zigbee-power-meter/fingerprints.yml index ec58259dd5..15d3f199ec 100644 --- a/drivers/SmartThings/zigbee-power-meter/fingerprints.yml +++ b/drivers/SmartThings/zigbee-power-meter/fingerprints.yml @@ -7,12 +7,27 @@ zigbeeManufacturer: deviceLabel: frient Energy Monitor manufacturer: Develco model: "ZHEMI101" - deviceProfileName: power-meter + deviceProfileName: frient-power-energy-consumption-report - id: "Develco/EMIZB-132" deviceLabel: frient Energy Monitor manufacturer: Develco Products A/S model: "EMIZB-132" - deviceProfileName: power-meter + deviceProfileName: frient-power-meter-consumption-report + - id: "frient A/S/EMIZB-132" + deviceLabel: frient Energy Monitor + manufacturer: frient A/S + model: "EMIZB-132" + deviceProfileName: frient-power-meter-consumption-report + - id: "frient A/S/EMIZB-141" + deviceLabel: "frient EMI 2 LED" + manufacturer: frient A/S + model: "EMIZB-141" + deviceProfileName: frient-power-energy-battery-consumption-report + - id: "frient A/S/EMIZB-151" + deviceLabel: "frient EMI 2 P1" + manufacturer: frient A/S + model: "EMIZB-151" + deviceProfileName: frient-power-energy-current-voltage - id: "ShinaSystem/PMM-300Z1" deviceLabel: SiHAS Energy Monitor manufacturer: ShinaSystem diff --git a/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-battery-consumption-report.yml b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-battery-consumption-report.yml new file mode 100644 index 0000000000..03278796a9 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-battery-consumption-report.yml @@ -0,0 +1,37 @@ +name: frient-power-energy-battery-consumption-report +components: + - id: main + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +preferences: + - title: "Pulse Configuration" + name: pulseConfiguration + description: "Number of pulses the meter outputs per unit" + required: false + preferenceType: integer + definition: + minimum: 50 + maximum: 10000 + default: 1000 + - title: "Initial Energy Consumption" + name: currentSummation + description: "Offset (scaled value) for current summation delivered" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 268435455 + default: 0 diff --git a/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-consumption-report.yml b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-consumption-report.yml new file mode 100644 index 0000000000..c4ac75361b --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-consumption-report.yml @@ -0,0 +1,35 @@ +name: frient-power-energy-consumption-report +components: + - id: main + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +preferences: + - title: "Pulse Configuration" + name: pulseConfiguration + description: "Number of pulses the meter outputs per unit" + required: false + preferenceType: integer + definition: + minimum: 50 + maximum: 10000 + default: 1000 + - title: "Initial Energy Consumption" + name: currentSummation + description: "Offset (scaled value) for current summation delivered" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 268435455 + default: 0 diff --git a/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-current-voltage.yml b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-current-voltage.yml new file mode 100644 index 0000000000..b4464d4ada --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-current-voltage.yml @@ -0,0 +1,48 @@ +name: frient-power-energy-current-voltage +components: + - id: main + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor + - id: production + label: Production + capabilities: + - id: energyMeter + version: 1 + - id: phaseA + label: "Phase A" + capabilities: + - id: powerMeter + version: 1 + - id: voltageMeasurement + version: 1 + - id: currentMeasurement + version: 1 + - id: phaseB + label: "Phase B" + capabilities: + - id: powerMeter + version: 1 + - id: voltageMeasurement + version: 1 + - id: currentMeasurement + version: 1 + - id: phaseC + label: "Phase C" + capabilities: + - id: powerMeter + version: 1 + - id: voltageMeasurement + version: 1 + - id: currentMeasurement + version: 1 \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-meter-consumption-report.yml b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-meter-consumption-report.yml new file mode 100644 index 0000000000..f65fce5d42 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-meter-consumption-report.yml @@ -0,0 +1,16 @@ +name: frient-power-meter-consumption-report +components: +- id: main + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/can_handle.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/can_handle.lua new file mode 100644 index 0000000000..85ee8c086f --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_frient_power_meter = function(opts, driver, device, zb_rx) + local FINGERPRINTS = require("frient.EMIZB-151.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return true, require("frient.EMIZB-151") + end + end + + return false +end + +return is_frient_power_meter diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/fingerprints.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/fingerprints.lua new file mode 100644 index 0000000000..e6af73d53e --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_POWER_METER_FINGERPRINTS = { + { model = "EMIZB-151"} +} + +return ZIGBEE_POWER_METER_FINGERPRINTS \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/init.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/init.lua new file mode 100644 index 0000000000..e7b2bae204 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/init.lua @@ -0,0 +1,258 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local zigbee_constants = require "st.zigbee.constants" +local capabilities = require "st.capabilities" + +local clusters = require "st.zigbee.zcl.clusters" +local SimpleMetering = clusters.SimpleMetering +local ElectricalMeasurement = clusters.ElectricalMeasurement +local utils = require "frient.utils" + +local data_types = require "st.zigbee.data_types" +local LAST_REPORT_TIME = "LAST_REPORT_TIME" +local SIMPLE_METERING_DEFAULT_DIVISOR = 1000 + +local AC_VOLTAGE_MULTIPLIER_KEY = "_electrical_measurement_ac_voltage_multiplier" +local AC_CURRENT_MULTIPLIER_KEY = "_electrical_measurement_ac_current_multiplier" +local AC_VOLTAGE_DIVISOR_KEY = "_electrical_measurement_ac_voltage_divisor" +local AC_CURRENT_DIVISOR_KEY = "_electrical_measurement_ac_current_divisor" + +local CurrentSummationReceived = 0x0001 + +local ATTRIBUTES = { + { + cluster = SimpleMetering.ID, + attribute = CurrentSummationReceived, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint48, + reportable_change = 1 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.ActivePowerPhB.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Int16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.ActivePowerPhC.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Int16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSVoltage.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSVoltagePhB.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSVoltagePhC.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSCurrent.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSCurrentPhB.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSCurrentPhC.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 5 + } +} + +local device_init = function(self, device) + for _, attribute in ipairs(ATTRIBUTES) do + device:add_configured_attribute(attribute) + end + + if device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == nil then + device:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, SIMPLE_METERING_DEFAULT_DIVISOR, { persist = true }) + end +end + +local do_configure = function(self, device) + device:refresh() + device:configure() + + -- Divisor and multipler for PowerMeter + device:send(SimpleMetering.attributes.Divisor:read(device)) + device:send(SimpleMetering.attributes.Multiplier:read(device)) +end + +local instantaneous_demand_handler = function(driver, device, value, zb_rx) + local raw_value = value.value + local multiplier = device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 + local divisor = device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) or SIMPLE_METERING_DEFAULT_DIVISOR + + raw_value = raw_value * multiplier / divisor * 1000 + + -- The result is already in watts, no need to multiply by 1000 + device:emit_event(capabilities.powerMeter.power({ value = raw_value, unit = "W" })) +end + +local current_summation_delivered_handler = function(driver, device, value, zb_rx) + local raw_value = value.value + + -- Handle potential overflow values + if raw_value < 0 or raw_value >= 0xFFFFFFFFFFFF then + return + end + + local multiplier = device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 + local divisor = device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) or SIMPLE_METERING_DEFAULT_DIVISOR + + raw_value = raw_value * multiplier / divisor * 1000 + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, capabilities.energyMeter.energy({ value = raw_value, unit = "Wh" })) + + 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 - current_power_consumption.energy, 0.0) + end + + local current_time = os.time() + local last_report_time = device:get_field(LAST_REPORT_TIME) or 0 + local next_report_time = last_report_time + 60 * 15 -- 15 mins, the minimum interval allowed between reports + if current_time < next_report_time then + return + end + + device:emit_event_for_endpoint( + zb_rx.address_header.src_endpoint.value, + capabilities.powerConsumptionReport.powerConsumption({ + start = utils.epoch_to_iso8601(last_report_time), + ["end"] = utils.epoch_to_iso8601(current_time - 1), + deltaEnergy = delta_energy, + energy = raw_value + }) + ) + device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) +end + +local current_summation_received_handler = function(driver, device, value, zb_rx) + local raw_value = value.value + + -- Handle potential overflow values + if raw_value < 0 or raw_value >= 0xFFFFFFFFFFFF then + return + end + + local multiplier = device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 + local divisor = device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) or 1000 + + raw_value = raw_value * multiplier / divisor * 1000 + device:emit_component_event(device.profile.components['production'], capabilities.energyMeter.energy({ value = raw_value, unit = "Wh" })) +end + +local electrical_measurement_ac_voltage_multiplier_handler = function(driver, device, multiplier, zb_rx) + local raw_value = multiplier.value + device:set_field(AC_VOLTAGE_MULTIPLIER_KEY, raw_value, { persist = true }) +end + +local electrical_measurement_ac_voltage_divisor_handler = function(driver, device, divisor, zb_rx) + local raw_value = divisor.value + if raw_value == 0 then + return + end + device:set_field(AC_VOLTAGE_DIVISOR_KEY, raw_value, { persist = true }) +end + +local electrical_measurement_ac_current_multiplier_handler = function(driver, device, multiplier, zb_rx) + local raw_value = multiplier.value + device:set_field(AC_CURRENT_MULTIPLIER_KEY, raw_value, { persist = true }) +end + +local electrical_measurement_ac_current_divisor_handler = function(driver, device, divisor, zb_rx) + local raw_value = divisor.value + if raw_value == 0 then + return + end + device:set_field(AC_CURRENT_DIVISOR_KEY, raw_value, { persist = true }) +end + +local measurement_handler = function(component, multiplier_key, divisor_key, emit_fn, unit) + local handler = function(driver, device, value, zb_rx) + local raw_value = value.value + -- By default emit raw value + local multiplier = device:get_field(multiplier_key) or 1 + local divisor = device:get_field(divisor_key) or 1 + + raw_value = raw_value * multiplier / divisor + + device:emit_component_event(device.profile.components[component], emit_fn({ value = raw_value, unit = unit })) + end + + return handler +end + +local frient_emi = { + NAME = "EMIZB-151", + lifecycle_handlers = { + init = device_init, + doConfigure = do_configure + }, + zigbee_handlers = { + cluster = { + }, + attr = { + [SimpleMetering.ID] = { + [CurrentSummationReceived] = current_summation_received_handler, + [SimpleMetering.attributes.CurrentSummationDelivered.ID] = current_summation_delivered_handler, + [SimpleMetering.attributes.InstantaneousDemand.ID] = instantaneous_demand_handler, + }, + [ElectricalMeasurement.ID] = { + [ElectricalMeasurement.attributes.ACVoltageDivisor.ID] = electrical_measurement_ac_voltage_divisor_handler, + [ElectricalMeasurement.attributes.ACVoltageMultiplier.ID] = electrical_measurement_ac_voltage_multiplier_handler, + [ElectricalMeasurement.attributes.ACCurrentDivisor.ID] = electrical_measurement_ac_current_divisor_handler, + [ElectricalMeasurement.attributes.ACCurrentMultiplier.ID] = electrical_measurement_ac_current_multiplier_handler, + [ElectricalMeasurement.attributes.ActivePower.ID] = measurement_handler("phaseA", zigbee_constants.ELECTRICAL_MEASUREMENT_MULTIPLIER_KEY, zigbee_constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, capabilities.powerMeter.power, "W"), + [ElectricalMeasurement.attributes.RMSVoltage.ID] = measurement_handler("phaseA", AC_VOLTAGE_MULTIPLIER_KEY, AC_VOLTAGE_DIVISOR_KEY, capabilities.voltageMeasurement.voltage, "V"), + [ElectricalMeasurement.attributes.RMSCurrent.ID] = measurement_handler("phaseA", AC_CURRENT_MULTIPLIER_KEY, AC_CURRENT_DIVISOR_KEY, capabilities.currentMeasurement.current, "A"), + [ElectricalMeasurement.attributes.ActivePowerPhB.ID] = measurement_handler("phaseB", zigbee_constants.ELECTRICAL_MEASUREMENT_MULTIPLIER_KEY, zigbee_constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, capabilities.powerMeter.power, "W"), + [ElectricalMeasurement.attributes.RMSVoltagePhB.ID] = measurement_handler("phaseB", AC_VOLTAGE_MULTIPLIER_KEY, AC_VOLTAGE_DIVISOR_KEY, capabilities.voltageMeasurement.voltage, "V"), + [ElectricalMeasurement.attributes.RMSCurrentPhB.ID] = measurement_handler("phaseB", AC_CURRENT_MULTIPLIER_KEY, AC_CURRENT_DIVISOR_KEY, capabilities.currentMeasurement.current, "A"), + [ElectricalMeasurement.attributes.ActivePowerPhC.ID] = measurement_handler("phaseC", zigbee_constants.ELECTRICAL_MEASUREMENT_MULTIPLIER_KEY, zigbee_constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, capabilities.powerMeter.power, "W"), + [ElectricalMeasurement.attributes.RMSVoltagePhC.ID] = measurement_handler("phaseC", AC_VOLTAGE_MULTIPLIER_KEY, AC_VOLTAGE_DIVISOR_KEY, capabilities.voltageMeasurement.voltage, "V"), + [ElectricalMeasurement.attributes.RMSCurrentPhC.ID] = measurement_handler("phaseC", AC_CURRENT_MULTIPLIER_KEY, AC_CURRENT_DIVISOR_KEY, capabilities.currentMeasurement.current, "A") + } + } + }, + can_handle = require("frient.EMIZB-151.can_handle") +} + +return frient_emi \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/fingerprints.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/fingerprints.lua index 5bc09f600d..1e00c4434e 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/frient/fingerprints.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/fingerprints.lua @@ -2,8 +2,10 @@ -- Licensed under the Apache License, Version 2.0 local ZIGBEE_POWER_METER_FINGERPRINTS = { - { model = "ZHEMI101" }, - { model = "EMIZB-132" }, + { model = "ZHEMI101", }, + { model = "EMIZB-132", }, + { model = "EMIZB-141", MIN_BAT = 2.3 , MAX_BAT = 3.0 }, + { model = "EMIZB-151", } } 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 5933faf5cb..e87e3f909d 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/init.lua @@ -1,28 +1,184 @@ --- Copyright 2025 SmartThings, Inc. +-- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local zigbee_constants = require "st.zigbee.constants" +local capabilities = require "st.capabilities" +local cluster_base = require "st.zigbee.cluster_base" +local battery_defaults = require "st.zigbee.defaults.battery_defaults" -local constants = require "st.zigbee.constants" -local configurations = require "configurations" +local clusters = require "st.zigbee.zcl.clusters" +local SimpleMetering = clusters.SimpleMetering +local PowerConfiguration = clusters.PowerConfiguration +local utils = require "frient.utils" +local LAST_REPORT_TIME = "LAST_REPORT_TIME" +local data_types = require "st.zigbee.data_types" -local do_configure = function(self, device) +local log = require "log" +local DEVELCO_MANUFACTURER_CODE = 0x1015 +local SIMPLE_METERING_DEFAULT_DIVISOR = 1000 + +local ZIGBEE_POWER_METER_FINGERPRINTS = require("frient.fingerprints") + +local device_init = function(self, device) + for _, fingerprint in ipairs(ZIGBEE_POWER_METER_FINGERPRINTS) do + if device:get_model() == fingerprint.model and fingerprint.MIN_BAT then + battery_defaults.build_linear_voltage_init(fingerprint.MIN_BAT, fingerprint.MAX_BAT)(self, device) + end + end +end + +local do_refresh = function(self, device) device:refresh() + if device:supports_capability(capabilities.battery) then + device:send(PowerConfiguration.attributes.BatteryVoltage:read(device)) + end +end + +local do_configure = function(self, device) device:configure() + for _, fingerprint in ipairs(ZIGBEE_POWER_METER_FINGERPRINTS) do + if device:get_model() == fingerprint.model and device.preferences then + -- Only write manufacturer-specific attributes when preferences exist for this device. + if device.preferences.pulseConfiguration ~= nil then + local pulseConfiguration = tonumber(device.preferences.pulseConfiguration) or 1000 + device:send(cluster_base.write_manufacturer_specific_attribute(device, SimpleMetering.ID, 0x0300, DEVELCO_MANUFACTURER_CODE, data_types.Uint16, pulseConfiguration):to_endpoint(0x02)) + end + + if device.preferences.currentSummation ~= nil then + local currentSummation = tonumber(device.preferences.currentSummation) or 0 + device:send(cluster_base.write_manufacturer_specific_attribute(device, SimpleMetering.ID, 0x0301, DEVELCO_MANUFACTURER_CODE, data_types.Uint48, currentSummation):to_endpoint(0x02)) + end + end + end + + -- Divisor and multipler for PowerMeter + device:send(SimpleMetering.attributes.Divisor:read(device)) + device:send(SimpleMetering.attributes.Multiplier:read(device)) + + device.thread:call_with_delay(5, function() + do_refresh(self, device) + end) end -local device_init = function(self, device) - device:set_field(constants.SIMPLE_METERING_DIVISOR_KEY, 1000, {persist = true}) - device:set_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, 10000, {persist = true}) +local function info_changed(driver, device, event, args) + for name, value in pairs(device.preferences) do + if (device.preferences[name] ~= nil and args.old_st_store.preferences[name] ~= device.preferences[name]) then + if (name == "pulseConfiguration") then + local pulseConfiguration = tonumber(device.preferences.pulseConfiguration) + device:send(cluster_base.write_manufacturer_specific_attribute(device, SimpleMetering.ID, 0x0300, DEVELCO_MANUFACTURER_CODE, data_types.Uint16, pulseConfiguration):to_endpoint(0x02)) + end + if (name == "currentSummation") then + local currentSummation = tonumber(device.preferences.currentSummation) + device:send(cluster_base.write_manufacturer_specific_attribute(device, SimpleMetering.ID, 0x0301, DEVELCO_MANUFACTURER_CODE, data_types.Uint48, currentSummation):to_endpoint(0x02)) + end + end + end + device.thread:call_with_delay(5, function() + do_refresh(driver, device) + end) +end + +local function simple_metering_divisor_handler(driver, device, divisor, zb_rx) + local new_divisor = SIMPLE_METERING_DEFAULT_DIVISOR + local header = zb_rx.body and zb_rx.body.zcl_header + if header and header.frame_ctrl:is_mfg_specific_set() then + log.debug_with({ hub_logs = true }, string.format("Ignoring manufacturer-specific divisor report: %s", tostring(divisor.value))) + elseif (divisor.value and divisor.value == 0) then + log.warn_with({ hub_logs = true }, "Simple metering divisor reported as 0; forcing divisor to 1000") + elseif (divisor.value and divisor.value > 0) then + new_divisor = divisor.value + end + device:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, new_divisor, { persist = true }) +end + +local function instantaneous_demand_handler(driver, device, value, zb_rx) + local raw_value = value.value + --- demand = demand received * Multipler/Divisor + local multiplier = device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 + local divisor = device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) or SIMPLE_METERING_DEFAULT_DIVISOR + if raw_value < -8388607 or raw_value >= 8388607 then + raw_value = 0 + end + + raw_value = raw_value * multiplier / divisor * 1000 + + local raw_value_watts = raw_value + device:emit_event(capabilities.powerMeter.power({ value = raw_value_watts, unit = "W" })) +end + +local function energy_meter_handler(driver, device, value, zb_rx) + local raw_value = value.value + local multiplier = device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 + local divisor = device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) or SIMPLE_METERING_DEFAULT_DIVISOR + + if raw_value < 0 or raw_value >= 0xFFFFFFFFFFFF then + return + end + + raw_value = (raw_value * multiplier) / divisor + + local offset = device:get_field(zigbee_constants.ENERGY_METER_OFFSET) or 0 + if raw_value < 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(zigbee_constants.ENERGY_METER_OFFSET, offset, { persist = true }) + end + raw_value = raw_value - offset + raw_value = raw_value * 1000 -- the unit of these values should be 'Wh' + + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, capabilities.energyMeter.energy({ value = raw_value, unit = "Wh" })) + + 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 - current_power_consumption.energy, 0.0) + end + + local current_time = os.time() + local last_report_time = device:get_field(LAST_REPORT_TIME) or 0 + local next_report_time = last_report_time + 60 * 15 -- 15 mins, the minimum interval allowed between reports + if current_time < next_report_time then + return + end + + device:emit_event_for_endpoint( + zb_rx.address_header.src_endpoint.value, + capabilities.powerConsumptionReport.powerConsumption({ + start = utils.epoch_to_iso8601(last_report_time), + ["end"] = utils.epoch_to_iso8601(current_time - 1), + deltaEnergy = delta_energy, + energy = raw_value + }) + ) + device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) end local frient_power_meter_handler = { NAME = "frient power meter handler", lifecycle_handlers = { - init = configurations.power_reconfig_wrapper(device_init), + init = device_init, doConfigure = do_configure, + infoChanged = info_changed + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zigbee_handlers = { + cluster = { + }, + attr = { + [SimpleMetering.ID] = { + [SimpleMetering.attributes.CurrentSummationDelivered.ID] = energy_meter_handler, + [SimpleMetering.attributes.InstantaneousDemand.ID] = instantaneous_demand_handler, + [SimpleMetering.attributes.Divisor.ID] = simple_metering_divisor_handler + } + } }, + sub_drivers = require("frient.sub_drivers"), can_handle = require("frient.can_handle"), } diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/sub_drivers.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/sub_drivers.lua new file mode 100644 index 0000000000..3698af85d7 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2026 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.EMIZB-151") +} + +return sub_drivers diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/utils.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/utils.lua new file mode 100644 index 0000000000..70e7684855 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/utils.lua @@ -0,0 +1,10 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local utils = {} + +utils.epoch_to_iso8601 = function(time) + return os.date("!%Y-%m-%dT%H:%M:%SZ", time) +end + +return utils \ 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 f15fae7905..6aa3d4b8c1 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/init.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/init.lua @@ -41,6 +41,7 @@ local zigbee_power_meter_driver_template = { capabilities.powerMeter, capabilities.energyMeter, capabilities.powerConsumptionReport, + capabilities.battery, }, zigbee_handlers = { global = { diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_battery_consumption_report.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_battery_consumption_report.lua new file mode 100644 index 0000000000..6a312c1906 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_battery_consumption_report.lua @@ -0,0 +1,299 @@ +-- Copyright 2026 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 ElectricalMeasurement = clusters.ElectricalMeasurement +local SimpleMetering = clusters.SimpleMetering +local PowerConfiguration = clusters.PowerConfiguration +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 DEVELCO_MANUFACTURER_CODE = 0x1015 +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("frient-power-energy-battery-consumption-report.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + model = "EMIZB-141", + server_clusters = { ElectricalMeasurement.ID, PowerConfiguration.ID, SimpleMetering.ID } + } + } + } +) + + +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( + "InstantaneousDemand Report should be handled.", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }, + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.InstantaneousDemand:build_test_attr_report(mock_device, 2700) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 2700.0, unit = "W" })) + } + } +) + +test.register_coroutine_test( + "lifecycle configure event should configure the 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, + cluster_base.write_manufacturer_specific_attribute(mock_device, SimpleMetering.ID, 0x0300, DEVELCO_MANUFACTURER_CODE, data_types.Uint16, 1000):to_endpoint(0x02) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, SimpleMetering.ID, 0x0301, DEVELCO_MANUFACTURER_CODE, data_types.Uint48, 0):to_endpoint(0x02) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Divisor:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Multiplier: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, + PowerConfiguration.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, + 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.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, + 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.CurrentSummationDelivered:configure_reporting( + mock_device, 5, 3600, 1 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:configure_reporting( + mock_device, 30, 21600, 1 + ) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +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, SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, SimpleMetering.attributes.InstantaneousDemand:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ActivePower:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) } + } + }, + { + inner_block_ordering = "relaxed" + } +) + +test.register_coroutine_test( + "infochanged to check for necessary preferences settings: pulseConfiguration, currentSummation", + function() + local updates = { + preferences = { + pulseConfiguration = 400, + currentSummation = 500 + } + } + + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute( + mock_device, + SimpleMetering.ID, + 0x0300, + DEVELCO_MANUFACTURER_CODE, + data_types.Uint16, + 400 + ):to_endpoint(0x02) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute( + mock_device, + SimpleMetering.ID, + 0x0301, + DEVELCO_MANUFACTURER_CODE, + data_types.Uint48, + 500 + ):to_endpoint(0x02) + }) + + test.socket.zigbee:__set_channel_ordering("relaxed") + + end +) + +test.register_coroutine_test( + "CurrentSummationDelivered Report should be handled.", + function() + local current_time = os.time() - 60 * 16 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + start = "1969-12-31T23:44:00Z", + ["end"] = "1969-12-31T23:59:59Z", + deltaEnergy = 0.0, + energy = 2700.0 + }) + ) + ) + end +) + +test.register_coroutine_test( + "CurrentSummationDelivered report should be handled without powerConsumptionReport because 15 min didn't pass since last report", + function() + local current_time = os.time() - 60 * 14 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_consumption_report.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_consumption_report.lua new file mode 100644 index 0000000000..2679329fd3 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_consumption_report.lua @@ -0,0 +1,272 @@ +-- Copyright 2026 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 ElectricalMeasurement = clusters.ElectricalMeasurement +local SimpleMetering = clusters.SimpleMetering +local PowerConfiguration = clusters.PowerConfiguration +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 DEVELCO_MANUFACTURER_CODE = 0x1015 +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("frient-power-energy-consumption-report.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + model = "ZHEMI101", + server_clusters = { ElectricalMeasurement.ID, PowerConfiguration.ID, SimpleMetering.ID } + } + } + } +) + + +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( + "InstantaneousDemand Report should be handled.", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }, + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.InstantaneousDemand:build_test_attr_report(mock_device, 2700) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 2700.0, unit = "W" })) + } + } +) + +test.register_coroutine_test( + "lifecycle configure event should configure the 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, + cluster_base.write_manufacturer_specific_attribute(mock_device, SimpleMetering.ID, 0x0300, DEVELCO_MANUFACTURER_CODE, data_types.Uint16, 1000):to_endpoint(0x02) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, SimpleMetering.ID, 0x0301, DEVELCO_MANUFACTURER_CODE, data_types.Uint48, 0):to_endpoint(0x02) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Divisor:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Multiplier: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, + 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.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, + 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.CurrentSummationDelivered:configure_reporting( + mock_device, 5, 3600, 1 + ) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +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, SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, SimpleMetering.attributes.InstantaneousDemand:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ActivePower:read(mock_device) } + } + }, + { + inner_block_ordering = "relaxed" + } +) + +test.register_coroutine_test( + "infochanged to check for necessary preferences settings: pulseConfiguration, currentSummation", + function() + local updates = { + preferences = { + pulseConfiguration = 400, + currentSummation = 500 + } + } + + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute( + mock_device, + SimpleMetering.ID, + 0x0300, + DEVELCO_MANUFACTURER_CODE, + data_types.Uint16, + 400 + ):to_endpoint(0x02) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute( + mock_device, + SimpleMetering.ID, + 0x0301, + DEVELCO_MANUFACTURER_CODE, + data_types.Uint48, + 500 + ):to_endpoint(0x02) + }) + + test.socket.zigbee:__set_channel_ordering("relaxed") + + end +) + +test.register_coroutine_test( + "CurrentSummationDelivered Report should be handled.", + function() + local current_time = os.time() - 60 * 16 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + start = "1969-12-31T23:44:00Z", + ["end"] = "1969-12-31T23:59:59Z", + deltaEnergy = 0.0, + energy = 2700.0 + }) + ) + ) + end +) + +test.register_coroutine_test( + "CurrentSummationDelivered report should be handled without powerConsumptionReport because 15 min didn't pass since last report", + function() + local current_time = os.time() - 60 * 14 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_current_voltage.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_current_voltage.lua new file mode 100644 index 0000000000..75f9da6961 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_current_voltage.lua @@ -0,0 +1,361 @@ +-- Copyright 2026 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 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 CurrentSummationReceived = 0x0001 +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local zigbee_constants = require "st.zigbee.constants" +zigbee_constants.ELECTRICAL_MEASUREMENT_AC_VOLTAGE_MULTIPLIER_KEY = "_electrical_measurement_ac_voltage_multiplier" +zigbee_constants.ELECTRICAL_MEASUREMENT_AC_CURRENT_MULTIPLIER_KEY = "_electrical_measurement_ac_current_multiplier" +zigbee_constants.ELECTRICAL_MEASUREMENT_AC_VOLTAGE_DIVISOR_KEY = "_electrical_measurement_ac_voltage_divisor" +zigbee_constants.ELECTRICAL_MEASUREMENT_AC_CURRENT_DIVISOR_KEY = "_electrical_measurement_ac_current_divisor" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("frient-power-energy-current-voltage.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + model = "EMIZB-151", + server_clusters = { ElectricalMeasurement.ID, SimpleMetering.ID } + } + } + } +) + +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) + +local function expected_refresh_commands() + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_attribute( + mock_device, + data_types.ClusterId(SimpleMetering.ID), + CurrentSummationReceived + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered: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, + 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.RMSCurrent: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.RMSCurrentPhC: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.RMSVoltagePhB: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.ActivePower: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.ActivePowerPhC:read(mock_device) + }) +end + + + + +test.register_coroutine_test( + "Refresh should read all necessary attributes", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", command = "refresh", args = {} } }) + + expected_refresh_commands() + end +) + +test.register_coroutine_test( + "ALl reports (for all phases) should be handled properly", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ACVoltageMultiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ACVoltageDivisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ACCurrentMultiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ACCurrentDivisor:build_test_attr_report(mock_device, 1000) }) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 30) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 30.0, unit = "Wh"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.InstantaneousDemand:build_test_attr_report(mock_device, 40) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 40.0, unit = "W"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_device, 50) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseA", capabilities.powerMeter.power({ value = 50.0, unit = "W"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.RMSVoltage:build_test_attr_report(mock_device, 50) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseA", capabilities.voltageMeasurement.voltage({ value = 0.05, unit = "V"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.RMSCurrent:build_test_attr_report(mock_device, 60) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseA", capabilities.currentMeasurement.current({ value = 0.06, unit = "A"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ActivePowerPhB:build_test_attr_report(mock_device, 70) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseB", capabilities.powerMeter.power({ value = 70.0, unit = "W"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.RMSVoltagePhB:build_test_attr_report(mock_device, 80) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseB", capabilities.voltageMeasurement.voltage({ value = 0.08, unit = "V"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.RMSCurrentPhB:build_test_attr_report(mock_device, 90) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseB", capabilities.currentMeasurement.current({ value = 0.09, unit = "A"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ActivePowerPhC:build_test_attr_report(mock_device, 100) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseC", capabilities.powerMeter.power({ value = 100.0, unit = "W"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.RMSVoltagePhC:build_test_attr_report(mock_device, 110) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseC", capabilities.voltageMeasurement.voltage({ value = 0.11, unit = "V"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.RMSCurrentPhC:build_test_attr_report(mock_device, 120) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseC", capabilities.currentMeasurement.current({ value = 0.12, unit = "A"})) + ) + + end +) + +test.register_coroutine_test( + "CurrentSummationDelivered Report should be handled.", + function() + local current_time = os.time() - 60 * 16 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + start = "1969-12-31T23:44:00Z", + ["end"] = "1969-12-31T23:59:59Z", + deltaEnergy = 0.0, + energy = 2700.0 + }) + ) + ) + end +) + +test.register_coroutine_test( + "CurrentSummationDelivered report should be handled without powerConsumptionReport because 15 min didn't pass since last report", + function() + local current_time = os.time() - 60 * 14 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + end +) + +test.register_coroutine_test( + "lifecycle configure event should configure the device", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + + expected_refresh_commands() + + 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, + 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.RMSCurrent:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrentPhB:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrentPhC:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltage:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltagePhB:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltagePhC:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + 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, + ElectricalMeasurement.attributes.ActivePowerPhB:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePowerPhC: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, + SimpleMetering.attributes.Divisor:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Multiplier:read(mock_device) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.configure_reporting( + mock_device, + data_types.ClusterId(SimpleMetering.ID), + data_types.AttributeId(CurrentSummationReceived), + data_types.ZigbeeDataType(data_types.Uint48.ID), + 5, + 3600, + 1 + ) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua index 56b2f45114..1409c8e749 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua @@ -5,12 +5,15 @@ 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 constants = require "st.zigbee.constants" +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + local mock_device = test.mock_device.build_test_zigbee_device({ - profile = t_utils.get_profile_definition("power-meter.yml"), + profile = t_utils.get_profile_definition("frient-power-meter-consumption-report.yml"), zigbee_endpoints = { [1] = { id = 1, @@ -35,14 +38,115 @@ test.register_coroutine_test( function() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) test.wait_for_events() + end +) + +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, SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, SimpleMetering.attributes.InstantaneousDemand:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ActivePower:read(mock_device) } + } + }, + { + inner_block_ordering = "relaxed" + } +) + +test.register_coroutine_test( + "frient instantaneous demand report emits power", + function() + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.InstantaneousDemand:build_test_attr_report(mock_device, 40) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 40.0, unit = "W" })) + ) + end +) + +test.register_coroutine_test( + "frient current summation delivered emits energy and consumption report", + function() + local current_time = os.time() - 60 * 16 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + start = "1969-12-31T23:44:00Z", + ["end"] = "1969-12-31T23:59:59Z", + deltaEnergy = 0.0, + energy = 2700.0 + }) + ) + ) + end +) + +test.register_coroutine_test( + "frient current summation delivered skips consumption report when interval is short", + function() + local current_time = os.time() - 60 * 14 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + end +) + +test.register_coroutine_test( + "frient divisor report updates divisor field", + function() + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 0) }) + test.wait_for_events() assert(mock_device:get_field(constants.SIMPLE_METERING_DIVISOR_KEY) == 1000, "SIMPLE_METERING_DIVISOR_KEY should be 1000") - assert(mock_device:get_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY) == 10000, - "ELECTRICAL_MEASUREMENT_DIVISOR_KEY should be 10000") - end, - { - min_api_version = 19 - } + end ) test.register_coroutine_test( @@ -50,18 +154,6 @@ test.register_coroutine_test( 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, - SimpleMetering.attributes.InstantaneousDemand:read(mock_device) - }) - 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, zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, SimpleMetering.ID) @@ -84,19 +176,19 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) + ElectricalMeasurement.attributes.ACPowerMultiplier:configure_reporting(mock_device, 1, 43200, 1) }) test.socket.zigbee:__expect_send({ mock_device.id, - ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) + 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) + SimpleMetering.attributes.Divisor:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, - ElectricalMeasurement.attributes.ACPowerDivisor:configure_reporting(mock_device, 1, 43200, 1) + SimpleMetering.attributes.Multiplier:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, From 059fac822db0ce0032069c2f5a105309a8bb9a64 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 9 Mar 2026 14:46:42 -0700 Subject: [PATCH 156/538] WWSTECERT-10553 Hager matter 2 buttons (battery) WWSTCERT-10556 Hager matter 4 buttons (battery) --- drivers/SmartThings/matter-switch/fingerprints.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 1c8adddff4..20a57fc8f0 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -753,6 +753,17 @@ matterManufacturer: vendorId: 0x1387 productId: 0x6094 deviceProfileName: light-color-level +# Hager + - id: "4741/8" + deviceLabel: Hager matter 2 buttons (battery) + vendorId: 0x1285 + productId: 0x0008 + deviceProfileName: 2-button-batteryLevel + - id: "4741/9" + deviceLabel: Hager matter 4 buttons (battery) + vendorId: 0x1285 + productId: 0x0009 + deviceProfileName: 4-button-batteryLevel # Hue - id: "4107/2049" deviceLabel: Hue W 1600 A21 E26 1P NAM From b0cfc99572e51dede9b3f0bbebe9f71a82d690a1 Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 11 Mar 2026 19:15:29 +0900 Subject: [PATCH 157/538] Update digital key event (#2829) Signed-off-by: Hunsup Jung --- .../SmartThings/matter-lock/src/new-matter-lock/init.lua | 6 +++--- .../matter-lock/src/test/test_new_matter_lock.lua | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) 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 11aa2b0884..1f1407a96b 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -2449,11 +2449,11 @@ local function lock_op_event_handler(driver, device, ib, response) elseif opSource.value == Source.RFID then opSource = "rfid" elseif opSource.value == Source.BIOMETRIC then - opSource = "keypad" + opSource = nil -- It will be updated R2 elseif opSource.value == Source.ALIRO then - opSource = nil + opSource = "digitalKey" else - opSource =nil + opSource = nil end if userIdx ~= nil then diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 4388c61a4a..e760d77e5d 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -921,7 +921,7 @@ test.register_message_test( message = mock_device:generate_test_message( "main", capabilities.lock.lock.unlocked( - {data = {method = "keypad", userIndex = 1}, state_change = true} + {data = {userIndex = 1}, state_change = true} ) ), }, @@ -948,7 +948,7 @@ test.register_message_test( message = mock_device:generate_test_message( "main", capabilities.lock.lock.unlocked( - {data = {userIndex = 1}, state_change = true} + {data = {method = "digitalKey", userIndex = 1}, state_change = true} ) ), } From 55d5a10b85b2a5b3c8ccf0db0bc52a401db72f56 Mon Sep 17 00:00:00 2001 From: Sanghee Kim Date: Wed, 11 Mar 2026 18:34:15 +0900 Subject: [PATCH 158/538] Matter Ikea Dual Button: Added missing refresh on embedded device config --- .../matter-switch/profiles/ikea-2-button-battery.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml b/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml index 0b256f3b65..011ecc1683 100644 --- a/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml +++ b/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml @@ -36,6 +36,9 @@ deviceConfig: - component: main capability: battery version: 1 + - component: main + capability: refresh + version: 1 - component: button2 capability: button version: 1 From 3056caf8756ded78154ba04f419eea8feb7cb278 Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:50:51 +0900 Subject: [PATCH 159/538] Postpone updating commandResult (#2765) Signed-off-by: Hunsup Jung --- .../matter-lock/src/new-matter-lock/init.lua | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) 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 1f1407a96b..00aed22adf 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -1609,17 +1609,6 @@ local function set_pin_response_handler(driver, device, ib, response) add_credential_to_table(device, userIdx, credIdx, "pin") end - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - credentialIndex = credIdx, - statusCode = status - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - -- If User Type is Guest and device support schedule, add default schedule local week_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.WEEK_DAY_ACCESS_SCHEDULES}) local year_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.YEAR_DAY_ACCESS_SCHEDULES}) @@ -1643,6 +1632,16 @@ local function set_pin_response_handler(driver, device, ib, response) ) ) else + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + credentialIndex = credIdx, + statusCode = status + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) end return @@ -2318,6 +2317,20 @@ local function set_year_day_schedule_handler(driver, device, ib, response) end if cmdName == "defaultSchedule" then + local cmdName = "addCredential" + local credIdx = device:get_field(lock_utils.CRED_INDEX) + + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + credentialIndex = credIdx, + statusCode = status + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) return end From 2ba57d2e38284a20a80eb220885122b88f2bdfaa Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:32:16 +0900 Subject: [PATCH 160/538] Limit the length of User Name (#2764) Signed-off-by: Hunsup Jung --- ...-Rename-the-variable-and-reduce-code.patch | 77 +++++++++++++++++++ .../matter-lock/src/new-matter-lock/init.lua | 40 ++++++---- .../src/test/test_new_matter_lock.lua | 8 +- 3 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 0001-Rename-the-variable-and-reduce-code.patch diff --git a/0001-Rename-the-variable-and-reduce-code.patch b/0001-Rename-the-variable-and-reduce-code.patch new file mode 100644 index 0000000000..f52f7882ef --- /dev/null +++ b/0001-Rename-the-variable-and-reduce-code.patch @@ -0,0 +1,77 @@ +From f19da9191362d70a020ab6852233eb327c19f906 Mon Sep 17 00:00:00 2001 +From: Hunsup Jung +Date: Sat, 7 Mar 2026 15:36:45 +0900 +Subject: [PATCH] Rename the variable and reduce code + +Signed-off-by: Hunsup Jung +--- + .../matter-lock/src/new-matter-lock/init.lua | 25 +++++++++---------- + 1 file changed, 12 insertions(+), 13 deletions(-) + +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 d9b9c9f6..37a9493a 100644 +--- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua ++++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +@@ -29,7 +29,8 @@ local PowerSource = clusters.PowerSource + + local INITIAL_CREDENTIAL_INDEX = 1 + local ALL_INDEX = 0xFFFE +-local NAME_MAX_L = 10 ++-- maximum as defined by the Matter specification ++local MAX_USER_NAME_LENGTH = 10 + local MIN_EPOCH_S = 0 + local MAX_EPOCH_S = 0xffffffff + local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 +@@ -1274,10 +1275,7 @@ local function handle_update_user(driver, device, command) + local cmdName = "updateUser" + local userIdx = command.args.userIndex + local userName = command.args.userName +- local userNameMatter = userName +- if #userNameMatter > NAME_MAX_L then +- userNameMatter = string.sub(userNameMatter, 1, NAME_MAX_L) +- end ++ local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) + local userType = command.args.userType + local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER + if userType == "guest" then +@@ -1307,7 +1305,7 @@ local function handle_update_user(driver, device, command) + device:send( + DoorLock.server.commands.SetUser( + device, ep, +- DoorLock.types.DataOperationTypeEnum.MODIFY, -- Operation Type: Add(0), Modify(2) ++ DoorLock.types.DataOperationTypeEnum.MODIFY, + userIdx, + userNameMatter, + nil, -- Unique ID +@@ -1355,6 +1353,7 @@ local function get_user_response_handler(driver, device, ib, response) + -- Found available user index + if status == nil or status == DoorLock.types.UserStatusEnum.AVAILABLE then + local userName = device:get_field(lock_utils.USER_NAME) ++ local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) + local userType = device:get_field(lock_utils.USER_TYPE) + local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER + if userType == "guest" then +@@ -1368,13 +1367,13 @@ local function get_user_response_handler(driver, device, ib, response) + device:send( + DoorLock.server.commands.SetUser( + device, ep, +- DoorLock.types.DataOperationTypeEnum.ADD, -- Operation Type: Add(0), Modify(2) +- userIdx, -- User Index +- userName, -- User Name +- nil, -- Unique ID +- nil, -- User Status +- userTypeMatter, -- User Type +- nil -- Credential Rule ++ DoorLock.types.DataOperationTypeEnum.ADD, ++ userIdx, ++ userNameMatter, ++ nil, -- Unique ID ++ nil, -- User Status ++ userTypeMatter, ++ nil -- Credential Rule + ) + ) + elseif userIdx >= maxUser then -- There's no available user index +-- +2.39.5 (Apple Git-154) + 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 00aed22adf..8f2a99fd5d 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -18,6 +18,8 @@ local PowerSource = clusters.PowerSource local INITIAL_CREDENTIAL_INDEX = 1 local ALL_INDEX = 0xFFFE +-- maximum as defined by the Matter specification +local MAX_USER_NAME_LENGTH = 10 local MIN_EPOCH_S = 0 local MAX_EPOCH_S = 0xffffffff local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 @@ -1220,6 +1222,7 @@ local function handle_update_user(driver, device, command) local cmdName = "updateUser" local userIdx = command.args.userIndex local userName = command.args.userName + local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) local userType = command.args.userType local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER if userType == "guest" then @@ -1249,13 +1252,13 @@ local function handle_update_user(driver, device, command) device:send( DoorLock.server.commands.SetUser( device, ep, - DoorLock.types.DataOperationTypeEnum.MODIFY, -- Operation Type: Add(0), Modify(2) - userIdx, -- User Index - userName, -- User Name - nil, -- Unique ID - nil, -- User Status - userTypeMatter, -- User Type - nil -- Credential Rule + DoorLock.types.DataOperationTypeEnum.MODIFY, + userIdx, + userNameMatter, + nil, -- Unique ID + nil, -- User Status + userTypeMatter, + nil -- Credential Rule ) ) end @@ -1297,6 +1300,7 @@ local function get_user_response_handler(driver, device, ib, response) -- Found available user index if status == nil or status == DoorLock.types.UserStatusEnum.AVAILABLE then local userName = device:get_field(lock_utils.USER_NAME) + local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) local userType = device:get_field(lock_utils.USER_TYPE) local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER if userType == "guest" then @@ -1310,13 +1314,13 @@ local function get_user_response_handler(driver, device, ib, response) device:send( DoorLock.server.commands.SetUser( device, ep, - DoorLock.types.DataOperationTypeEnum.ADD, -- Operation Type: Add(0), Modify(2) - userIdx, -- User Index - userName, -- User Name - nil, -- Unique ID - nil, -- User Status - userTypeMatter, -- User Type - nil -- Credential Rule + DoorLock.types.DataOperationTypeEnum.ADD, + userIdx, + userNameMatter, + nil, -- Unique ID + nil, -- User Status + userTypeMatter, + nil -- Credential Rule ) ) elseif userIdx >= maxUser then -- There's no available user index @@ -1477,14 +1481,16 @@ local function handle_add_credential(driver, device, command) -- Get parameters local cmdName = "addCredential" local userIdx = command.args.userIndex - if userIdx == 0 then - userIdx = nil - end local userType = command.args.userType local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER if userType == "guest" then userTypeMatter = DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER end + if userIdx == 0 then + userIdx = nil + else + userTypeMatter = nil + end local credential = { credential_type = DoorLock.types.CredentialTypeEnum.PIN, credential_index = INITIAL_CREDENTIAL_INDEX diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index e760d77e5d..d2ee8f39ba 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -1402,7 +1402,7 @@ test.register_coroutine_test( "654123", -- credential_data 1, -- user_index nil, -- user_status - DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + nil -- user_type ), } ) @@ -1496,7 +1496,7 @@ test.register_coroutine_test( "654123", -- credential_data 1, -- user_index nil, -- user_status - DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + nil -- user_type ), } ) @@ -1552,7 +1552,7 @@ test.register_coroutine_test( "654123", -- credential_data 1, -- user_index nil, -- user_status - DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + nil -- user_type ), } ) @@ -1615,7 +1615,7 @@ test.register_coroutine_test( "654123", -- credential_data 1, -- user_index nil, -- user_status - DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + nil -- user_type ), } ) From 71f5324be13c891e4865553c0d9e3001c3efb5dd Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:41:12 +0900 Subject: [PATCH 161/538] Remove garbage file (#2835) Signed-off-by: Hunsup Jung --- ...-Rename-the-variable-and-reduce-code.patch | 77 ------------------- 1 file changed, 77 deletions(-) delete mode 100644 0001-Rename-the-variable-and-reduce-code.patch diff --git a/0001-Rename-the-variable-and-reduce-code.patch b/0001-Rename-the-variable-and-reduce-code.patch deleted file mode 100644 index f52f7882ef..0000000000 --- a/0001-Rename-the-variable-and-reduce-code.patch +++ /dev/null @@ -1,77 +0,0 @@ -From f19da9191362d70a020ab6852233eb327c19f906 Mon Sep 17 00:00:00 2001 -From: Hunsup Jung -Date: Sat, 7 Mar 2026 15:36:45 +0900 -Subject: [PATCH] Rename the variable and reduce code - -Signed-off-by: Hunsup Jung ---- - .../matter-lock/src/new-matter-lock/init.lua | 25 +++++++++---------- - 1 file changed, 12 insertions(+), 13 deletions(-) - -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 d9b9c9f6..37a9493a 100644 ---- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua -+++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua -@@ -29,7 +29,8 @@ local PowerSource = clusters.PowerSource - - local INITIAL_CREDENTIAL_INDEX = 1 - local ALL_INDEX = 0xFFFE --local NAME_MAX_L = 10 -+-- maximum as defined by the Matter specification -+local MAX_USER_NAME_LENGTH = 10 - local MIN_EPOCH_S = 0 - local MAX_EPOCH_S = 0xffffffff - local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 -@@ -1274,10 +1275,7 @@ local function handle_update_user(driver, device, command) - local cmdName = "updateUser" - local userIdx = command.args.userIndex - local userName = command.args.userName -- local userNameMatter = userName -- if #userNameMatter > NAME_MAX_L then -- userNameMatter = string.sub(userNameMatter, 1, NAME_MAX_L) -- end -+ local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) - local userType = command.args.userType - local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER - if userType == "guest" then -@@ -1307,7 +1305,7 @@ local function handle_update_user(driver, device, command) - device:send( - DoorLock.server.commands.SetUser( - device, ep, -- DoorLock.types.DataOperationTypeEnum.MODIFY, -- Operation Type: Add(0), Modify(2) -+ DoorLock.types.DataOperationTypeEnum.MODIFY, - userIdx, - userNameMatter, - nil, -- Unique ID -@@ -1355,6 +1353,7 @@ local function get_user_response_handler(driver, device, ib, response) - -- Found available user index - if status == nil or status == DoorLock.types.UserStatusEnum.AVAILABLE then - local userName = device:get_field(lock_utils.USER_NAME) -+ local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) - local userType = device:get_field(lock_utils.USER_TYPE) - local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER - if userType == "guest" then -@@ -1368,13 +1367,13 @@ local function get_user_response_handler(driver, device, ib, response) - device:send( - DoorLock.server.commands.SetUser( - device, ep, -- DoorLock.types.DataOperationTypeEnum.ADD, -- Operation Type: Add(0), Modify(2) -- userIdx, -- User Index -- userName, -- User Name -- nil, -- Unique ID -- nil, -- User Status -- userTypeMatter, -- User Type -- nil -- Credential Rule -+ DoorLock.types.DataOperationTypeEnum.ADD, -+ userIdx, -+ userNameMatter, -+ nil, -- Unique ID -+ nil, -- User Status -+ userTypeMatter, -+ nil -- Credential Rule - ) - ) - elseif userIdx >= maxUser then -- There's no available user index --- -2.39.5 (Apple Git-154) - From 4b938b35d551491f1e94d80308db52ba670e39d9 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Fri, 6 Mar 2026 11:49:04 -0600 Subject: [PATCH 162/538] move AttributeList read to subscribe --- .../matter-lock/src/new-matter-lock/init.lua | 40 +++++++++---------- .../src/test/test_matter_lock_modular.lua | 12 +++--- .../src/test/test_new_matter_lock_battery.lua | 9 +++-- 3 files changed, 31 insertions(+), 30 deletions(-) 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 8f2a99fd5d..bc38370d37 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -133,6 +133,11 @@ end local function device_init(driver, device) device:set_component_to_endpoint_fn(component_to_endpoint) + if #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) == 0 then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist = true}) + elseif device:get_field(profiling_data.BATTERY_SUPPORT) == nil then + device:add_subscribed_attribute(clusters.PowerSource.attributes.AttributeList) + end for cap_id, attributes in pairs(subscribed_attributes) do if device:supports_capability_by_id(cap_id) then for _, attr in ipairs(attributes) do @@ -152,12 +157,6 @@ local function device_init(driver, device) local function device_added(driver, device) device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) - 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(device)) - else - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, { persist = true }) - end end local function match_profile_modular(driver, device) @@ -590,23 +589,22 @@ end -- Power Source Attribute List -- --------------------------------- local function handle_power_source_attribute_list(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, { persist = true }) - match_profile(driver, device) - return - elseif attr.value == 0x0E then - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_LEVEL, { persist = true }) - match_profile(driver, device) - return + local latest_battery_support = device:get_field(profiling_data.BATTERY_SUPPORT) + for _, attr in ipairs(ib.data.elements or {}) do + if attr.value == clusters.PowerSource.attributes.BatPercentRemaining.ID then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_PERCENTAGE, {persist=true}) + break -- BATTERY_PERCENTAGE is highest priority. break early if found + elseif attr.value == clusters.PowerSource.attributes.BatChargeLevel.ID then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_LEVEL, {persist=true}) end end - - -- neither BatChargeLevel nor BatPercentRemaining were found. Re-profiling without battery. - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, { persist = true }) - match_profile(driver, device) + -- in the case that 1) no battery has been set, and 2) the returned ib does not include battery attributes, ignore battery + if latest_battery_support == nil and not device:get_field(profiling_data.BATTERY_SUPPORT) then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist=true}) + end + if latest_battery_support == nil or latest_battery_support ~= device:get_field(profiling_data.BATTERY_SUPPORT) then + match_profile(driver, device) + end end ------------------------------- diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index c5600c069b..36d3825cf6 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -208,6 +208,8 @@ local function test_init() subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device)) + -- add test device test.mock_device.add_test_device(mock_device) -- actual onboarding flow @@ -215,7 +217,6 @@ local function test_init() test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device.id, clusters.PowerSource.attributes.AttributeList:read()}) 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" }) @@ -229,6 +230,7 @@ local function test_init_unlatch() subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_unlatch)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_unlatch)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_unlatch)) -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_unlatch) -- actual onboarding flow @@ -236,7 +238,6 @@ local function test_init_unlatch() test.socket.capability:__expect_send( mock_device_unlatch:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_unlatch.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_unlatch.id, "init" }) test.socket.matter:__expect_send({mock_device_unlatch.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_unlatch.id, "doConfigure" }) @@ -256,6 +257,7 @@ local function test_init_user_pin() subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin)) -- add test device test.mock_device.add_test_device(mock_device_user_pin) -- actual onboarding flow @@ -263,7 +265,6 @@ local function test_init_user_pin() test.socket.capability:__expect_send( mock_device_user_pin:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_user_pin.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin.id, "init" }) test.socket.matter:__expect_send({mock_device_user_pin.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin.id, "doConfigure" }) @@ -285,6 +286,7 @@ local function test_init_user_pin_schedule_unlatch() subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin_schedule_unlatch)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin_schedule_unlatch)) -- add test device test.mock_device.add_test_device(mock_device_user_pin_schedule_unlatch) -- actual onboarding flow @@ -292,7 +294,6 @@ local function test_init_user_pin_schedule_unlatch() test.socket.capability:__expect_send( mock_device_user_pin_schedule_unlatch:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_user_pin_schedule_unlatch.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin_schedule_unlatch.id, "init" }) test.socket.matter:__expect_send({mock_device_user_pin_schedule_unlatch.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin_schedule_unlatch.id, "doConfigure" }) @@ -306,6 +307,7 @@ local function test_init_modular() subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_modular)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_modular)) -- add test device test.mock_device.add_test_device(mock_device_modular) -- actual onboarding flow @@ -313,7 +315,6 @@ local function test_init_modular() test.socket.capability:__expect_send( mock_device_modular:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_modular.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_modular.id, "init" }) test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_modular.id, "doConfigure" }) @@ -654,6 +655,7 @@ test.register_coroutine_test( subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_modular)) subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device_modular)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_modular)) test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) test.socket.capability:__expect_send( mock_device_modular:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua index 373c4184ed..ae0f2f6009 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua @@ -170,6 +170,7 @@ local function test_init() subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device)) -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) @@ -178,7 +179,6 @@ local function test_init() test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device.id, clusters.PowerSource.attributes.AttributeList:read()}) 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" }) @@ -192,6 +192,7 @@ local function test_init_unlatch() subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_unlatch)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_unlatch)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_unlatch)) -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_unlatch) test.socket.matter:__expect_send({mock_device_unlatch.id, subscribe_request}) @@ -200,7 +201,6 @@ local function test_init_unlatch() test.socket.capability:__expect_send( mock_device_unlatch:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_unlatch.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_unlatch.id, "init" }) test.socket.matter:__expect_send({mock_device_unlatch.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_unlatch.id, "doConfigure" }) @@ -220,6 +220,7 @@ local function test_init_user_pin() subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin)) -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_user_pin) test.socket.matter:__expect_send({mock_device_user_pin.id, subscribe_request}) @@ -228,7 +229,6 @@ local function test_init_user_pin() test.socket.capability:__expect_send( mock_device_user_pin:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_user_pin.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin.id, "init" }) test.socket.matter:__expect_send({mock_device_user_pin.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin.id, "doConfigure" }) @@ -250,6 +250,8 @@ local function test_init_user_pin_schedule_unlatch() subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin_schedule_unlatch)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin_schedule_unlatch)) + -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_user_pin_schedule_unlatch) test.socket.matter:__expect_send({mock_device_user_pin_schedule_unlatch.id, subscribe_request}) @@ -258,7 +260,6 @@ local function test_init_user_pin_schedule_unlatch() test.socket.capability:__expect_send( mock_device_user_pin_schedule_unlatch:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_user_pin_schedule_unlatch.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin_schedule_unlatch.id, "init" }) test.socket.matter:__expect_send({mock_device_user_pin_schedule_unlatch.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin_schedule_unlatch.id, "doConfigure" }) From a189801d06044ca00fcedeaea47296c2cbdd894c Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 12 Mar 2026 14:03:18 -0700 Subject: [PATCH 163/538] WWSTCERT-10643: Govee TV Light Bar --- drivers/SmartThings/matter-switch/fingerprints.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 20a57fc8f0..f022334fd0 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -753,6 +753,16 @@ matterManufacturer: vendorId: 0x1387 productId: 0x6094 deviceProfileName: light-color-level + - id: "4999/24646" + deviceLabel: Govee TV Light Bar + vendorId: 0x1387 + productId: 0x6046 + deviceProfileName: light-color-level + - id: "4999/24662" + deviceLabel: Govee RGBICWW Light Bar + vendorId: 0x1387 + productId: 0x6056 + deviceProfileName: light-color-level # Hager - id: "4741/8" deviceLabel: Hager matter 2 buttons (battery) From 812b71a333317e1630a966677f387013d25c04d3 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 12 Mar 2026 13:58:01 -0700 Subject: [PATCH 164/538] WWSTCERT-10665: Lifx Ceiling 13 WWSTCERT-10656 LIFX Ceiling 13 WWSTCERT-10659 LIFX Mirror WWSTCERT-10662 LIFX Mirror --- .../matter-switch/fingerprints.yml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 20a57fc8f0..96a5b7b776 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1296,6 +1296,27 @@ matterManufacturer: vendorId: 0x1423 productId: 0x00CD deviceProfileName: light-level-colorTemperature + - id: "5155/265" + deviceLabel: Ceiling 13 + vendorId: 0x1423 + productId: 0x0109 + deviceProfileName: light-level-colorTemperature + - id: "5155/266" + deviceLabel: LIFX Ceiling 13 + vendorId: 0x1423 + productId: 0x010A + deviceProfileName: light-level-colorTemperature + - id: "5155/267" + deviceLabel: LIFX Mirror + vendorId: 0x1423 + productId: 0x010B + deviceProfileName: light-level-colorTemperature + - id: "5155/268" + deviceLabel: LIFX Mirror + vendorId: 0x1423 + productId: 0x010C + deviceProfileName: light-level-colorTemperature + #LG - id: "4142/8784" deviceLabel: LG Smart Button (1 Button) From e121462910223b5bdea7ed5fecbd1def6804648d Mon Sep 17 00:00:00 2001 From: Sanghee Kim <55477180+sangheedotkim@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:54:04 +0900 Subject: [PATCH 165/538] Matter Ikea Scroll: Changed to use custom presentation (#2825) For custom plugin to provide better device-specific user experience --- .../matter-switch/profiles/ikea-scroll.yml | 49 ++----------------- 1 file changed, 3 insertions(+), 46 deletions(-) diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml index 166b0a62f1..973c789ac6 100644 --- a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml +++ b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml @@ -33,49 +33,6 @@ components: version: 1 categories: - name: Button -deviceConfig: - icons: - - group: main - iconUrl: 'icon://button_wheel' - dashboard: - states: - - component: main - capability: button - version: 1 - detailView: - - component: main - capability: button - version: 1 - - component: main - capability: knob - version: 1 - - component: main - capability: battery - version: 1 - - component: group2 - capability: button - version: 1 - - component: group2 - capability: knob - version: 1 - - component: group3 - capability: button - version: 1 - - component: group3 - capability: knob - version: 1 - automation: - conditions: - - component: main - capability: button - version: 1 - - component: main - capability: battery - version: 1 - - component: group2 - capability: button - version: 1 - - component: group3 - capability: button - version: 1 - actions: [] +metadata: + mnmn: SmartThingsEdge + vid: ikea-scroll \ No newline at end of file From 4b4a24c2fe57148dffab2572ec087e337c6e51d9 Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:11:47 +0900 Subject: [PATCH 166/538] Add lockAlarm configuration to do_configure (#2763) Signed-off-by: HunsupJung --- .../matter-lock/src/new-matter-lock/init.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 bc38370d37..b9fe8f3bb1 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -262,8 +262,10 @@ local function info_changed(driver, device, event, args) end end device:subscribe() - device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) - device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is madatory + if device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then + device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) + device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory + end end local function profiling_data_still_required(device) @@ -287,6 +289,10 @@ end local function do_configure(driver, device) match_profile(driver, device) + device.thread:call_with_delay(5, function() + device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) + device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory + end) end local function driver_switched(driver, device) From 6b9330d5ba13e198954633676322abf1ca65939c Mon Sep 17 00:00:00 2001 From: caowei-cccc Date: Sat, 21 Mar 2026 04:37:03 +0800 Subject: [PATCH 167/538] WWSTCERT-9820 Add matter devcie:CS-T35 (#2499) * Add matter devcie:CS-T35 Signed-off-by: cao wei * Update fingerprints.yml --------- Signed-off-by: cao wei --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ tools/localizations/cn.csv | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 66f0588163..0e0abb6f69 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -300,6 +300,11 @@ matterManufacturer: vendorId: 0x1434 productId: 0x1000 deviceProfileName: matter-bridge + - id: "5172/4352" + deviceLabel: Wi-Fi Relay + vendorId: 0x1434 + productId: 0x1100 + deviceProfileName: plug-binary #GE - id: "4921/177" diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 952b4a5c44..2f053272e6 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -120,6 +120,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXF Smart Vertical Blind Motor",威仕达智能梦幻帘电机 WSCMXF "WISTAR WSCMXF-LED Smart Vertical Blind Motor",威仕达智能梦幻帘电机 WSCMXF-LED "VIVIDSTORM Smart Screen VWSDSTUST120H",VIVIDSTORM智能幕布 VWSDSTUST120H +"Wi-Fi Relay",无线继电器 "HOPOsmart Window Opener A2230011",HOPOsmart链式开窗器 A2230011 "Yanmi Switch (3 Way)",岩米三位智能开关面板 "Onvis Smart Plug S4EU",Onvis 智能插座S4EU From dabfc3e2f98d0b7c049d2fce6023b8b4c4b95603 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 24 Mar 2026 07:44:14 -0700 Subject: [PATCH 168/538] WWSTCERT-10533 TARUIE AC Remote (#2822) * WWSTCERT-10533 TARUIE AC Remote * update profile --- drivers/SmartThings/matter-thermostat/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-thermostat/fingerprints.yml b/drivers/SmartThings/matter-thermostat/fingerprints.yml index 30e99caadb..82be079601 100644 --- a/drivers/SmartThings/matter-thermostat/fingerprints.yml +++ b/drivers/SmartThings/matter-thermostat/fingerprints.yml @@ -83,6 +83,12 @@ matterManufacturer: vendorId: 0x134E productId: 0x0002 deviceProfileName: thermostat-humidity-heating-only-nostate-nobattery + #Taruie + - id: "5151/4101" + deviceLabel: TARUIE AC Remote + vendorId: 0x141F + productId: 0x1005 + deviceProfileName: thermostat-humidity #720 - id: "5482/1" deviceLabel: xCREAS Smart Air Purifier Cyclone400 From 0e8b55e42d326da5b38f1c9ae2953f4332284525 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 24 Mar 2026 09:55:07 -0700 Subject: [PATCH 169/538] WWSTCERT-10572 Decora Smart Wi-Fi (2ndGen) Fan Speed Controller (#2833) * WWSTCERT-10572 Decora Smart Wi-Fi (2ndGen) Fan Speed Controller --------- Co-authored-by: Chris Baumler --- drivers/SmartThings/matter-thermostat/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-thermostat/fingerprints.yml b/drivers/SmartThings/matter-thermostat/fingerprints.yml index 82be079601..cd1e7c5cbd 100644 --- a/drivers/SmartThings/matter-thermostat/fingerprints.yml +++ b/drivers/SmartThings/matter-thermostat/fingerprints.yml @@ -49,6 +49,12 @@ matterManufacturer: vendorId: 0x1527 productId: 0x0002 deviceProfileName: thermostat-heating-only-batteryLevel + #Leviton + - id: "4251/4101" + deviceLabel: Decora Smart Wi-Fi (2ndGen) Fan Speed Controller + vendorId: 0x109B + productId: 0x1005 + deviceProfileName: fan-generic #Lux - id: "4614/1" deviceLabel: LUX TQ1 Smart Thermostat From 6a322b537c5f7028201631145b079395854c921c Mon Sep 17 00:00:00 2001 From: seojune79 <119141897+seojune79@users.noreply.github.com> Date: Wed, 25 Mar 2026 02:34:00 +0900 Subject: [PATCH 170/538] [Aqara/Locks] Improvement of credential info management (#2712) * Update the system to manage credentialInfoTable within the persist area * Improvement of credential info management during Hub switch-over or replacement. * Remove whitespace * Revision of credential data management * Modify to save the current data if PERSIST_DATA is nil. * Fix code indentation * Adding test cases * Remove the unused variables --------- Co-authored-by: Carter Swedal --- .../Aqara/aqara-lock/src/credential_utils.lua | 30 ++++++++ drivers/Aqara/aqara-lock/src/init.lua | 1 + .../src/test/test_aqara_lock_L100.lua | 77 +++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua diff --git a/drivers/Aqara/aqara-lock/src/credential_utils.lua b/drivers/Aqara/aqara-lock/src/credential_utils.lua index 5725d5f55a..df0e904886 100644 --- a/drivers/Aqara/aqara-lock/src/credential_utils.lua +++ b/drivers/Aqara/aqara-lock/src/credential_utils.lua @@ -6,6 +6,31 @@ local lockCredentialInfo = capabilities["stse.lockCredentialInfo"] local credential_utils = {} local HOST_COUNT = "__host_count" +local PERSIST_DATA = "__persist_area" + +credential_utils.eventResource = function(table) + local credentialResource = {} + for key, value in pairs(table) do + credentialResource[key] = value + end + return credentialResource +end + +credential_utils.backup_data = function(device) -- Back up data the persistent + local credentialInfoTable = utils.deep_copy(device:get_latest_state("main", lockCredentialInfo.ID, + lockCredentialInfo.credentialInfo.NAME, {})) + device:set_field(PERSIST_DATA, credentialInfoTable, { persist = true }) +end + +credential_utils.sync = function(driver, device) + local table = device:get_field(PERSIST_DATA) or nil + if table ~= nil then + device:emit_event(lockCredentialInfo.credentialInfo(credential_utils.eventResource(table), + { visibility = { displayed = false } })) + else + credential_utils.backup_data(device) + end +end credential_utils.save_data = function(driver) driver.datastore:save() @@ -28,6 +53,7 @@ credential_utils.update_remote_control_status = function(driver, device, added) end device:set_field(HOST_COUNT, host_cnt, { persist = true }) + credential_utils.backup_data(device) credential_utils.save_data(driver) end @@ -38,6 +64,7 @@ credential_utils.sync_all_credential_info = function(driver, device, command) end end device:emit_event(lockCredentialInfo.credentialInfo(command.args.credentialInfo, { visibility = { displayed = false } })) + credential_utils.backup_data(device) credential_utils.save_data(driver) end @@ -73,6 +100,7 @@ credential_utils.upsert_credential_info = function(driver, device, command) end device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) + credential_utils.backup_data(device) credential_utils.save_data(driver) end @@ -95,6 +123,7 @@ credential_utils.delete_user = function(driver, device, command) end device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) + credential_utils.backup_data(device) credential_utils.save_data(driver) end @@ -116,6 +145,7 @@ credential_utils.delete_credential = function(driver, device, command) end device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) + credential_utils.backup_data(device) credential_utils.save_data(driver) end diff --git a/drivers/Aqara/aqara-lock/src/init.lua b/drivers/Aqara/aqara-lock/src/init.lua index e9eb32c9b4..9afe6bb77e 100644 --- a/drivers/Aqara/aqara-lock/src/init.lua +++ b/drivers/Aqara/aqara-lock/src/init.lua @@ -86,6 +86,7 @@ local function device_init(self, device) end device:emit_event(capabilities.battery.quantity(battery_quantity)) device:emit_event(capabilities.batteryLevel.quantity(battery_quantity)) + credential_utils.sync(self, device) end local function device_added(self, device) diff --git a/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua b/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua new file mode 100644 index 0000000000..3801183490 --- /dev/null +++ b/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua @@ -0,0 +1,77 @@ +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 remoteControlStatus = capabilities.remoteControlStatus +local antiLockStatus = capabilities["stse.antiLockStatus"] +test.add_package_capability("antiLockStatus.yaml") +local lockCredentialInfo = capabilities["stse.lockCredentialInfo"] +test.add_package_capability("lockCredentialInfo.yaml") +local lockAlarm = capabilities["lockAlarm"] +test.add_package_capability("lockAlarm.yaml") +local Battery = capabilities.battery +local BatteryLevel = capabilities.batteryLevel +local Lock = capabilities.lock + +local PRI_CLU = 0xFCC0 + +local HOST_COUNT = "__host_count" +local PERSIST_DATA = "__persist_area" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-lock-battery.yml"), + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Lumi", + model = "aqara.lock.akr001", + server_clusters = { PRI_CLU } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + local SUPPORTED_ALARM_VALUES = { "damaged", "forcedOpeningAttempt", "unableToLockTheDoor", "notClosedForALongTime", + "highTemperature", "attemptsExceeded" } + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + lockAlarm.supportedAlarmValues(SUPPORTED_ALARM_VALUES, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + Lock.supportedUnlockDirections({"fromInside", "fromOutside"}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.type("AA"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.type("AA"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.quantity(6))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.quantity(6))) + local credentialInfoData = { + { credentialId = 1, credentialType = "keypad", userId = "1", userLabel = "june", userType = "host" } + } + mock_device:set_field(PERSIST_DATA, credentialInfoData, { persist = true }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + lockCredentialInfo.credentialInfo(credentialInfoData, { visibility = { displayed = false } }))) + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle added lifecycle - only regular user", + function() + mock_device:set_field(HOST_COUNT, 1, { persist = true }) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + remoteControlStatus.remoteControlEnabled('true', { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.battery(100))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.battery("normal"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + lockAlarm.alarm.clear({ visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + antiLockStatus.antiLockStatus('unknown', { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Lock.lock("locked"))) + end +) + +test.run_registered_tests() From a2914159e857907d73b72f27e3f14e303dce4df0 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Wed, 25 Feb 2026 16:14:23 -0600 Subject: [PATCH 171/538] in infoChanged handler, update profile_changed across drivers --- .../matter-lock/src/new-matter-lock/init.lua | 50 +++++++- .../src/test/test_matter_lock_modular.lua | 26 +++-- .../air_quality_sensor_utils/utils.lua | 50 ++++++-- .../sub_drivers/air_quality_sensor/init.lua | 3 +- ...test_matter_air_quality_sensor_modular.lua | 101 ++++++++++++++++ .../SmartThings/matter-switch/src/init.lua | 3 +- .../sub_drivers/camera/camera_utils/utils.lua | 18 --- .../src/sub_drivers/camera/init.lua | 2 +- .../src/switch_utils/device_configuration.lua | 1 - .../matter-switch/src/switch_utils/fields.lua | 2 - .../matter-switch/src/switch_utils/utils.lua | 44 +++++++ .../src/test/test_matter_light_fan.lua | 71 +++++++++-- .../matter-thermostat/src/init.lua | 2 +- .../test/test_matter_thermostat_modular.lua | 110 ++++++++++++++++++ .../src/thermostat_utils/utils.lua | 44 +++++++ 15 files changed, 468 insertions(+), 59 deletions(-) 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 b9fe8f3bb1..fdae5ec432 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -24,8 +24,6 @@ local MIN_EPOCH_S = 0 local MAX_EPOCH_S = 0xffffffff local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 -local MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED" - local RESPONSE_STATUS_MAP = { [DoorLock.types.DlStatus.SUCCESS] = "success", [DoorLock.types.DlStatus.FAILURE] = "failure", @@ -204,7 +202,6 @@ local function match_profile_modular(driver, device) table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) - device:set_field(MODULAR_PROFILE_UPDATED, true) end local function match_profile_switch(driver, device) @@ -242,11 +239,54 @@ local function match_profile_switch(driver, device) device:try_update_metadata({profile = profile_name}) end +--- Deeply compare two values. +--- Handles metatables. Can optionally ignore cycle checking and/or function differences. +--- +--- @param a any +--- @param b any +--- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } +--- @param seen table|nil +--- @return boolean +local function deep_equals(a, b, opts, seen) + if a == b then return true end -- same object + if type(a) ~= type(b) then return false end -- different type + if type(a) == "function" and opts and opts.ignore_functions then return true end + if type(a) ~= "table" then return false end -- same type but not table, thus was already compared + + -- check for cycles in table references and preserve reference topology. + if not (opts and opts.ignore_cycles) then + seen = seen or {} + seen[a] = seen[a] or {} + if seen[a][b] then + return seen[a][b] + end + seen[a][b] = true + end + + -- Compare keys/values from a + for k, v in next, a do + if not deep_equals(v, rawget(b, k), opts, seen) then + return false + end + end + + -- Ensure b doesn't have extra keys + for k in next, b do + if rawget(a, k) == nil then + return false + end + end + + -- Compare metatables + local mt_a = getmetatable(a) + local mt_b = getmetatable(b) + return deep_equals(mt_a, mt_b, opts, seen) +end + local function info_changed(driver, device, event, args) - if device.profile.id == args.old_st_store.profile.id and not device:get_field(MODULAR_PROFILE_UPDATED) then + if deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then return end - device:set_field(MODULAR_PROFILE_UPDATED, nil) for cap_id, attributes in pairs(subscribed_attributes) do if device:supports_capability_by_id(cap_id) then for _, attr in ipairs(attributes) do diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index 36d3825cf6..c56424cd1d 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -632,16 +632,26 @@ test.register_coroutine_test( mock_device_modular:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) ) mock_device_modular:expect_metadata_update({ profile = "lock-modular-embedded-unlatch", optional_component_capabilities = {{"main", {"lockUsers", "lockCredentials", "lockSchedules", "battery"}}} }) + end, + { test_init = test_init_modular } +) - local updated_device_profile = t_utils.get_profile_definition("lock-modular-embedded-unlatch.yml", - {enabled_optional_capabilities = {{ "main", {"lockUsers", "lockCredentials", "lockSchedules", "battery"}}, - },} - ) - updated_device_profile.id = "00000000-1111-2222-3333-000000000010" - - test.wait_for_events() - test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({ profile = updated_device_profile })) +test.register_coroutine_test( + "No component-capability update and no profile ID update should not cause a re-subscribe in infoChanged handler", function() + -- simulate no actual change + test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({})) + end, + { test_init = test_init_modular } +) +test.register_coroutine_test( + "Component-capability update without profile ID update should cause re-subscribe in infoChanged handler", function() + test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed( + {profile = {id = "00000000-1111-2222-3333-000000000010", components = { main = {capabilities={ + ["lock"]= {id="lock", version=1}, ["lockAlarm"] = {id="lockAlarm", version=1}, ["remoteControlStatus"] = {id="remoteControlStatus", version=1}, + ["lockUsers"] = {id="lockUsers", version=1}, ["lockCredentials"] = {id="lockCredentials", version=1}, ["lockSchedules"] = {id="lockSchedules", version=1}, + ["battery"] = {id="battery", version=1}, ["firmwareUpdate"] = {id="firmwareUpdate", version=1}, ["refresh"] = {id="refresh", version=1}}}}}}) + ) local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_modular) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device_modular)) 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 index 95ca80964c..e23b9f7eeb 100644 --- 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 @@ -77,22 +77,48 @@ function AirQualitySensorUtils.set_supported_health_concern_values(device) end end -function AirQualitySensorUtils.profile_changed(synced_components, prev_components) - if #synced_components ~= #prev_components then - return true +--- Deeply compare two values. +--- Handles metatables. Can optionally ignore cycle checking and/or function differences. +--- +--- @param a any +--- @param b any +--- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } +--- @param seen table|nil +--- @return boolean +function AirQualitySensorUtils.deep_equals(a, b, opts, seen) + if a == b then return true end -- same object + if type(a) ~= type(b) then return false end -- different type + if type(a) == "function" and opts and opts.ignore_functions then return true end + if type(a) ~= "table" then return false end -- same type but not table, thus was already compared + + -- check for cycles in table references and preserve reference topology. + if not (opts and opts.ignore_cycles) then + seen = seen or {} + seen[a] = seen[a] or {} + if seen[a][b] then + return seen[a][b] + end + seen[a][b] = 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 + + -- Compare keys/values from a + for k, v in next, a do + if not AirQualitySensorUtils.deep_equals(v, rawget(b, k), opts, seen) then + return false end - for _, capability in pairs(component.capabilities or {}) do - if prev_components[component.id][capability.id] == nil then - return true - end + end + + -- Ensure b doesn't have extra keys + for k in next, b do + if rawget(a, k) == nil then + return false end end - return false + + -- Compare metatables + local mt_a = getmetatable(a) + local mt_b = getmetatable(b) + return AirQualitySensorUtils.deep_equals(mt_a, mt_b, opts, seen) end return AirQualitySensorUtils 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 index 98b8430c98..e8d0cd4701 100644 --- 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 @@ -66,8 +66,7 @@ function AirQualitySensorLifecycleHandlers.device_init(driver, device) end function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, args) - if device.profile.id ~= args.old_st_store.profile.id or - aqs_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) then + if not aqs_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) 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) 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 69e87e0e31..28e5d8dc66 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 @@ -82,6 +82,36 @@ local mock_device_common = test.mock_device.build_test_matter_device({ } }) +local mock_device_modular_fingerprint = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("aqs-modular.yml", + {enabled_optional_capabilities = {{"main", {}}}}), + 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.AirQuality.ID, cluster_type = "SERVER", feature_map = 3}, + {cluster_id = clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 1}, + }, + device_types = { + {device_type_id = 0x002C, device_type_revision = 1} -- Air Quality Sensor + } + } + } +}) + 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" }) @@ -94,6 +124,18 @@ local function test_init_all() test.socket.matter:__expect_send({mock_device_all.id, subscribe_request}) end +local function test_init_modular_fingerprint() + test.mock_device.add_test_device(mock_device_modular_fingerprint) + test.socket.device_lifecycle:__queue_receive({ mock_device_modular_fingerprint.id, "init" }) + test.socket.capability:__expect_send(mock_device_modular_fingerprint: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_modular_fingerprint) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.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" }) @@ -243,6 +285,30 @@ local function get_subscribe_request_common() return subscribe_request end +local function get_subscribe_request_tvoc() + local subscribed_attributes = { + [capabilities.airQualityHealthConcern.ID] = { + clusters.AirQuality.attributes.AirQuality + }, + [capabilities.tvocMeasurement.ID] = { + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue, + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit, + }, + } + 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_modular_fingerprint) + else + subscribe_request:merge(attribute:subscribe(mock_device_modular_fingerprint)) + end + end + end + return subscribe_request +end + + -- run the profile configuration tests 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"}) @@ -355,5 +421,40 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Component-capability update without profile ID update should cause re-subscribe in infoChanged handler", + function() + local expected_metadata_modular_disabled = { + optional_component_capabilities={ + { + "main", + { + "tvocMeasurement", + }, + }, + }, + profile="aqs-modular", + } + local subscribe_request_tvoc = get_subscribe_request_tvoc() + local updated_device_profile = t_utils.get_profile_definition("aqs-modular.yml", + {enabled_optional_capabilities = expected_metadata_modular_disabled.optional_component_capabilities} + ) + updated_device_profile.id = "00000000-1111-2222-3333-000000000006" + test.socket.device_lifecycle:__queue_receive(mock_device_modular_fingerprint:generate_info_changed({ profile = updated_device_profile })) + test.socket.capability:__expect_send(mock_device_modular_fingerprint:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "unhealthy", "moderate", "slightlyUnhealthy"}, {visibility={displayed=false}}))) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, subscribe_request_tvoc}) + end, + { test_init = test_init_modular_fingerprint } +) + +test.register_coroutine_test( + "No component-capability update and no profile ID update should not cause a re-subscribe in infoChanged handler", + function() + -- simulate no actual change + test.socket.device_lifecycle:__queue_receive(mock_device_modular_fingerprint:generate_info_changed({})) + end, + { test_init = test_init_modular_fingerprint } +) + -- run tests test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index c2f8f1c5ee..b1c6a8df8b 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -60,8 +60,7 @@ function SwitchLifecycleHandlers.driver_switched(driver, device) end function SwitchLifecycleHandlers.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 - device:set_field(fields.MODULAR_PROFILE_UPDATED, nil) + if not switch_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then if device.network_type == device_lib.NETWORK_TYPE_MATTER then device:subscribe() button_cfg.configure_buttons(device, 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 index 12341f493e..5793c1d9fc 100644 --- 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 @@ -134,24 +134,6 @@ function CameraUtils.build_supported_resolutions(device, max_encoded_pixel_rate, return resolutions 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 = {} diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index 179ed54742..24ca154950 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -47,7 +47,7 @@ function CameraLifecycleHandlers.driver_switched(driver, device) end function CameraLifecycleHandlers.info_changed(driver, device, event, args) - if camera_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) then + if not switch_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 18219ef459..ab2e075eb7 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -241,7 +241,6 @@ function DeviceConfiguration.match_profile(driver, device) 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 2b315c6528..65a962ca2c 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -148,8 +148,6 @@ SwitchFields.ELECTRICAL_SENSOR_EPS = "__electrical_sensor_eps" --- for an Electrical Sensor EP with a "primary" endpoint, used during device profiling. SwitchFields.ELECTRICAL_TAGS = "__electrical_tags" -SwitchFields.MODULAR_PROFILE_UPDATED = "__modular_profile_updated" - SwitchFields.profiling_data = { POWER_TOPOLOGY = "__power_topology", BATTERY_SUPPORT = "__battery_support", diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 9fd4c77c84..1afecb318f 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -326,6 +326,50 @@ function utils.create_multi_press_values_list(size, supportsHeld) return list end +--- Deeply compare two values. +--- Handles metatables. Can optionally ignore cycle checking and/or function differences. +--- +--- @param a any +--- @param b any +--- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } +--- @param seen table|nil +--- @return boolean +function utils.deep_equals(a, b, opts, seen) + if a == b then return true end -- same object + if type(a) ~= type(b) then return false end -- different type + if type(a) == "function" and opts and opts.ignore_functions then return true end + if type(a) ~= "table" then return false end -- same type but not table, thus was already compared + + -- check for cycles in table references and preserve reference topology. + if not (opts and opts.ignore_cycles) then + seen = seen or {} + seen[a] = seen[a] or {} + if seen[a][b] then + return seen[a][b] + end + seen[a][b] = true + end + + -- Compare keys/values from a + for k, v in next, a do + if not utils.deep_equals(v, rawget(b, k), opts, seen) then + return false + end + end + + -- Ensure b doesn't have extra keys + for k in next, b do + if rawget(a, k) == nil then + return false + end + end + + -- Compare metatables + local mt_a = getmetatable(a) + local mt_b = getmetatable(b) + return utils.deep_equals(mt_a, mt_b, opts, seen) +end + function utils.detect_bridge(device) return #utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.AGGREGATOR) > 0 end 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 cc435a7f95..eb38e516ee 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 @@ -16,7 +16,8 @@ local mock_device_ep2 = 2 local mock_device = test.mock_device.build_test_matter_device({ label = "Matter Fan Light", - profile = t_utils.get_profile_definition("fan-modular.yml", {}), + profile = t_utils.get_profile_definition("fan-modular.yml", + {enabled_optional_capabilities = {{"main", {"fanSpeedPercent", "fanMode"}}}}), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -58,6 +59,40 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) +local mock_device_capabilities_disabled = test.mock_device.build_test_matter_device({ + label = "Matter Fan Light", + profile = t_utils.get_profile_definition("fan-modular.yml", + {enabled_optional_capabilities = {{"main", {}}}}), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + matter_version = { + software = 1, + hardware = 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 = mock_device_ep2, + clusters = { + {cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 15}, + }, + device_types = { + {device_type_id = 0x002B, device_type_revision = 1,} -- Fan + } + } + } +}) + local CLUSTER_SUBSCRIBE_LIST ={ clusters.OnOff.attributes.OnOff, clusters.LevelControl.attributes.CurrentLevel, @@ -110,16 +145,38 @@ local function test_init() }) 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) +test.register_coroutine_test( + "Component-capability update without profile ID update should cause re-subscribe in infoChanged handler", function() + local cluster_subscribe_list ={ + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode, + clusters.FanControl.attributes.PercentCurrent, + } + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_capabilities_disabled) + for i, clus in ipairs(cluster_subscribe_list) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_capabilities_disabled)) end + end + test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed( + {profile = {id = "00000000-1111-2222-3333-000000000004", components = { main = {capabilities={["fanSpeedPercent"] = {id="fanSpeedPercent", version=1}, ["fanMode"] = {id="fanMode", version=1}, ["firmwareUpdate"] = {id="firmwareUpdate", version=1}, ["refresh"] = {id="refresh", version=1}}}}}}) + ) + test.socket.matter:__expect_send({mock_device_capabilities_disabled.id, subscribe_request}) + end, + { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } +) + +test.register_coroutine_test( + "No component-capability update and no profile ID update should not cause a re-subscribe in infoChanged handler", function() + -- simulate no actual change + test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed({})) + end, + { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } +) + + test.register_coroutine_test( "Switch capability should send the appropriate commands", function() test.socket.capability:__queue_receive( diff --git a/drivers/SmartThings/matter-thermostat/src/init.lua b/drivers/SmartThings/matter-thermostat/src/init.lua index 54cb3318f6..d438ba0952 100644 --- a/drivers/SmartThings/matter-thermostat/src/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/init.lua @@ -116,7 +116,7 @@ function ThermostatLifecycleHandlers.info_changed(driver, device, event, args) device:extend_device("supports_capability_by_id", thermostat_utils.supports_capability_by_id_modular) end - if device.profile.id ~= args.old_st_store.profile.id then + if not thermostat_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then thermostat_utils.handle_thermostat_operating_state_info(device) device:subscribe() 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 6e034beba1..ed7ffe510f 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 @@ -47,6 +47,43 @@ local mock_device_basic = test.mock_device.build_test_matter_device({ } }) +local mock_device_modular = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("thermostat-modular.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.FanControl.ID, cluster_type = "SERVER", feature_map = 0}, + { + cluster_id = clusters.Thermostat.ID, + cluster_revision=5, + cluster_type="SERVER", + feature_map=3, -- Heat and Cool features + }, + {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER", feature_map = 0}, + }, + device_types = { + {device_type_id = 0x0301, device_type_revision = 1} -- Thermostat + } + } + } +}) + -- create test_init functions local function initialize_mock_device(generic_mock_device, generic_subscribed_attributes) local subscribe_request = generic_subscribed_attributes[1]:subscribe(generic_mock_device) @@ -146,5 +183,78 @@ test.register_coroutine_test( } ) +local function initialize_subscribe_request(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) + else + subscribe_request:merge(attribute:subscribe(mock_device)) + end + end + end + return subscribe_request +end + + +local function test_init_modular_fingerprint() + test.mock_device.add_test_device(mock_device_modular) + test.socket.device_lifecycle:__queue_receive({ mock_device_modular.id, "init" }) + local subscribe_request = initialize_subscribe_request(mock_device_modular, { + [clusters.Thermostat.ID] = { + clusters.Thermostat.attributes.LocalTemperature, + clusters.Thermostat.attributes.SystemMode, + clusters.Thermostat.attributes.ControlSequenceOfOperation, + }, + [clusters.TemperatureMeasurement.ID] = { + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue, + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + }, + }) + test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) +end + +test.register_coroutine_test( +"Component-capability update without profile ID update should cause re-subscribe in infoChanged handler", function() + local subscribe_request = initialize_subscribe_request(mock_device_modular, { + [clusters.Thermostat.ID] = { + clusters.Thermostat.attributes.LocalTemperature, + clusters.Thermostat.attributes.SystemMode, + clusters.Thermostat.attributes.ControlSequenceOfOperation, + }, + [clusters.TemperatureMeasurement.ID] = { + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue, + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + }, + [clusters.FanControl.ID] = { + clusters.FanControl.attributes.FanMode, + clusters.FanControl.attributes.FanModeSequence, + }, + }) + local expected_metadata_modular = { + optional_component_capabilities={{"main", {"fanMode"}}}, + profile="thermostat-modular", + } + local updated_device_profile = t_utils.get_profile_definition("thermostat-modular.yml", + {enabled_optional_capabilities = expected_metadata_modular.optional_component_capabilities} + ) + updated_device_profile.id = "00000000-1111-2222-3333-000000000003" + test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({ profile = updated_device_profile })) + test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) + end, + { test_init = test_init_modular_fingerprint } +) + +test.register_coroutine_test( + "No component-capability update and no profile ID update should not cause a re-subscribe in infoChanged handler", function() + -- simulate no actual change + test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({})) + end, + { test_init = function() test.mock_device.add_test_device(mock_device_modular) end } +) + -- run tests test.run_registered_tests() diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua index 7773c9ba02..dd298bc987 100644 --- a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua @@ -72,6 +72,50 @@ function ThermostatUtils.get_total_cumulative_energy_imported(device) return total_energy end +--- Deeply compare two values. +--- Handles metatables. Can optionally ignore cycle checking and/or function differences. +--- +--- @param a any +--- @param b any +--- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } +--- @param seen table|nil +--- @return boolean +function ThermostatUtils.deep_equals(a, b, opts, seen) + if a == b then return true end -- same object + if type(a) ~= type(b) then return false end -- different type + if type(a) == "function" and opts and opts.ignore_functions then return true end + if type(a) ~= "table" then return false end -- same type but not table, thus was already compared + + -- check for cycles in table references and preserve reference topology. + if not (opts and opts.ignore_cycles) then + seen = seen or {} + seen[a] = seen[a] or {} + if seen[a][b] then + return seen[a][b] + end + seen[a][b] = true + end + + -- Compare keys/values from a + for k, v in next, a do + if not ThermostatUtils.deep_equals(v, rawget(b, k), opts, seen) then + return false + end + end + + -- Ensure b doesn't have extra keys + for k in next, b do + if rawget(a, k) == nil then + return false + end + end + + -- Compare metatables + local mt_a = getmetatable(a) + local mt_b = getmetatable(b) + return ThermostatUtils.deep_equals(mt_a, mt_b, opts, seen) +end + function ThermostatUtils.get_endpoints_by_device_type(device, device_type) local endpoints = {} for _, ep in ipairs(device.endpoints) do From 2b5a958f790469520b008ec54558b6a98a1d788d Mon Sep 17 00:00:00 2001 From: Trfwww Date: Wed, 25 Mar 2026 02:52:13 +0800 Subject: [PATCH 172/538] add HAOJAI Smart Switch 3-key and HAOJAI Smart Switch 6-key (#2839) --- drivers/SmartThings/matter-switch/fingerprints.yml | 11 +++++++++++ tools/localizations/cn.csv | 2 ++ 2 files changed, 13 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 0e0abb6f69..115b06f325 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -779,6 +779,17 @@ matterManufacturer: vendorId: 0x1285 productId: 0x0009 deviceProfileName: 4-button-batteryLevel +# HAOJAI + - id: "5530/4113" + deviceLabel: HAOJAI Smart Switch 3-key + vendorId: 0x159A + productId: 0x1011 + deviceProfileName: 3-button-motion + - id: "5530/4098" + deviceLabel: HAOJAI Smart Switch 6-key + vendorId: 0x159A + productId: 0x1002 + deviceProfileName: 6-button-motion # Hue - id: "4107/2049" deviceLabel: Hue W 1600 A21 E26 1P NAM diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 2f053272e6..c03098c37f 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -132,3 +132,5 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXB Smart Curtain Motor",威仕达智能开合帘电机 WSCMXB "WISTAR WSCMXC Smart Curtain Motor",威仕达智能开合帘电机 WSCMXC "WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ +"HAOJAI Smart Switch 3-key",好家智能三键开关 +"HAOJAI Smart Switch 6-key",好家智能六键开关 From 60f9bbe035b47b6157da953bf8e73396e54a1fcf Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 25 Mar 2026 11:18:28 +0900 Subject: [PATCH 173/538] Support keypair generation (#2815) Signed-off-by: Hunsup Jung --- .../matter-lock/src/lock_utils.lua | 176 ++++- .../matter-lock/src/new-matter-lock/init.lua | 238 +++--- .../src/test/test_aqara_matter_lock.lua | 3 + .../src/test/test_matter_lock_modular.lua | 83 +-- .../src/test/test_matter_lock_unlatch.lua | 3 + .../src/test/test_new_matter_lock.lua | 3 + .../src/test/test_new_matter_lock_aliro.lua | 682 ++++++++++++++++++ .../src/test/test_new_matter_lock_battery.lua | 11 +- 8 files changed, 1026 insertions(+), 173 deletions(-) create mode 100644 drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua diff --git a/drivers/SmartThings/matter-lock/src/lock_utils.lua b/drivers/SmartThings/matter-lock/src/lock_utils.lua index 94e95c196f..fcea79d7f5 100644 --- a/drivers/SmartThings/matter-lock/src/lock_utils.lua +++ b/drivers/SmartThings/matter-lock/src/lock_utils.lua @@ -1,6 +1,9 @@ -- Copyright 2022 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local security = require "st.security" +local PUB_KEY_PREFIX = "04" + local lock_utils = { -- Lock device field names LOCK_CODES = "lockCodes", @@ -39,7 +42,11 @@ local lock_utils = { ENDPOINT_KEY_INDEX = "endpointKeyIndex", ENDPOINT_KEY_TYPE = "endpointKeyType", DEVICE_KEY_ID = "deviceKeyId", - COMMAND_REQUEST_ID = "commandRequestId" + COMMAND_REQUEST_ID = "commandRequestId", + MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED", + ALIRO_READER_CONFIG_UPDATED = "aliroReaderConfigUpdated", + LATEST_DOOR_LOCK_FEATURE_MAP = "latestDoorLockFeatureMap", + IS_MODULAR_PROFILE = "isModularProfile" } local capabilities = require "st.capabilities" local json = require "st.json" @@ -102,4 +109,171 @@ end -- keys are the code slots that ST uses -- user_index and credential_index are used in the matter commands -- +function lock_utils.get_field_for_endpoint(device, field, endpoint) + return device:get_field(string.format("%s_%d", field, endpoint)) +end + +function lock_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 lock_utils.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 ~= "lock" and capability.id ~= "lockAlarm" and capability.id ~= "remoteControlStatus" and + 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 + +-- This function check busy_state and if busy_state is false, set it to true(current time) +function lock_utils.is_busy_state_set(device) + local c_time = os.time() + local busy_state = device:get_field(lock_utils.BUSY_STATE) or false + if busy_state == false or c_time - busy_state > 10 then + device:set_field(lock_utils.BUSY_STATE, c_time, {persist = true}) + return false + else + return true + end +end + +function lock_utils.hex_string_to_octet_string(hex_string) + if hex_string == nil then + return nil + end + local octet_string = "" + for i = 1, #hex_string, 2 do + local hex = hex_string:sub(i, i + 1) + octet_string = octet_string .. string.char(tonumber(hex, 16)) + end + return octet_string +end + +function lock_utils.create_group_id_resolving_key() + math.randomseed(os.time()) + local result = string.format("%02x", math.random(0, 255)) + for i = 1, 15 do + result = result .. string.format("%02x", math.random(0, 255)) + end + return result +end + +function lock_utils.generate_keypair(device) + local request_opts = { + key_algorithm = { + type = "ec", + curve = "prime256v1" + }, + signature_algorithm = "sha256", + return_formats = { + pem = true, + der = true + }, + subject = { + common_name = "reader config" + }, + validity_days = 36500, + x509_extensions = { + key_usage = { + critical = true, + digital_signature = true + }, + certificate_policies = { + critical = true, + policy_2030_5_self_signed_client = true + } + } + } + local status = security.generate_self_signed_cert(request_opts) + if not status or not status.key_der then + device.log.error("generate_self_signed_cert returned no data") + return nil, nil + end + + local der = status.key_der + local privKey, pubKey = nil, nil + -- Helper: Parse ASN.1 length (handles 1-byte and multi-byte lengths) + local function get_length(data, start_pos) + local b = string.byte(data, start_pos) + if not b then return nil, start_pos end + + if b < 0x80 then + return b, start_pos + 1 + else + local num_bytes = b - 0x80 + local len = 0 + for i = 1, num_bytes do + len = (len * 256) + string.byte(data, start_pos + i) + end + return len, start_pos + 1 + num_bytes + end + end + -- Start parsing after the initial SEQUENCE tag (0x30) + -- Most keys start: [0x30][Length]. We find the first length to find the start of content. + local _, pos = get_length(der, 2) + + while pos < #der do + local tag = string.byte(der, pos) + local len, content_start = get_length(der, pos + 1) + if not len then break end + if tag == 0x04 then + -- PRIVATE KEY: Octet String + privKey = utils.bytes_to_hex_string(string.sub(der, content_start, content_start + len - 1)) + elseif tag == 0xA1 then + -- PUBLIC KEY Wrapper: Explicit Tag [1] + -- Inside 0xA1 is a BIT STRING (0x03) + local inner_tag = string.byte(der, content_start) + if inner_tag == 0x03 then + local bit_len, bit_start = get_length(der, content_start + 1) + -- BIT STRINGS have a "leading null byte" (unused bits indicator) + -- We skip that byte (bit_start) and the 0x04 EC prefix to get the raw X/Y coordinates + local actual_key_start = bit_start + 2 + local actual_key_len = bit_len - 2 + pubKey = PUB_KEY_PREFIX .. utils.bytes_to_hex_string(string.sub(der, actual_key_start, actual_key_start + actual_key_len - 1)) + end + end + -- Move pointer to the next tag + pos = content_start + len + end + + if not privKey or not pubKey then + device.log.error("Failed to extract keys from DER") + end + return privKey, pubKey +end + return lock_utils 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 fdae5ec432..cca7a56b27 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -1,7 +1,6 @@ -- Copyright 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 - local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local im = require "st.matter.interaction_model" @@ -49,7 +48,6 @@ local ALIRO_KEY_TYPE_TO_CRED_ENUM_MAP = { ["nonEvictableEndpointKey"] = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY } - local battery_support = { NO_BATTERY = "NO_BATTERY", BATTERY_LEVEL = "BATTERY_LEVEL", @@ -60,6 +58,7 @@ local profiling_data = { BATTERY_SUPPORT = "__BATTERY_SUPPORT", } +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local subscribed_attributes = { [capabilities.lock.ID] = { DoorLock.attributes.LockState @@ -111,7 +110,6 @@ local subscribed_events = { } } - local function find_default_endpoint(device, cluster) local res = device.MATTER_DEFAULT_ENDPOINT local eps = device:get_endpoints(cluster) @@ -143,6 +141,7 @@ local function device_init(driver, device) end end end + device:add_subscribed_attribute(DoorLockFeatureMapAttr) for cap_id, events in pairs(subscribed_events) do if device:supports_capability_by_id(cap_id) then for _, e in ipairs(events) do @@ -157,6 +156,61 @@ local function device_added(driver, device) device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) end +local function set_reader_config(device) + local reader_config_updated = device:get_field(lock_utils.ALIRO_READER_CONFIG_UPDATED) or nil + if reader_config_updated == "TRUE" or reader_config_updated == "IN_PROGRESS" then return end + + local cmdName = "setReaderConfig" + local groupId = lock_utils.create_group_id_resolving_key() + local groupResolvingKey = nil + local aliro_ble_uwb_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.ALIROBLEUWB}) + if #aliro_ble_uwb_eps > 0 then + groupResolvingKey = lock_utils.create_group_id_resolving_key() + end + local privKey, pubKey = lock_utils.generate_keypair(device) + if not privKey or not pubKey then + local command_result_info = { + commandName = cmdName, + statusCode = "failure" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.VERIFICATION_KEY, pubKey, {persist = true}) + device:set_field(lock_utils.GROUP_ID, groupId, {persist = true}) + device:set_field(lock_utils.GROUP_RESOLVING_KEY, groupResolvingKey, {persist = true}) + + -- Send command + local ep = find_default_endpoint(device, clusters.DoorLock.ID) + device:send( + DoorLock.server.commands.SetAliroReaderConfig( + device, ep, + lock_utils.hex_string_to_octet_string(privKey), + lock_utils.hex_string_to_octet_string(pubKey), + lock_utils.hex_string_to_octet_string(groupId), + lock_utils.hex_string_to_octet_string(groupResolvingKey) + ) + ) + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "IN_PROGRESS", {persist = true}) +end + local function match_profile_modular(driver, device) local enabled_optional_component_capability_pairs = {} local main_component_capabilities = {} @@ -165,7 +219,11 @@ local function match_profile_modular(driver, device) for _, ep_cluster in pairs(device_ep.clusters) do if ep_cluster.cluster_id == DoorLock.ID then local clus_has_feature = function(feature_bitmap) - return DoorLock.are_features_supported(feature_bitmap, ep_cluster.feature_map) + return DoorLock.are_features_supported( + feature_bitmap, + lock_utils.get_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, device_ep.endpoint_id) or + ep_cluster.feature_map + ) end if clus_has_feature(DoorLock.types.Feature.USER) then table.insert(main_component_capabilities, capabilities.lockUsers.ID) @@ -201,7 +259,9 @@ local function match_profile_modular(driver, device) end table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) - device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) + if lock_utils.optional_capabilities_list_changed(enabled_optional_component_capability_pairs, device.profile.components) then + device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) + end end local function match_profile_switch(driver, device) @@ -302,6 +362,9 @@ local function info_changed(driver, device, event, args) end end device:subscribe() + if device:supports_capability_by_id(capabilities.lockAliro.ID) then + set_reader_config(device) + end if device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory @@ -317,18 +380,17 @@ local function profiling_data_still_required(device) return false end -local function match_profile(driver, device) +local function match_profile(driver, device, ignore_static_profiling) if profiling_data_still_required(device) then return end - if version.api >= 15 and version.rpc >= 9 then match_profile_modular(driver, device) - else + elseif ignore_static_profiling ~= true then match_profile_switch(driver, device) end end local function do_configure(driver, device) - match_profile(driver, device) + match_profile(driver, device, false) device.thread:call_with_delay(5, function() device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory @@ -336,19 +398,7 @@ local function do_configure(driver, device) end local function driver_switched(driver, device) - match_profile(driver, device) -end - --- This function check busy_state and if busy_state is false, set it to true(current time) -local function is_busy_state_set(device) - local c_time = os.time() - local busy_state = device:get_field(lock_utils.BUSY_STATE) or false - if busy_state == false or c_time - busy_state > 10 then - device:set_field(lock_utils.BUSY_STATE, c_time, {persist = true}) - return false - else - return true - end + match_profile(driver, device, false) end -- Matter Handler @@ -449,7 +499,7 @@ local function set_cota_credential(device, credential_index) end -- Check Busy State - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then device.log.debug("delaying setting COTA credential since a credential is currently being set") device.thread:call_with_delay(2, function(t) set_cota_credential(device, credential_index) @@ -524,21 +574,6 @@ local function max_year_schedule_of_user_handler(driver, device, ib, response) device:emit_event(capabilities.lockSchedules.yearDaySchedulesPerUser(ib.data.value, {visibility = {displayed = false}})) end ----------------- --- Aliro Util -- ----------------- -local function hex_string_to_octet_string(hex_string) - if hex_string == nil then - return nil - end - local octet_string = "" - for i = 1, #hex_string, 2 do - local hex = hex_string:sub(i, i + 1) - octet_string = octet_string .. string.char(tonumber(hex, 16)) - end - return octet_string -end - ----------------------------------- -- Aliro Reader Verification Key -- ----------------------------------- @@ -547,6 +582,7 @@ local function aliro_reader_verification_key_handler(driver, device, ib, respons device:emit_event(capabilities.lockAliro.readerVerificationKey( utils.bytes_to_hex_string(ib.data.value), {visibility = {displayed = false}} )) + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "TRUE", {persist = true}) end end @@ -631,6 +667,18 @@ local function max_aliro_endpoint_key_handler(driver, device, ib, response) end end +------------------------------ +-- Feature Map of Door Lock -- +------------------------------ +local function door_lock_feature_map_handler(driver, device, ib, response) + if ib.data.value == nil then return end + local feature_map = lock_utils.get_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id) or nil + if feature_map ~= ib.data.value then + lock_utils.set_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id, ib.data.value, { persist = true }) + match_profile(driver, device, true) + end +end + --------------------------------- -- Power Source Attribute List -- --------------------------------- @@ -649,7 +697,7 @@ local function handle_power_source_attribute_list(driver, device, ib, response) device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist=true}) end if latest_battery_support == nil or latest_battery_support ~= device:get_field(profiling_data.BATTERY_SUPPORT) then - match_profile(driver, device) + match_profile(driver, device, false) end end @@ -1236,7 +1284,7 @@ local function handle_add_user(driver, device, command) local userType = command.args.userType -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -1274,7 +1322,7 @@ local function handle_update_user(driver, device, command) end -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -1433,7 +1481,7 @@ local function handle_delete_user(driver, device, command) local userIdx = command.args.userIndex -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -1461,7 +1509,7 @@ local function handle_delete_all_users(driver, device, command) local cmdName = "deleteAllUsers" -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -1542,7 +1590,7 @@ local function handle_add_credential(driver, device, command) local credData = command.args.credentialData -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -1590,7 +1638,7 @@ local function handle_update_credential(driver, device, command) local credData = command.args.credentialData -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -1835,7 +1883,7 @@ local function set_issuer_key_response_handler(driver, device, ib, response) device, ep, DoorLock.types.DataOperationTypeEnum.ADD, credential, -- Credential - hex_string_to_octet_string(credData), -- Credential Data + lock_utils.hex_string_to_octet_string(credData), -- Credential Data userIdx, -- User Index nil, -- User Status userType -- User Type @@ -1934,7 +1982,7 @@ local function set_endpoint_key_response_handler(driver, device, ib, response) device, ep, DoorLock.types.DataOperationTypeEnum.ADD, credential, -- Credential - hex_string_to_octet_string(credData), -- Credential Data + lock_utils.hex_string_to_octet_string(credData), -- Credential Data userIdx, -- User Index nil, -- User Status userType -- User Type @@ -1983,7 +2031,7 @@ local function handle_delete_credential(driver, device, command) } -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2015,7 +2063,7 @@ local function handle_delete_all_credentials(driver, device, command) } -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2128,7 +2176,7 @@ local function handle_set_week_day_schedule(driver, device, command) local endMinute = schedule.endMinute -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2222,7 +2270,7 @@ local function handle_clear_week_day_schedule(driver, device, command) local userIdx = command.args.userIndex -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2318,7 +2366,7 @@ local function handle_set_year_day_schedule(driver, device, command) local localEndTime = command.args.schedule.localEndTime -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2417,7 +2465,7 @@ local function handle_clear_year_day_schedule(driver, device, command) local userIdx = command.args.userIndex -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2545,7 +2593,7 @@ local function handle_set_reader_config(driver, device, command) end -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2556,6 +2604,22 @@ local function handle_set_reader_config(driver, device, command) return end + local reader_config_updated = device:get_field(lock_utils.ALIRO_READER_CONFIG_UPDATED) or nil + if reader_config_updated == "IN_PROGRESS" then + return + elseif reader_config_updated == "TRUE" then + -- Update commandResult + local command_result_info = { + commandName = cmdName, + statusCode = "success" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + return + end + -- Save values to field device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) device:set_field(lock_utils.VERIFICATION_KEY, verificationKey, {persist = true}) @@ -2567,54 +2631,27 @@ local function handle_set_reader_config(driver, device, command) device:send( DoorLock.server.commands.SetAliroReaderConfig( device, ep, - hex_string_to_octet_string(signingKey), - hex_string_to_octet_string(verificationKey), - hex_string_to_octet_string(groupId), -- Group identification - hex_string_to_octet_string(groupResolvingKey) -- Group resolving key + lock_utils.hex_string_to_octet_string(signingKey), + lock_utils.hex_string_to_octet_string(verificationKey), + lock_utils.hex_string_to_octet_string(groupId), + lock_utils.hex_string_to_octet_string(groupResolvingKey) ) ) + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "IN_PROGRESS", {persist = true}) end local function set_aliro_reader_config_handler(driver, device, ib, response) -- Get result local cmdName = device:get_field(lock_utils.COMMAND_NAME) - local verificationKey = device:get_field(lock_utils.VERIFICATION_KEY) - local groupId = device:get_field(lock_utils.GROUP_ID) - local groupResolvingKey = device:get_field(lock_utils.GROUP_RESOLVING_KEY) - - local status = "success" - if ib.status == DoorLock.types.DlStatus.FAILURE then - status = "failure" - elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then - status = "invalidCommand" - elseif ib.status == DoorLock.types.DlStatus.SUCCESS then - if verificationKey ~= nil then - device:emit_event(capabilities.lockAliro.readerVerificationKey( - verificationKey, - { - state_change = true, - visibility = {displayed = false} - } - )) - end - if groupId ~= nil then - device:emit_event(capabilities.lockAliro.readerGroupIdentifier( - groupId, - { - state_change = true, - visibility = {displayed = false} - } - )) - end - if groupResolvingKey ~= nil then - device:emit_event(capabilities.lockAliro.groupResolvingKey( - groupResolvingKey, - { - state_change = true, - visibility = {displayed = false} - } - )) + local status = "failure" + if ib.status == DoorLock.types.DlStatus.SUCCESS then + status = "success" + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "TRUE", {persist = true}) + else + if ib.status == DoorLock.types.DlStatus.INVALID_FIELD then + status = "invalidCommand" end + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, nil, {persist = true}) end -- Update commandResult @@ -2654,7 +2691,7 @@ local function handle_set_issuer_key(driver, device, command) end -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, userIndex = userIdx, @@ -2681,7 +2718,7 @@ local function handle_set_issuer_key(driver, device, command) device, ep, DoorLock.types.DataOperationTypeEnum.ADD, -- Data Operation Type: Add(0), Modify(2) credential, -- Credential - hex_string_to_octet_string(issuerKey), -- Credential Data + lock_utils.hex_string_to_octet_string(issuerKey), -- Credential Data userIdx, -- User Index nil, -- User Status userType -- User Type @@ -2696,7 +2733,7 @@ local function handle_clear_issuer_key(driver, device, command) local reqId = command.args.requestId -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, userIndex = userIdx, @@ -2757,7 +2794,7 @@ local function handle_set_endpoint_key(driver, device, command) end -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, userIndex = userIdx, @@ -2815,7 +2852,7 @@ local function handle_set_endpoint_key(driver, device, command) device, ep, dataOpType, -- Data Operation Type: Add(0), Modify(2) credential, -- Credential - hex_string_to_octet_string(endpointKey), -- Credential Data + lock_utils.hex_string_to_octet_string(endpointKey), -- Credential Data userIdx, -- User Index nil, -- User Status userType -- User Type @@ -2832,7 +2869,7 @@ local function handle_clear_endpoint_key(driver, device, command) local reqId = command.args.requestId -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, userIndex = userIdx, @@ -2910,6 +2947,7 @@ local new_matter_lock_handler = { [DoorLock.attributes.AliroBLEAdvertisingVersion.ID] = aliro_ble_advertising_version_handler, [DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported.ID] = max_aliro_credential_issuer_key_handler, [DoorLock.attributes.NumberOfAliroEndpointKeysSupported.ID] = max_aliro_endpoint_key_handler, + [DoorLockFeatureMapAttr.ID] = door_lock_feature_map_handler, }, [PowerSource.ID] = { [PowerSource.attributes.AttributeList.ID] = handle_power_source_attribute_list, diff --git a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua index e8a8c3ce24..fa55d37774 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua @@ -7,6 +7,7 @@ test.set_rpc_version(0) local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("lock-user-pin.yml"), @@ -42,6 +43,7 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = clusters.DoorLock.ID} local function test_init() test.disable_startup_messages() -- subscribe request @@ -52,6 +54,7 @@ local function test_init() subscribe_request:merge(clusters.DoorLock.attributes.MaxPINCodeLength:subscribe(mock_device)) subscribe_request:merge(clusters.DoorLock.attributes.MinPINCodeLength:subscribe(mock_device)) subscribe_request:merge(clusters.DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(clusters.DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(clusters.DoorLock.events.DoorLockAlarm:subscribe(mock_device)) subscribe_request:merge(clusters.DoorLock.events.LockUserChange:subscribe(mock_device)) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index c56424cd1d..3ac725ea9b 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -1,13 +1,12 @@ -- 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 cluster_base = require "st.matter.cluster_base" local DoorLock = clusters.DoorLock local mock_device = test.mock_device.build_test_matter_device({ @@ -200,12 +199,13 @@ local mock_device_modular = test.mock_device.build_test_matter_device({ } }) - +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local function test_init() test.disable_startup_messages() -- subscribe request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device)) @@ -228,9 +228,11 @@ local function test_init_unlatch() -- subscribe request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_unlatch) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_unlatch)) + subscribe_request:merge(cluster_base.subscribe(mock_device_unlatch, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_unlatch)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_unlatch)) + -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_unlatch) -- actual onboarding flow @@ -254,10 +256,12 @@ local function test_init_user_pin() subscribe_request:merge(DoorLock.attributes.MaxPINCodeLength:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.attributes.MinPINCodeLength:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device_user_pin)) + subscribe_request:merge(cluster_base.subscribe(mock_device_user_pin, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin)) + -- add test device test.mock_device.add_test_device(mock_device_user_pin) -- actual onboarding flow @@ -283,10 +287,12 @@ local function test_init_user_pin_schedule_unlatch() subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device_user_pin_schedule_unlatch)) + subscribe_request:merge(cluster_base.subscribe(mock_device_user_pin_schedule_unlatch, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin_schedule_unlatch)) + -- add test device test.mock_device.add_test_device(mock_device_user_pin_schedule_unlatch) -- actual onboarding flow @@ -305,9 +311,11 @@ local function test_init_modular() -- subscribe request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_modular) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_modular)) + subscribe_request:merge(cluster_base.subscribe(mock_device_modular, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_modular)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_modular)) + -- add test device test.mock_device.add_test_device(mock_device_modular) -- actual onboarding flow @@ -323,39 +331,6 @@ end test.set_test_init_function(test_init) -test.register_coroutine_test( - "Test lock profile change when attributes related to BAT feature is not available.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, - { - uint32(0), - uint32(1), - uint32(2), - uint32(31), - uint32(65528), - uint32(65529), - uint32(65531), - uint32(65532), - uint32(65533), - }) - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) - ) - mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {}}} }) - end, - { - min_api_version = 19 - } -) - test.register_coroutine_test( "Test modular lock profile change when BatChargeLevel attribute is available", function() @@ -425,40 +400,6 @@ test.register_coroutine_test( } ) -test.register_coroutine_test( - "Test modular lock profile change with unlatch when attributes related to BAT feature is not available.", - function() - test.socket.matter:__queue_receive( - { - mock_device_unlatch.id, - clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device_unlatch, 1, - { - uint32(0), - uint32(1), - uint32(2), - uint32(31), - uint32(65528), - uint32(65529), - uint32(65531), - uint32(65532), - uint32(65533), - }) - } - ) - test.socket.capability:__expect_send( - mock_device_unlatch:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "unlatched", "not fully locked"}, {visibility = {displayed = false}})) - ) - test.socket.capability:__expect_send( - mock_device_unlatch:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) - ) - mock_device_unlatch:expect_metadata_update({ profile = "lock-modular-embedded-unlatch", optional_component_capabilities = {{"main", {}}} }) - end, - { - test_init = test_init_unlatch, - min_api_version = 19 - } -) - test.register_coroutine_test( "Test lock-unlatch profile change with unlatch when BatChargeLevel attribute is available", function() @@ -661,11 +602,13 @@ test.register_coroutine_test( subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device_modular)) + subscribe_request:merge(cluster_base.subscribe(mock_device_modular, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_modular)) subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device_modular)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_modular)) + test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) test.socket.capability:__expect_send( mock_device_modular:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua index d8d2b9f137..56f6da5e41 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua @@ -7,6 +7,7 @@ test.set_rpc_version(0) local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" local DoorLock = clusters.DoorLock local types = DoorLock.types @@ -43,11 +44,13 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local function test_init() test.disable_startup_messages() -- subscribe_request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) -- add test device, handle initial subscribe diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index d2ee8f39ba..19cc296e34 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -7,6 +7,7 @@ test.set_rpc_version(0) local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" local DoorLock = clusters.DoorLock local types = DoorLock.types local lock_utils = require "lock_utils" @@ -44,6 +45,7 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local function test_init() test.disable_startup_messages() -- subscribe request @@ -56,6 +58,7 @@ local function test_init() subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device)) subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua new file mode 100644 index 0000000000..04d051b93e --- /dev/null +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua @@ -0,0 +1,682 @@ +-- Copyright 2023 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" +local DoorLock = clusters.DoorLock +local OctetString1 = require "st.matter.data_types.OctetString1" + +local enabled_optional_component_capability_pairs = {{ + "main", + { + capabilities.lockUsers.ID, + capabilities.lockSchedules.ID, + capabilities.lockAliro.ID + } +}} +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition( + "lock-modular.yml", + {enabled_optional_capabilities = enabled_optional_component_capability_pairs} + ), + manufacturer_info = { + vendor_id = 0x135D, + product_id = 0x00C1, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x2510, -- WDSCH & YDSCH & USR & ALIRO + } + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} +local function test_init() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) + subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroReaderVerificationKey:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroReaderGroupIdentifier:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroReaderGroupSubIdentifier:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroGroupResolvingKey:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroBLEAdvertisingVersion:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfAliroEndpointKeysSupported:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) + subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) + subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) + subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) + test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) + ) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle received AliroReaderVerificationKey from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroReaderVerificationKey:build_test_report_data( + mock_device, 1, + "\x04\xA9\xCB\xE4\x18\xEB\x09\x66\x16\x43\xE2\xA4\xA8\x46\xB8\xED\xFE\x27\x86\x98\x30\x2E\x9F\xB4\x3E\x9B\xFF\xD3\xE3\x10\xCC\x2C\x2C\x7F\xF4\x02\xE0\x6E\x40\xEA\x3C\xE1\x29\x43\x52\x73\x36\x68\x3F\xC5\xB1\xCB\x0C\x6A\x7C\x3F\x0B\x5A\xFF\x78\x35\xDF\x21\xC6\x24" + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.readerVerificationKey( + "04a9cbe418eb09661643e2a4a846b8edfe278698302e9fb43e9bffd3e310cc2c2c7ff402e06e40ea3ce12943527336683fc5b1cb0c6a7c3f0b5aff7835df21c624", + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroReaderGroupIdentifier from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroReaderGroupIdentifier:build_test_report_data( + mock_device, 1, + "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8" + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.readerGroupIdentifier( + "e24f1b205ba923b32cd13dc009e993a8", + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroExpeditedTransactionSupportedProtocolVersions from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions:build_test_report_data( + mock_device, 1, + {OctetString1("\x00\x09"), OctetString1("\x01\x00")} + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.expeditedTransactionProtocolVersions( + {"0.9", "1.0"}, + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroSupportedBLEUWBProtocolVersions from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions:build_test_report_data( + mock_device, 1, + {OctetString1("\x00\x09"), OctetString1("\x01\x00")} + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.bleUWBProtocolVersions( + {"0.9", "1.0"}, + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroReaderVerificationKey from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported:build_test_report_data( + mock_device, 1, + 35 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.maxCredentialIssuerKeys( + 35, + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroGroupResolvingKey from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroGroupResolvingKey:build_test_report_data( + mock_device, 1, + "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8" + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.groupResolvingKey( + "e24f1b205ba923b32cd13dc009e993a8", + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroBLEAdvertisingVersion from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroBLEAdvertisingVersion:build_test_report_data( + mock_device, 1, + 1 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.bleAdvertisingVersion( + "1", + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received NumberOfAliroEndpointKeysSupported from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.NumberOfAliroEndpointKeysSupported:build_test_report_data( + mock_device, 1, + 10 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.maxEndpointKeys( + 10, + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle Set Card Id command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setCardId", + args = {"3icub18c8pr00"} + }, + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.cardId("3icub18c8pr00", {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle Set Reader Config command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setReaderConfig", + args = { + "1a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac", + "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", + "e24f1b205ba923b32cd13dc009e993a8", + nil + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetAliroReaderConfig( + mock_device, 1, -- endpoint + "\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC", + "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", + "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8", + nil + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.SetAliroReaderConfig:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS -- status + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + {commandName="setReaderConfig", statusCode="success"}, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end +) + +test.register_coroutine_test( + "Handle Set Endpoint Key command and Clear Endpoint Key command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setEndpointKey", + args = { + 0, + "vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + "nonEvictableEndpointKey", + "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", + "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2" + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY, + credential_index = 1 + } + ), -- credential + "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users( + {{userIndex=1, userType="adminMember"}}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.credentials( + {{ + keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + keyIndex=1, + keyType="nonEvictableEndpointKey", + userIndex=1 + }}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setEndpointKey", + keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", + statusCode="success", + userIndex=1 + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "clearEndpointKey", + args = {1, "vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", "nonEvictableEndpointKey"} + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.ClearCredential( + mock_device, 1, -- endpoint + DoorLock.types.CredentialStruct( + {credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY, credential_index = 1} + ) + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.ClearCredential:build_test_command_response( + mock_device, 1 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.credentials({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.weekDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.yearDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="clearEndpointKey", + keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + statusCode="success", + userIndex=1 + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end +) + +test.register_coroutine_test( + "Handle Set Issuer Key command and Clear Issuer Key command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setIssuerKey", + args = { + 0, + "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", + "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2" + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, + credential_index = 1 + } + ), -- credential + "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users( + {{userIndex=1, userType="adminMember"}}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.credentials( + {{ + keyIndex=1, + keyType="issuerKey", + userIndex=1 + }}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setIssuerKey", + requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", + statusCode="success", + userIndex=1 + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "clearIssuerKey", + args = {1, "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2"} + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.ClearCredential( + mock_device, 1, -- endpoint + DoorLock.types.CredentialStruct( + {credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, credential_index = 1} + ) + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.ClearCredential:build_test_command_response( + mock_device, 1 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.credentials({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.weekDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.yearDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="clearIssuerKey", + requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", + statusCode="success", + userIndex=1 + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua index ae0f2f6009..8b31a80b0c 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua @@ -1,14 +1,13 @@ -- Copyright 2023 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" local clusters = require "st.matter.clusters" local t_utils = require "integration_test.utils" local uint32 = require "st.matter.data_types.Uint32" - +local cluster_base = require "st.matter.cluster_base" local DoorLock = clusters.DoorLock local mock_device = test.mock_device.build_test_matter_device({ @@ -163,14 +162,17 @@ local mock_device_user_pin_schedule_unlatch = test.mock_device.build_test_matter } }) +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local function test_init() test.disable_startup_messages() -- subscribe request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device)) + -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) @@ -190,9 +192,11 @@ local function test_init_unlatch() -- subscribe request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_unlatch) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_unlatch)) + subscribe_request:merge(cluster_base.subscribe(mock_device_unlatch, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_unlatch)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_unlatch)) + -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_unlatch) test.socket.matter:__expect_send({mock_device_unlatch.id, subscribe_request}) @@ -217,10 +221,12 @@ local function test_init_user_pin() subscribe_request:merge(DoorLock.attributes.MaxPINCodeLength:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.attributes.MinPINCodeLength:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device_user_pin)) + subscribe_request:merge(cluster_base.subscribe(mock_device_user_pin, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin)) + -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_user_pin) test.socket.matter:__expect_send({mock_device_user_pin.id, subscribe_request}) @@ -247,6 +253,7 @@ local function test_init_user_pin_schedule_unlatch() subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device_user_pin_schedule_unlatch)) + subscribe_request:merge(cluster_base.subscribe(mock_device_user_pin_schedule_unlatch, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin_schedule_unlatch)) From edfba7e5004731486a88d61024e6657df674294b Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 25 Mar 2026 23:49:02 +0900 Subject: [PATCH 174/538] Support DPS feature (#2750) Signed-off-by: Hunsup Jung Co-authored-by: Harrison Carter --- .../lock-modular-embedded-unlatch.yml | 3 + .../matter-lock/profiles/lock-modular.yml | 3 + .../matter-lock/src/lock_utils.lua | 3 +- .../matter-lock/src/new-matter-lock/init.lua | 64 +++- .../matter-lock/src/test/test_door_state.lua | 278 ++++++++++++++++++ 5 files changed, 348 insertions(+), 3 deletions(-) create mode 100644 drivers/SmartThings/matter-lock/src/test/test_door_state.lua diff --git a/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml b/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml index 3d9e68b44c..253374212b 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml @@ -6,6 +6,9 @@ components: version: 1 - id: lockAlarm version: 1 + - id: doorState + version: 1 + optional: true - id: remoteControlStatus version: 1 - id: lockUsers diff --git a/drivers/SmartThings/matter-lock/profiles/lock-modular.yml b/drivers/SmartThings/matter-lock/profiles/lock-modular.yml index 3a8a53bf70..7a664767dd 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-modular.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-modular.yml @@ -4,6 +4,9 @@ components: capabilities: - id: lock version: 1 + - id: doorState + version: 1 + optional: true - id: lockAlarm version: 1 - id: remoteControlStatus diff --git a/drivers/SmartThings/matter-lock/src/lock_utils.lua b/drivers/SmartThings/matter-lock/src/lock_utils.lua index fcea79d7f5..94a0e747c4 100644 --- a/drivers/SmartThings/matter-lock/src/lock_utils.lua +++ b/drivers/SmartThings/matter-lock/src/lock_utils.lua @@ -45,8 +45,7 @@ local lock_utils = { COMMAND_REQUEST_ID = "commandRequestId", MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED", ALIRO_READER_CONFIG_UPDATED = "aliroReaderConfigUpdated", - LATEST_DOOR_LOCK_FEATURE_MAP = "latestDoorLockFeatureMap", - IS_MODULAR_PROFILE = "isModularProfile" + LATEST_DOOR_LOCK_FEATURE_MAP = "latestDoorLockFeatureMap" } local capabilities = require "st.capabilities" local json = require "st.json" 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 cca7a56b27..5720856242 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -56,6 +56,7 @@ local battery_support = { local profiling_data = { BATTERY_SUPPORT = "__BATTERY_SUPPORT", + ENABLE_DOOR_STATE = "__ENABLE_DOOR_STATE" } local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} @@ -63,6 +64,9 @@ local subscribed_attributes = { [capabilities.lock.ID] = { DoorLock.attributes.LockState }, + [capabilities.doorState.ID] = { + DoorLock.attributes.DoorState + }, [capabilities.remoteControlStatus.ID] = { DoorLock.attributes.OperatingMode }, @@ -129,6 +133,11 @@ end local function device_init(driver, device) device:set_component_to_endpoint_fn(component_to_endpoint) + if #device:get_endpoints(clusters.DoorLock.ID, {feature_bitmap = clusters.DoorLock.types.Feature.DOOR_POSITION_SENSOR}) == 0 then + device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true}) + else + device:add_subscribed_attribute(clusters.DoorLock.attributes.DoorState) + end if #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) == 0 then device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist = true}) elseif device:get_field(profiling_data.BATTERY_SUPPORT) == nil then @@ -258,6 +267,10 @@ local function match_profile_modular(driver, device) table.insert(main_component_capabilities, capabilities.battery.ID) end + if device:get_field(profiling_data.ENABLE_DOOR_STATE) then + table.insert(main_component_capabilities, capabilities.doorState.ID) + end + table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) if lock_utils.optional_capabilities_list_changed(enabled_optional_component_capability_pairs, device.profile.components) then device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) @@ -365,7 +378,10 @@ local function info_changed(driver, device, event, args) if device:supports_capability_by_id(capabilities.lockAliro.ID) then set_reader_config(device) end - if device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then + if device:supports_capability(capabilities.doorState) and device:get_latest_state("main", capabilities.doorState.ID, capabilities.doorState.supportedDoorStates.NAME) == nil then + device:emit_event(capabilities.doorState.supportedDoorStates({"open", "closed"}, {visibility = {displayed = false}})) -- open and closed are mandatory + end + if device:supports_capability(capabilities.lockAlarm) and device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory end @@ -427,6 +443,45 @@ local function lock_state_handler(driver, device, ib, response) end) end +local function door_state_handler(driver, device, ib, response) + if ib.data.value == nil then + -- early return on nil data. Also, if ENABLE_DOOR_STATE is unset, set it to false and attempt profile matching. + if device:get_field(profiling_data.ENABLE_DOOR_STATE) == nil then + device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true}) + match_profile(driver, device) + end + return + elseif device:supports_capability(capabilities.doorState) == false then + -- if a non-nil report comes in and the doorState capability is unsupported, set ENABLE_DOOR_STATE to true and attempt profile matching. + device:set_field(profiling_data.ENABLE_DOOR_STATE, true, {persist = true}) + match_profile(driver, device) + return + end + + local DoorStateEnum = DoorLock.types.DoorStateEnum + local doorState = capabilities.doorState.doorState + local DOOR_STATE_MAP = { + [DoorStateEnum.DOOR_OPEN] = doorState.open, + [DoorStateEnum.DOOR_CLOSED] = doorState.closed, + [DoorStateEnum.DOOR_JAMMED] = doorState.jammed, + [DoorStateEnum.DOOR_FORCED_OPEN] = doorState.forcedOpen, + [DoorStateEnum.DOOR_UNSPECIFIED_ERROR] = doorState.unspecifiedError, + [DoorStateEnum.DOOR_AJAR] = doorState.ajar + } + local door_state = DOOR_STATE_MAP[ib.data.value] or doorState.unspecifiedError -- fallback to unspecifiedError if we receive a value we don't recognize + device:emit_event(door_state()) + + -- add new door states to supportedDoorStates list if not already present. + local supported_door_states = utils.deep_copy(device:get_latest_state("main", capabilities.doorState.ID, capabilities.doorState.supportedDoorStates.NAME) or {}) + for _, state in pairs(supported_door_states) do + if state == door_state.NAME then + return + end + end + table.insert(supported_door_states, door_state.NAME) + device:emit_event(capabilities.doorState.supportedDoorStates(supported_door_states, {visibility = {displayed = false}})) +end + --------------------- -- Operating Modes -- --------------------- @@ -675,6 +730,12 @@ local function door_lock_feature_map_handler(driver, device, ib, response) local feature_map = lock_utils.get_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id) or nil if feature_map ~= ib.data.value then lock_utils.set_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id, ib.data.value, { persist = true }) + -- If the DPS feature is changed, check the DoorState value and call the match_profile. + if ib.data.value & clusters.DoorLock.types.Feature.DOOR_POSITION_SENSOR == 0 then + device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true}) + else + device:set_field(profiling_data.ENABLE_DOOR_STATE, true, {persist = true}) + end match_profile(driver, device, true) end end @@ -2931,6 +2992,7 @@ local new_matter_lock_handler = { attr = { [DoorLock.ID] = { [DoorLock.attributes.LockState.ID] = lock_state_handler, + [DoorLock.attributes.DoorState.ID] = door_state_handler, [DoorLock.attributes.OperatingMode.ID] = operating_modes_handler, [DoorLock.attributes.NumberOfTotalUsersSupported.ID] = total_users_supported_handler, [DoorLock.attributes.NumberOfPINUsersSupported.ID] = pin_users_supported_handler, diff --git a/drivers/SmartThings/matter-lock/src/test/test_door_state.lua b/drivers/SmartThings/matter-lock/src/test/test_door_state.lua new file mode 100644 index 0000000000..c105346b15 --- /dev/null +++ b/drivers/SmartThings/matter-lock/src/test/test_door_state.lua @@ -0,0 +1,278 @@ +-- 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 clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" +local DoorLock = clusters.DoorLock + +local mock_device_door_state_disabled = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("lock-modular.yml"), + manufacturer_info = { + vendor_id = 0x115f, + product_id = 0x2802, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x20, -- DPS + } + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + +local enabled_optional_component_capability_pairs = {{ "main", {capabilities.doorState.ID} }} +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition( + "lock-modular.yml", + {enabled_optional_capabilities = enabled_optional_component_capability_pairs} + ), + manufacturer_info = { + vendor_id = 0x115f, + product_id = 0x2802, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x20, -- DPS + } + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} +local function test_init() + test.disable_startup_messages() + local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) + subscribe_request:merge(DoorLock.attributes.DoorState:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device_door_state_disabled, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) + subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) + subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) + test.mock_device.add_test_device(mock_device) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + 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({ provisioning_state = "PROVISIONED" }) +end + +test.set_test_init_function(test_init) + +local function test_init_door_state_disabled() + test.disable_startup_messages() + local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_door_state_disabled) + subscribe_request:merge(DoorLock.attributes.DoorState:subscribe(mock_device_door_state_disabled)) + subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_door_state_disabled)) + subscribe_request:merge(cluster_base.subscribe(mock_device_door_state_disabled, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) + subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_door_state_disabled)) + subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_door_state_disabled)) + test.mock_device.add_test_device(mock_device_door_state_disabled) + test.socket.device_lifecycle:__queue_receive({ mock_device_door_state_disabled.id, "added" }) + test.socket.capability:__expect_send( + mock_device_door_state_disabled:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + test.socket.device_lifecycle:__queue_receive({ mock_device_door_state_disabled.id, "init" }) + test.socket.matter:__expect_send({mock_device_door_state_disabled.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_door_state_disabled.id, "doConfigure" }) + mock_device_door_state_disabled:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + +test.register_coroutine_test( + "Check that the device is updated with correct capabilities based on the profile and attributes.", + function () + test.socket.matter:__queue_receive({ + mock_device_door_state_disabled.id, + DoorLock.attributes.DoorState:build_test_report_data(mock_device_door_state_disabled, 1, DoorLock.attributes.DoorState.DOOR_CLOSED) + }) + test.socket.capability:__expect_send( + mock_device_door_state_disabled:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_device_door_state_disabled:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) + ) + + mock_device_door_state_disabled:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {"doorState"}}}}) + end, + { test_init = test_init_door_state_disabled } +) + + +test.register_coroutine_test( + "Handle received DoorState.DOOR_CLOSED from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_CLOSED + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.closed()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"closed"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_JAMMED from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_JAMMED + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.jammed()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"jammed"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_FORCED_OPEN from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_FORCED_OPEN + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.forcedOpen()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"forcedOpen"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_UNSPECIFIED_ERROR from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_UNSPECIFIED_ERROR + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.unspecifiedError()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"unspecifiedError"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_AJAR from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_AJAR + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.ajar()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"ajar"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_OPEN from Matter device, and then DoorState.DOOR_AJAR, ensuring supportedDoorStates is updated to include both states.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_OPEN + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"open"}, {visibility={displayed=false}})) + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_AJAR + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.ajar()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"open", "ajar"}, {visibility={displayed=false}})) + ) + end +) + +test.run_registered_tests() From cf24fb4b4c5f8e61b6b9f34c85da374f6c582c1e Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 24 Feb 2026 13:52:12 -0800 Subject: [PATCH 175/538] WWSTCERT-10426 Halo Select Plus --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 +++++ .../matter-lock/src/new-matter-lock/fingerprints.lua | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 3efd33b364..29c37dd69c 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -72,6 +72,11 @@ matterManufacturer: vendorId: 0x1421 productId: 0x0042 deviceProfileName: lock-user-pin-battery + - id: "5153/65" + deviceLabel: Halo Select Plus + vendorId: 0x1421 + productId: 0x0041 + deviceProfileName: lock-user-pin-battery - id: "5153/129" deviceLabel: Kwikset Aura Reach vendorId: 0x1421 diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index 799c20b9ae..ac0352c75a 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -28,6 +28,7 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x15F2, 0x0001}, -- Viomi, AiSafety Smart Lock E100 {0x158B, 0x0001}, -- Deasino, DS-MT01 {0x10E1, 0x2002}, -- VDA + {0x1421, 0x0041}, -- Kwikset Halo Select Plus {0x1421, 0x0042}, -- Kwikset Halo Select Plus {0x1421, 0x0081}, -- Kwikset Aura Reach {0x1236, 0xa538}, -- Schlage Sense Pro From 416746e3aa412e8bc6e2761dab89ac0ae73b84eb Mon Sep 17 00:00:00 2001 From: Konrad K <33450498+KKlimczukS@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:40:50 +0100 Subject: [PATCH 176/538] NodOn fingerprint ( WWSTCERT-9497) (#2713) * NodOn fingerprints * Fingerprint for NodOn IRB-4-1-00 * removes 2 fingerprints * removes fingerprint from the PR due to issues with the device * removes fingerprint from the PR due to issues with the device --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 6bb32c5978..8765be8aeb 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2405,6 +2405,11 @@ zigbeeManufacturer: manufacturer: NodOn model: SIN-4-1-20 deviceProfileName: basic-switch + - id: "NodOn/SIN-4-1-20-PRO" + deviceLabel: Zigbee Multifunction Relay Switch PRO + manufacturer: NodOn + model: SIN-4-1-20-PRO + deviceProfileName: basic-switch - id: "NodOn/SIN-4-2-20" deviceLabel: Zigbee ON/OFF Lighting Relay Switch manufacturer: NodOn From c62e5ce57c10e06324a625db9021504fc3b42529 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 9 Mar 2026 14:46:42 -0700 Subject: [PATCH 177/538] WWSTECERT-10553 Hager matter 2 buttons (battery) WWSTCERT-10556 Hager matter 4 buttons (battery) --- drivers/SmartThings/matter-switch/fingerprints.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 1c8adddff4..20a57fc8f0 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -753,6 +753,17 @@ matterManufacturer: vendorId: 0x1387 productId: 0x6094 deviceProfileName: light-color-level +# Hager + - id: "4741/8" + deviceLabel: Hager matter 2 buttons (battery) + vendorId: 0x1285 + productId: 0x0008 + deviceProfileName: 2-button-batteryLevel + - id: "4741/9" + deviceLabel: Hager matter 4 buttons (battery) + vendorId: 0x1285 + productId: 0x0009 + deviceProfileName: 4-button-batteryLevel # Hue - id: "4107/2049" deviceLabel: Hue W 1600 A21 E26 1P NAM From 1909d1949de1170cc72fbee711bed4177d5e43a5 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 12 Mar 2026 14:03:18 -0700 Subject: [PATCH 178/538] WWSTCERT-10643: Govee TV Light Bar --- drivers/SmartThings/matter-switch/fingerprints.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 20a57fc8f0..f022334fd0 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -753,6 +753,16 @@ matterManufacturer: vendorId: 0x1387 productId: 0x6094 deviceProfileName: light-color-level + - id: "4999/24646" + deviceLabel: Govee TV Light Bar + vendorId: 0x1387 + productId: 0x6046 + deviceProfileName: light-color-level + - id: "4999/24662" + deviceLabel: Govee RGBICWW Light Bar + vendorId: 0x1387 + productId: 0x6056 + deviceProfileName: light-color-level # Hager - id: "4741/8" deviceLabel: Hager matter 2 buttons (battery) From ed1f5777bbb757c27076a23f3f1cb92c43bea33b Mon Sep 17 00:00:00 2001 From: Steven Green Date: Thu, 12 Mar 2026 13:58:01 -0700 Subject: [PATCH 179/538] WWSTCERT-10665: Lifx Ceiling 13 WWSTCERT-10656 LIFX Ceiling 13 WWSTCERT-10659 LIFX Mirror WWSTCERT-10662 LIFX Mirror --- .../matter-switch/fingerprints.yml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index f022334fd0..66f0588163 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1306,6 +1306,27 @@ matterManufacturer: vendorId: 0x1423 productId: 0x00CD deviceProfileName: light-level-colorTemperature + - id: "5155/265" + deviceLabel: Ceiling 13 + vendorId: 0x1423 + productId: 0x0109 + deviceProfileName: light-level-colorTemperature + - id: "5155/266" + deviceLabel: LIFX Ceiling 13 + vendorId: 0x1423 + productId: 0x010A + deviceProfileName: light-level-colorTemperature + - id: "5155/267" + deviceLabel: LIFX Mirror + vendorId: 0x1423 + productId: 0x010B + deviceProfileName: light-level-colorTemperature + - id: "5155/268" + deviceLabel: LIFX Mirror + vendorId: 0x1423 + productId: 0x010C + deviceProfileName: light-level-colorTemperature + #LG - id: "4142/8784" deviceLabel: LG Smart Button (1 Button) From 5b0b0939d0de5960bacb1829ec87fac4934c146e Mon Sep 17 00:00:00 2001 From: caowei-cccc Date: Sat, 21 Mar 2026 04:37:03 +0800 Subject: [PATCH 180/538] WWSTCERT-9820 Add matter devcie:CS-T35 (#2499) * Add matter devcie:CS-T35 Signed-off-by: cao wei * Update fingerprints.yml --------- Signed-off-by: cao wei --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ tools/localizations/cn.csv | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 66f0588163..0e0abb6f69 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -300,6 +300,11 @@ matterManufacturer: vendorId: 0x1434 productId: 0x1000 deviceProfileName: matter-bridge + - id: "5172/4352" + deviceLabel: Wi-Fi Relay + vendorId: 0x1434 + productId: 0x1100 + deviceProfileName: plug-binary #GE - id: "4921/177" diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 952b4a5c44..2f053272e6 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -120,6 +120,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXF Smart Vertical Blind Motor",威仕达智能梦幻帘电机 WSCMXF "WISTAR WSCMXF-LED Smart Vertical Blind Motor",威仕达智能梦幻帘电机 WSCMXF-LED "VIVIDSTORM Smart Screen VWSDSTUST120H",VIVIDSTORM智能幕布 VWSDSTUST120H +"Wi-Fi Relay",无线继电器 "HOPOsmart Window Opener A2230011",HOPOsmart链式开窗器 A2230011 "Yanmi Switch (3 Way)",岩米三位智能开关面板 "Onvis Smart Plug S4EU",Onvis 智能插座S4EU From 019decfe8de3438d66711b7a11a94a2ac7d54105 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 24 Mar 2026 07:44:14 -0700 Subject: [PATCH 181/538] WWSTCERT-10533 TARUIE AC Remote (#2822) * WWSTCERT-10533 TARUIE AC Remote * update profile --- drivers/SmartThings/matter-thermostat/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-thermostat/fingerprints.yml b/drivers/SmartThings/matter-thermostat/fingerprints.yml index 30e99caadb..82be079601 100644 --- a/drivers/SmartThings/matter-thermostat/fingerprints.yml +++ b/drivers/SmartThings/matter-thermostat/fingerprints.yml @@ -83,6 +83,12 @@ matterManufacturer: vendorId: 0x134E productId: 0x0002 deviceProfileName: thermostat-humidity-heating-only-nostate-nobattery + #Taruie + - id: "5151/4101" + deviceLabel: TARUIE AC Remote + vendorId: 0x141F + productId: 0x1005 + deviceProfileName: thermostat-humidity #720 - id: "5482/1" deviceLabel: xCREAS Smart Air Purifier Cyclone400 From f1b124a8db56e6e3d517cce18b1c6ae0d92fdf09 Mon Sep 17 00:00:00 2001 From: Trfwww Date: Wed, 25 Mar 2026 02:52:13 +0800 Subject: [PATCH 182/538] add HAOJAI Smart Switch 3-key and HAOJAI Smart Switch 6-key (#2839) --- drivers/SmartThings/matter-switch/fingerprints.yml | 11 +++++++++++ tools/localizations/cn.csv | 2 ++ 2 files changed, 13 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 0e0abb6f69..115b06f325 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -779,6 +779,17 @@ matterManufacturer: vendorId: 0x1285 productId: 0x0009 deviceProfileName: 4-button-batteryLevel +# HAOJAI + - id: "5530/4113" + deviceLabel: HAOJAI Smart Switch 3-key + vendorId: 0x159A + productId: 0x1011 + deviceProfileName: 3-button-motion + - id: "5530/4098" + deviceLabel: HAOJAI Smart Switch 6-key + vendorId: 0x159A + productId: 0x1002 + deviceProfileName: 6-button-motion # Hue - id: "4107/2049" deviceLabel: Hue W 1600 A21 E26 1P NAM diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 2f053272e6..c03098c37f 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -132,3 +132,5 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXB Smart Curtain Motor",威仕达智能开合帘电机 WSCMXB "WISTAR WSCMXC Smart Curtain Motor",威仕达智能开合帘电机 WSCMXC "WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ +"HAOJAI Smart Switch 3-key",好家智能三键开关 +"HAOJAI Smart Switch 6-key",好家智能六键开关 From e9f7098d9804278b15115da035dd4c7a5b31d245 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 3 Mar 2026 11:13:57 -0800 Subject: [PATCH 183/538] CHAD-17711 Adds missing fix from BUG2-532 for Stelpro Ki Zigbee thermostat --- .../zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua index 7dd1d5f0d2..d9f53defe1 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro-ki-zigbee-thermostat/init.lua @@ -212,7 +212,7 @@ local function set_heating_setpoint(driver, device, command) end if value >= MIN_SETPOINT and value <= MAX_SETPOINT then - device:send(Thermostat.attributes.OccupiedHeatingSetpoint:write(device, value * 100)) + device:send(Thermostat.attributes.OccupiedHeatingSetpoint:write(device, utils.round(value * 100))) device:send(Thermostat.attributes.OccupiedHeatingSetpoint:read(device)) device:send(Thermostat.attributes.PIHeatingDemand:read(device)) end From dd45164eaac80394aa555c7f74c49290d5e38c42 Mon Sep 17 00:00:00 2001 From: Marcin Krystian Tyminski <81477027+marcintyminski@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:15:21 +0100 Subject: [PATCH 184/538] WWSTCERT-10511/WWSTCERT-10514 Add support to frient smoke co sensor (#2776) * initial commit * handle co value * complete driver with tests * removed unused variable * changes according to pr comments * pr comments changes --- .../fingerprints.yml | 10 + .../frient-smoke-co-temperature-battery.yml | 55 ++ .../src/frient/can_handle.lua | 15 + .../src/frient/fingerprints.lua | 9 + .../src/frient/init.lua | 236 ++++++++ .../src/init.lua | 5 + .../src/sub_drivers.lua | 3 +- ...st_frient_co_smoke_temperature_battery.lua | 506 ++++++++++++++++++ 8 files changed, 838 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/profiles/frient-smoke-co-temperature-battery.yml create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/init.lua create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_frient_co_smoke_temperature_battery.lua diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/fingerprints.yml b/drivers/SmartThings/zigbee-carbon-monoxide-detector/fingerprints.yml index 3c05e02436..eb0d7e0209 100644 --- a/drivers/SmartThings/zigbee-carbon-monoxide-detector/fingerprints.yml +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/fingerprints.yml @@ -14,3 +14,13 @@ zigbeeManufacturer: manufacturer: HEIMAN model: COSensor-EM deviceProfileName: carbonMonoxide-battery + - id: "frient A/S/SCAZB-143" + deviceLabel: Frient Carbon Monoxide Detector + manufacturer: frient A/S + model: SCAZB-143 + deviceProfileName: frient-smoke-co-temperature-battery + - id: "frient A/S/SCAZB-141" + deviceLabel: Frient Carbon Monoxide Detector + manufacturer: frient A/S + model: SCAZB-141 + deviceProfileName: frient-smoke-co-temperature-battery \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/profiles/frient-smoke-co-temperature-battery.yml b/drivers/SmartThings/zigbee-carbon-monoxide-detector/profiles/frient-smoke-co-temperature-battery.yml new file mode 100644 index 0000000000..3a22a0ad77 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/profiles/frient-smoke-co-temperature-battery.yml @@ -0,0 +1,55 @@ +name: frient-smoke-co-temperature-battery +components: +- id: main + capabilities: + - id: smokeDetector + version: 1 + - id: carbonMonoxideDetector + version: 1 + - id: carbonMonoxideMeasurement + version: 1 + - id: tamperAlert + version: 1 + - 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: SmokeDetector +preferences: +- title: "Max alarm duration (s)" + name: maxWarningDuration + description: "After how many seconds should the alarm turn off" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 65534 + default: 240 +- preferenceId: tempOffset + explicit: true +- title: "Temperature Sensitivity (°C)" + 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-carbon-monoxide-detector/src/frient/can_handle.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/can_handle.lua new file mode 100644 index 0000000000..f451011e03 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_frient_smoke_carbon_monoxide = function(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 is_frient_smoke_carbon_monoxide diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/fingerprints.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/fingerprints.lua new file mode 100644 index 0000000000..22180458e9 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FRIENT_SMOKE_CARBON_MONOXIDE_FINGERPRINTS = { + { mfr = "frient A/S", model = "SCAZB-141" }, + { mfr = "frient A/S", model = "SCAZB-143" } +} + +return FRIENT_SMOKE_CARBON_MONOXIDE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/init.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/init.lua new file mode 100644 index 0000000000..4f7d8fa8d5 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/frient/init.lua @@ -0,0 +1,236 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local zcl_clusters = require "st.zigbee.zcl.clusters" +local data_types = require "st.zigbee.data_types" +local battery_defaults = require "st.zigbee.defaults.battery_defaults" +local SinglePrecisionFloat = require "st.zigbee.data_types.SinglePrecisionFloat" +local zcl_global_commands = require "st.zigbee.zcl.global_commands" +local Status = require "st.zigbee.generated.types.ZclStatus" + +local IASZone = zcl_clusters.IASZone +local CarbonMonoxideCluster = zcl_clusters.CarbonMonoxide +local TemperatureMeasurement = zcl_clusters.TemperatureMeasurement +local IASWD = zcl_clusters.IASWD +local SirenConfiguration = IASWD.types.SirenConfiguration +local carbonMonoxide = capabilities.carbonMonoxideDetector +local alarm = capabilities.alarm +local smokeDetector = capabilities.smokeDetector +local carbonMonoxideMeasurement = capabilities.carbonMonoxideMeasurement +local tamperAlert = capabilities.tamperAlert + +local ALARM_COMMAND = "alarmCommand" +local DEFAULT_MAX_WARNING_DURATION = 0x00F0 +local CarbonMonoxideEndpoint = 0x2E +local SmokeAlarmEndpoint = 0x23 +local TEMPERATURE_ENDPOINT = 0x26 + +local alarm_command = { + OFF = 0, + SIREN = 1 +} + +local CONFIGURATIONS = { + { + cluster = CarbonMonoxideCluster.ID, + attribute = CarbonMonoxideCluster.attributes.MeasuredValue.ID, + minimum_interval = 30, + maximum_interval = 600, + data_type = data_types.SinglePrecisionFloat, + reportable_change = SinglePrecisionFloat(0, -20, 0.048576) -- 0, -20, 0.048576 is 1ppm in SinglePrecisionFloat + } +} + +local function get_current_max_warning_duration(device) + return device.preferences.maxWarningDuration == nil and DEFAULT_MAX_WARNING_DURATION or device.preferences.maxWarningDuration +end + +local function device_added(driver, device) + device:emit_event(alarm.alarm.off()) + device:emit_event(smokeDetector.smoke.clear()) + device:emit_event(carbonMonoxide.carbonMonoxide.clear()) + device:emit_event(tamperAlert.tamper.clear()) + device:emit_event(carbonMonoxideMeasurement.carbonMonoxideLevel({value = 0, unit = "ppm"})) +end + +local function device_init(driver, device) + battery_defaults.build_linear_voltage_init(2.6, 3.1)(driver, device) + if CONFIGURATIONS ~= nil then + for _, attribute in ipairs(CONFIGURATIONS) do + device:add_configured_attribute(attribute) + end + end +end + +local function generate_event_from_zone_status(driver, device, zone_status, zigbee_message) + local endpoint = zigbee_message.address_header.src_endpoint.value + if endpoint == SmokeAlarmEndpoint then + if zone_status:is_test_set() then + device:emit_event(smokeDetector.smoke.tested()) + elseif zone_status:is_alarm1_set() then + device:emit_event(smokeDetector.smoke.detected()) + else + device.thread:call_with_delay(6, function () + device:emit_event(smokeDetector.smoke.clear()) + end) + end + elseif endpoint == CarbonMonoxideEndpoint then + if zone_status:is_test_set() then + device:emit_event(carbonMonoxide.carbonMonoxide.tested()) + elseif zone_status:is_alarm1_set() then + device:emit_event(carbonMonoxide.carbonMonoxide.detected()) + else + device.thread:call_with_delay(6, function () + device:emit_event(carbonMonoxide.carbonMonoxide.clear()) + end) + end + end + if zone_status:is_tamper_set() then + device:emit_event(tamperAlert.tamper.detected()) + else + device:emit_event(tamperAlert.tamper.clear()) + end +end + +local function ias_zone_status_change_handler(driver, device, zb_rx) + local zone_status = zb_rx.body.zcl_body.zone_status + generate_event_from_zone_status(driver, device, zone_status, zb_rx) +end + +local function carbon_monoxide_measure_value_attr_handler(driver, device, attr_val, zb_rx) + local co_value = attr_val.value + if co_value <= 1 then + co_value = co_value * 1000000 + else + return + end + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, carbonMonoxideMeasurement.carbonMonoxideLevel({value = co_value, unit = "ppm"})) +end + +local function do_configure(driver, device) + device:configure() + local maxWarningDuration = get_current_max_warning_duration(device) + device:send(IASWD.attributes.MaxDuration:write(device, maxWarningDuration):to_endpoint(0x23)) + + device.thread:call_with_delay(5, function() + device:refresh() + end) +end + +local function send_siren_command(device, warning_mode, warning_siren_level) + local warning_duration = get_current_max_warning_duration(device) + local siren_configuration + + siren_configuration = SirenConfiguration(0x00) + siren_configuration:set_warning_mode(warning_mode) + siren_configuration:set_siren_level(warning_siren_level) + + device:send( + IASWD.server.commands.StartWarning( + device, + siren_configuration, + data_types.Uint16(warning_duration), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + ) +end + +local function siren_switch_off_handler(driver, device, command) + device:set_field(ALARM_COMMAND, alarm_command.OFF, {persist = true}) + send_siren_command(device, 0x00, 0x00) +end + +local function siren_alarm_siren_handler(driver, device, command) + device:set_field(ALARM_COMMAND, alarm_command.SIREN, {persist = true}) + send_siren_command(device, 0x01 , 0x01) + + local warningDurationDelay = get_current_max_warning_duration(device) + + device.thread:call_with_delay(warningDurationDelay, function() -- Send command to switch from siren to off in the app when the siren is done + if(device:get_field(ALARM_COMMAND) == alarm_command.SIREN) then + siren_switch_off_handler(driver, device, command) + end + end) +end + +local emit_alarm_event = function(device, cmd) + if cmd == alarm_command.OFF then + device:emit_event(capabilities.alarm.alarm.off()) + elseif cmd == alarm_command.SIREN then + device:emit_event(capabilities.alarm.alarm.siren()) + end +end + +local default_response_handler = function(driver, device, zigbee_message) + local is_success = zigbee_message.body.zcl_body.status.value + local command = zigbee_message.body.zcl_body.cmd.value + local alarm_ev = device:get_field(ALARM_COMMAND) + + if command == IASWD.server.commands.StartWarning.ID and is_success == Status.SUCCESS then + if alarm_ev ~= alarm_command.OFF then + emit_alarm_event(device, alarm_ev) + local lastDuration = get_current_max_warning_duration(device) + device.thread:call_with_delay(lastDuration, function(d) + device:emit_event(capabilities.alarm.alarm.off()) + end) + else + emit_alarm_event(device,alarm_command.OFF) + end + end +end + +local function info_changed(driver, device, event, args) + for name, info in pairs(device.preferences) do + if (device.preferences[name] ~= nil and args.old_st_store.preferences[name] ~= device.preferences[name]) then + if (name == "maxWarningDuration") then + local input = device.preferences.maxWarningDuration + device:send(IASWD.attributes.MaxDuration:write(device, input)) + elseif (name == "temperatureSensitivity") then + local sensitivity = device.preferences.temperatureSensitivity + local temperatureSensitivity = math.floor(sensitivity * 100 + 0.5) + device:send(TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(device, 30, 600, temperatureSensitivity):to_endpoint(TEMPERATURE_ENDPOINT)) + end + end + end +end + +local frient_smoke_carbon_monoxide = { + NAME = "Frient Smoke Carbon Monoxide", + lifecycle_handlers = { + added = device_added, + init = device_init, + configure = do_configure, + infoChanged = info_changed, + }, + capability_handlers = { + [alarm.ID] = { + [alarm.commands.off.NAME] = siren_switch_off_handler, + [alarm.commands.siren.NAME] = siren_alarm_siren_handler + } + }, + zigbee_handlers = { + global = { + [IASWD.ID] = { + [zcl_global_commands.DEFAULT_RESPONSE_ID] = default_response_handler + } + }, + cluster = { + [IASZone.ID] = { + [IASZone.client.commands.ZoneStatusChangeNotification.ID] = ias_zone_status_change_handler + } + }, + attr = { + [IASZone.ID] = { + [IASZone.attributes.ZoneStatus.ID] = generate_event_from_zone_status + }, + [CarbonMonoxideCluster.ID] = { + [CarbonMonoxideCluster.attributes.MeasuredValue.ID] = carbon_monoxide_measure_value_attr_handler + } + } + }, + can_handle = require("frient.can_handle"), +} + +return frient_smoke_carbon_monoxide \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua index 8ef8a50795..4ddb66aa4a 100644 --- a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua @@ -12,6 +12,11 @@ local zigbee_carbon_monoxide_driver_template = { supported_capabilities = { capabilities.carbonMonoxideDetector, capabilities.battery, + capabilities.carbonMonoxideMeasurement, + capabilities.temperatureMeasurement, + capabilities.smokeDetector, + capabilities.tamperAlert, + capabilities.alarm }, ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, health_check = false, diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/sub_drivers.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/sub_drivers.lua index 6a7a185392..c826ab7d00 100644 --- a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/sub_drivers.lua @@ -4,7 +4,8 @@ local lazy_load_if_possible = require "lazy_load_subdriver" local sub_drivers = { - lazy_load_if_possible("ClimaxTechnology") + lazy_load_if_possible("ClimaxTechnology"), + lazy_load_if_possible("frient"), } return sub_drivers diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_frient_co_smoke_temperature_battery.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_frient_co_smoke_temperature_battery.lua new file mode 100644 index 0000000000..16625fdf92 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_frient_co_smoke_temperature_battery.lua @@ -0,0 +1,506 @@ +-- Copyright 2026 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 IASZone = clusters.IASZone +local IASWD = clusters.IASWD +local CarbonMonoxideCluster = clusters.CarbonMonoxide +local PowerConfiguration = clusters.PowerConfiguration +local TemperatureMeasurement = clusters.TemperatureMeasurement +local capabilities = require "st.capabilities" +local alarm = capabilities.alarm +local smokeDetector = capabilities.smokeDetector +local carbonMonoxideDetector = capabilities.carbonMonoxideDetector +local carbonMonoxideMeasurement = capabilities.carbonMonoxideMeasurement +local tamperAlert = capabilities.tamperAlert +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local data_types = require "st.zigbee.data_types" +local SinglePrecisionFloat = require "st.zigbee.data_types.SinglePrecisionFloat" +local device_management = require "st.zigbee.device_management" +local default_response = require "st.zigbee.zcl.global_commands.default_response" +local messages = require "st.zigbee.messages" +local zb_const = require "st.zigbee.constants" +local zcl_messages = require "st.zigbee.zcl" +local Status = require "st.zigbee.generated.types.ZclStatus" + +local SMOKE_ENDPOINT = 0x23 +local CO_ENDPOINT = 0x2E +local TEMPERATURE_ENDPOINT = 0x26 +local ALARM_COMMAND = "alarmCommand" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("frient-smoke-co-temperature-battery.yml"), + fingerprinted_endpoint_id = SMOKE_ENDPOINT, + zigbee_endpoints = { + [SMOKE_ENDPOINT] = { + id = SMOKE_ENDPOINT, + manufacturer = "frient A/S", + model = "SCAZB-143", + server_clusters = { PowerConfiguration.ID, IASZone.ID, IASWD.ID } + }, + [CO_ENDPOINT] = { + id = CO_ENDPOINT, + server_clusters = { IASZone.ID, CarbonMonoxideCluster.ID } + }, + [TEMPERATURE_ENDPOINT] = { + id = TEMPERATURE_ENDPOINT, + server_clusters = { TemperatureMeasurement.ID } + } + } + } +) + +local function build_default_response_msg(cluster, command, status, endpoint) + local addr_header = messages.AddressHeader( + mock_device:get_short_address(), + endpoint or SMOKE_ENDPOINT, + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + zb_const.HA_PROFILE_ID, + cluster + ) + local default_response_body = default_response.DefaultResponse(command, status) + local zcl_header = zcl_messages.ZclHeader({ + cmd = data_types.ZCLCommandId(default_response_body.ID) + }) + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_header, + zcl_body = default_response_body + }) + return messages.ZigbeeMessageRx({ + address_header = addr_header, + body = message_body + }) +end + +local function expect_bind_and_config(config, endpoint) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.build_bind_request(mock_device, config.cluster, zigbee_test_utils.mock_hub_eui, endpoint):to_endpoint(endpoint) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.attr_config(mock_device, config):to_endpoint(endpoint) + }) +end + +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( + "added lifecycle should set default states", + function() + 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", smokeDetector.smoke.clear()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideDetector.carbonMonoxide.clear()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideMeasurement.carbonMonoxideLevel({ value = 0, unit = "ppm" })) + ) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "init and doConfigure should bind, configure, and refresh", + function() + local battery_config = { + cluster = PowerConfiguration.ID, + attribute = PowerConfiguration.attributes.BatteryVoltage.ID, + minimum_interval = 30, + maximum_interval = 21600, + data_type = data_types.Uint8, + reportable_change = 1 + } + local ias_zone_config = { + cluster = IASZone.ID, + attribute = IASZone.attributes.ZoneStatus.ID, + minimum_interval = 0, + maximum_interval = 180, + data_type = IASZone.attributes.ZoneStatus.base_type + } + local co_config = { + cluster = CarbonMonoxideCluster.ID, + attribute = CarbonMonoxideCluster.attributes.MeasuredValue.ID, + minimum_interval = 30, + maximum_interval = 600, + data_type = data_types.SinglePrecisionFloat, + reportable_change = SinglePrecisionFloat(0, -20, 0.048576) + } + local temp_config = { + cluster = TemperatureMeasurement.ID, + attribute = TemperatureMeasurement.attributes.MeasuredValue.ID, + minimum_interval = 30, + maximum_interval = 600, + data_type = data_types.Int16, + reportable_change = data_types.Int16(100) + } + + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__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, "doConfigure" }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.attr_refresh(mock_device, PowerConfiguration.ID, PowerConfiguration.attributes.BatteryVoltage.ID):to_endpoint(SMOKE_ENDPOINT) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.attr_refresh(mock_device, IASZone.ID, IASZone.attributes.ZoneStatus.ID):to_endpoint(SMOKE_ENDPOINT) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.attr_refresh(mock_device, IASZone.ID, IASZone.attributes.ZoneStatus.ID):to_endpoint(CO_ENDPOINT) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.attr_refresh(mock_device, CarbonMonoxideCluster.ID, CarbonMonoxideCluster.attributes.MeasuredValue.ID):to_endpoint(CO_ENDPOINT) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.attr_refresh(mock_device, TemperatureMeasurement.ID, TemperatureMeasurement.attributes.MeasuredValue.ID):to_endpoint(TEMPERATURE_ENDPOINT) + }) + + expect_bind_and_config(battery_config, SMOKE_ENDPOINT) + expect_bind_and_config(ias_zone_config, SMOKE_ENDPOINT) + expect_bind_and_config(ias_zone_config, CO_ENDPOINT) + expect_bind_and_config(co_config, CO_ENDPOINT) + expect_bind_and_config(temp_config, TEMPERATURE_ENDPOINT) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.IASCIEAddress:write(mock_device, zigbee_test_utils.mock_hub_eui) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.server.commands.ZoneEnrollResponse(mock_device, 0x00, 0x00) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "IAS Zone smoke detected should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0001):from_endpoint(SMOKE_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", smokeDetector.smoke.detected()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + end +) + +test.register_coroutine_test( + "IAS Zone smoke tested should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0100):from_endpoint(SMOKE_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", smokeDetector.smoke.tested()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + end +) + +test.register_coroutine_test( + "IAS Zone smoke clear should be delayed", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(6, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000):from_endpoint(SMOKE_ENDPOINT) + }) + + test.mock_time.advance_time(6) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", smokeDetector.smoke.clear()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "IAS Zone carbon monoxide detected should be handled", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0001):from_endpoint(CO_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideDetector.carbonMonoxide.detected()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + end +) + +test.register_coroutine_test( + "IAS Zone carbon monoxide tested should be handled", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0100):from_endpoint(CO_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideDetector.carbonMonoxide.tested()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + end +) + +test.register_coroutine_test( + "IAS Zone carbon monoxide clear should be delayed", + function() + test.timer.__create_and_queue_test_time_advance_timer(6, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000):from_endpoint(CO_ENDPOINT) + }) + + test.mock_time.advance_time(6) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideDetector.carbonMonoxide.clear()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Tamper detected should be handled", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0004):from_endpoint(SMOKE_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.detected()) + ) + end +) + +test.register_coroutine_test( + "Tamper clear should be handled", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000):from_endpoint(SMOKE_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", tamperAlert.tamper.clear()) + ) + end +) + +test.register_coroutine_test( + "Carbon monoxide measurement should scale values <= 1", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + CarbonMonoxideCluster.attributes.MeasuredValue:build_test_attr_report( + mock_device, + SinglePrecisionFloat(0, -20, 0.048576) + ):from_endpoint(CO_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideMeasurement.carbonMonoxideLevel({ value = 0.99999999747524, unit = "ppm" })) + ) + end +) + +test.register_coroutine_test( + "Carbon monoxide measurement should pass through values > 1", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + CarbonMonoxideCluster.attributes.MeasuredValue:build_test_attr_report( + mock_device, + SinglePrecisionFloat(0, -15, 0.572864) + ):from_endpoint(CO_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", carbonMonoxideMeasurement.carbonMonoxideLevel({ value = 47.999998059822, unit = "ppm" })) + ) + end +) + +test.register_coroutine_test( + "infoChanged should update maxWarningDuration and temperatureSensitivity", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + local updates = { + preferences = { + maxWarningDuration = 120, + temperatureSensitivity = 1.3 + } + } + + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.attributes.MaxDuration:write(mock_device, 120) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting( + mock_device, + 30, + 600, + 130 + ):to_endpoint(TEMPERATURE_ENDPOINT) + }) + end +) + +test.register_coroutine_test( + "Alarm siren command should send StartWarning and auto-off", + function() + mock_device.preferences.maxWarningDuration = 5 + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + + local expected_configuration = IASWD.types.SirenConfiguration(0x00) + expected_configuration:set_warning_mode(0x01) + expected_configuration:set_siren_level(0x01) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning( + mock_device, + expected_configuration, + data_types.Uint16(5), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) + + test.wait_for_events() + test.mock_time.advance_time(5) + + local expected_off_configuration = IASWD.types.SirenConfiguration(0x00) + expected_off_configuration:set_warning_mode(0x00) + expected_off_configuration:set_siren_level(0x00) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning( + mock_device, + expected_off_configuration, + data_types.Uint16(5), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) + end +) + +test.register_coroutine_test( + "Alarm off command should send StartWarning stop", + function() + mock_device.preferences.maxWarningDuration = 5 + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "off", args = {} } + }) + + local expected_configuration = IASWD.types.SirenConfiguration(0x00) + expected_configuration:set_warning_mode(0x00) + expected_configuration:set_siren_level(0x00) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning( + mock_device, + expected_configuration, + data_types.Uint16(5), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) + end +) + +test.register_coroutine_test( + "Default response to StartWarning should emit alarm events", + function() + mock_device.preferences.maxWarningDuration = 2 + mock_device:set_field(ALARM_COMMAND, 1, { persist = true }) + + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_default_response_msg(IASWD.ID, IASWD.server.commands.StartWarning.ID, Status.SUCCESS, SMOKE_ENDPOINT) + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", alarm.alarm.siren()) + ) + + test.wait_for_events() + test.mock_time.advance_time(2) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", alarm.alarm.off()) + ) + test.wait_for_events() + end +) + +test.run_registered_tests() + From 7921a1f6cd4c186116393bc0e4e717ce33b73254 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:42:29 -0600 Subject: [PATCH 185/538] Matter Switch: Subscribe to power topology attributes rather than read them (#2792) --- .../SmartThings/matter-switch/src/init.lua | 12 +--- .../switch_handlers/attribute_handlers.lua | 8 +++ .../matter-switch/src/switch_utils/utils.lua | 66 ++++++++++++------- .../src/test/test_electrical_sensor_set.lua | 7 +- .../src/test/test_electrical_sensor_tree.lua | 4 +- 5 files changed, 53 insertions(+), 44 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index cac42e4483..c2f8f1c5ee 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -16,7 +16,6 @@ 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 @@ -37,8 +36,6 @@ 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 @@ -58,7 +55,6 @@ 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 @@ -106,15 +102,9 @@ function SwitchLifecycleHandlers.device_init(driver, device) if #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) == 0 then device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, {persist = true}) end + switch_utils.handle_electrical_sensor_info(device) device:extend_device("subscribe", switch_utils.subscribe) device:subscribe() - - -- device energy reporting must be handled cumulatively, periodically, or by both simultaneously. - -- 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_SUPPORTED, true, {persist = false}) - end end end diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index ae76709be8..b16610cf25 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -316,6 +316,10 @@ end --- 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) + if device:get_field(fields.profiling_data.POWER_TOPOLOGY) ~= nil then + device.log.warn("Received an AvailableEndpoints response after power topology has already been determined. Ignoring this response.") + return + end 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 @@ -341,6 +345,10 @@ end -- [[ DESCRIPTOR CLUSTER ATTRIBUTES ]] -- function AttributeHandlers.parts_list_handler(driver, device, ib, response) + if device:get_field(fields.profiling_data.POWER_TOPOLOGY) ~= nil then + device.log.warn("Received a PartsList response after power topology has already been determined. Ignoring this response.") + return + end 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 0592d9a342..9fd4c77c84 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -279,7 +279,8 @@ function utils.find_cluster_on_ep(ep, cluster_id, opts) 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 type(ep) ~= "table" then return nil end + for _, cluster in ipairs(ep.clusters or {}) 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") @@ -398,32 +399,32 @@ function utils.handle_electrical_sensor_info(device) 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)) + -- energy reporting must be handled by a cumulative report, a periodic report, or both attributes simultaneously. + -- To ensure a single source of truth, we only handle a device's periodic reporting if cumulative reporting is not supported. + for _, ep_info in ipairs(electrical_sensor_eps) do + if utils.find_cluster_on_ep(ep_info, clusters.ElectricalEnergyMeasurement.ID, + {feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY}) then + device:set_field(fields.CUMULATIVE_REPORTS_SUPPORTED, true) + break 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 + + -- check the feature map for the first (or only) Electrical Sensor EP if the device profiling has not been completed + if device:get_field(fields.profiling_data.POWER_TOPOLOGY) == nil then + 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) or + clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.TREE_TOPOLOGY, endpoint_power_topology_feature_map) then + -- 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 + device:set_field(fields.ELECTRICAL_SENSOR_EPS, electrical_sensor_eps) + 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 end - return end end @@ -510,6 +511,21 @@ function utils.subscribe(device) subscribe_request:with_info_block(ib) end + -- If the power topology of the device has not yet been determined, add the AvailableEndpoints (for SET topology) + -- or PartsList (for TREE topology) attributes to the list of subscribed attributes in order to map the device's electrical endpoints + -- to the proper device card(s). + if device:get_field(fields.profiling_data.POWER_TOPOLOGY) == nil then + local electrical_sensor_eps = utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.ELECTRICAL_SENSOR, { with_info = true }) or {} + local endpoint_power_topology_cluster = utils.find_cluster_on_ep(electrical_sensor_eps[1], clusters.PowerTopology.ID) or {} + if clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.SET_TOPOLOGY, endpoint_power_topology_cluster.feature_map or 0) then + local ib = im.InteractionInfoBlock(nil, clusters.PowerTopology.ID, clusters.PowerTopology.attributes.AvailableEndpoints.ID) + subscribe_request:with_info_block(ib) + elseif clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.TREE_TOPOLOGY, endpoint_power_topology_cluster.feature_map or 0) then + local ib = im.InteractionInfoBlock(nil, clusters.Descriptor.ID, clusters.Descriptor.attributes.PartsList.ID) + subscribe_request:with_info_block(ib) + end + end + if #subscribe_request.info_blocks > 0 then device:send(subscribe_request) end 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 index 8e3ad613da..8cabc4fe09 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -111,6 +111,7 @@ local subscribed_attributes_periodic = { clusters.OnOff.attributes.OnOff, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + clusters.PowerTopology.attributes.AvailableEndpoints, } local subscribed_attributes = { clusters.OnOff.attributes.OnOff, @@ -120,6 +121,7 @@ local subscribed_attributes = { clusters.ElectricalPowerMeasurement.attributes.ActivePower, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + clusters.PowerTopology.attributes.AvailableEndpoints, } local cumulative_report_val_19 = { @@ -180,9 +182,6 @@ local function test_init() 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 @@ -197,8 +196,6 @@ local function test_init_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 }) diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua index b548bb819b..5e6644ecac 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua @@ -87,6 +87,7 @@ local subscribed_attributes = { clusters.ElectricalPowerMeasurement.attributes.ActivePower, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + clusters.Descriptor.attributes.PartsList, } local cumulative_report_val_19 = { @@ -128,9 +129,6 @@ local function test_init() 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 }) end From 6e426fb16291f1fe73b09547511eea7f4b2bb4fb Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Fri, 6 Mar 2026 10:54:30 -0600 Subject: [PATCH 186/538] Remove keying off client clusters in matter-camera --- .../sub_drivers/camera/camera_utils/device_configuration.lua | 3 +-- .../SmartThings/matter-switch/src/test/test_matter_camera.lua | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) 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 index 4f467cbd07..f709d25420 100644 --- 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 @@ -110,8 +110,7 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr 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 + elseif ep_cluster.cluster_id == clusters.WebRTCTransportProvider.ID and has_server_cluster_type(ep_cluster) then table.insert(main_component_capabilities, capabilities.webrtc.ID) end end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 24a7f3306c..65d3de5872 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -67,10 +67,6 @@ local mock_device = test.mock_device.build_test_matter_device({ cluster_id = clusters.WebRTCTransportProvider.ID, cluster_type = "SERVER" }, - { - cluster_id = clusters.WebRTCTransportRequestor.ID, - cluster_type = "CLIENT" - }, { cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER" From c2ce79d57ab823bcbf63375eb23b6526b7e549d1 Mon Sep 17 00:00:00 2001 From: Marcin Krystian Tyminski <81477027+marcintyminski@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:51:50 +0100 Subject: [PATCH 187/538] WWSTCERT-10559/10562/10565/10568 add driver to frient EMI devices (#2730) * add driver * removed unused variables * Changes according to pr comments * More changes based on pr comments * date changes * Requested changes to categories in profiles and updated tests * fix conflict * remove extra closing parenthesis --- .../zigbee-power-meter/fingerprints.yml | 19 +- ...ower-energy-battery-consumption-report.yml | 37 ++ ...frient-power-energy-consumption-report.yml | 35 ++ .../frient-power-energy-current-voltage.yml | 48 +++ .../frient-power-meter-consumption-report.yml | 16 + .../src/frient/EMIZB-151/can_handle.lua | 15 + .../src/frient/EMIZB-151/fingerprints.lua | 8 + .../src/frient/EMIZB-151/init.lua | 258 +++++++++++++ .../src/frient/fingerprints.lua | 6 +- .../zigbee-power-meter/src/frient/init.lua | 172 ++++++++- .../src/frient/sub_drivers.lua | 9 + .../zigbee-power-meter/src/frient/utils.lua | 10 + .../zigbee-power-meter/src/init.lua | 1 + ...ower_energy_battery_consumption_report.lua | 299 +++++++++++++++ ...frient_power_energy_consumption_report.lua | 272 +++++++++++++ ...st_frient_power_energy_current_voltage.lua | 361 ++++++++++++++++++ .../test/test_zigbee_power_meter_frient.lua | 200 ++++++++++ 17 files changed, 1754 insertions(+), 12 deletions(-) create mode 100644 drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-battery-consumption-report.yml create mode 100644 drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-consumption-report.yml create mode 100644 drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-current-voltage.yml create mode 100644 drivers/SmartThings/zigbee-power-meter/profiles/frient-power-meter-consumption-report.yml create mode 100644 drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/init.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/frient/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/frient/utils.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_battery_consumption_report.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_consumption_report.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_current_voltage.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua diff --git a/drivers/SmartThings/zigbee-power-meter/fingerprints.yml b/drivers/SmartThings/zigbee-power-meter/fingerprints.yml index ec58259dd5..15d3f199ec 100644 --- a/drivers/SmartThings/zigbee-power-meter/fingerprints.yml +++ b/drivers/SmartThings/zigbee-power-meter/fingerprints.yml @@ -7,12 +7,27 @@ zigbeeManufacturer: deviceLabel: frient Energy Monitor manufacturer: Develco model: "ZHEMI101" - deviceProfileName: power-meter + deviceProfileName: frient-power-energy-consumption-report - id: "Develco/EMIZB-132" deviceLabel: frient Energy Monitor manufacturer: Develco Products A/S model: "EMIZB-132" - deviceProfileName: power-meter + deviceProfileName: frient-power-meter-consumption-report + - id: "frient A/S/EMIZB-132" + deviceLabel: frient Energy Monitor + manufacturer: frient A/S + model: "EMIZB-132" + deviceProfileName: frient-power-meter-consumption-report + - id: "frient A/S/EMIZB-141" + deviceLabel: "frient EMI 2 LED" + manufacturer: frient A/S + model: "EMIZB-141" + deviceProfileName: frient-power-energy-battery-consumption-report + - id: "frient A/S/EMIZB-151" + deviceLabel: "frient EMI 2 P1" + manufacturer: frient A/S + model: "EMIZB-151" + deviceProfileName: frient-power-energy-current-voltage - id: "ShinaSystem/PMM-300Z1" deviceLabel: SiHAS Energy Monitor manufacturer: ShinaSystem diff --git a/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-battery-consumption-report.yml b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-battery-consumption-report.yml new file mode 100644 index 0000000000..03278796a9 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-battery-consumption-report.yml @@ -0,0 +1,37 @@ +name: frient-power-energy-battery-consumption-report +components: + - id: main + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +preferences: + - title: "Pulse Configuration" + name: pulseConfiguration + description: "Number of pulses the meter outputs per unit" + required: false + preferenceType: integer + definition: + minimum: 50 + maximum: 10000 + default: 1000 + - title: "Initial Energy Consumption" + name: currentSummation + description: "Offset (scaled value) for current summation delivered" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 268435455 + default: 0 diff --git a/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-consumption-report.yml b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-consumption-report.yml new file mode 100644 index 0000000000..c4ac75361b --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-consumption-report.yml @@ -0,0 +1,35 @@ +name: frient-power-energy-consumption-report +components: + - id: main + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +preferences: + - title: "Pulse Configuration" + name: pulseConfiguration + description: "Number of pulses the meter outputs per unit" + required: false + preferenceType: integer + definition: + minimum: 50 + maximum: 10000 + default: 1000 + - title: "Initial Energy Consumption" + name: currentSummation + description: "Offset (scaled value) for current summation delivered" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 268435455 + default: 0 diff --git a/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-current-voltage.yml b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-current-voltage.yml new file mode 100644 index 0000000000..b4464d4ada --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-energy-current-voltage.yml @@ -0,0 +1,48 @@ +name: frient-power-energy-current-voltage +components: + - id: main + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor + - id: production + label: Production + capabilities: + - id: energyMeter + version: 1 + - id: phaseA + label: "Phase A" + capabilities: + - id: powerMeter + version: 1 + - id: voltageMeasurement + version: 1 + - id: currentMeasurement + version: 1 + - id: phaseB + label: "Phase B" + capabilities: + - id: powerMeter + version: 1 + - id: voltageMeasurement + version: 1 + - id: currentMeasurement + version: 1 + - id: phaseC + label: "Phase C" + capabilities: + - id: powerMeter + version: 1 + - id: voltageMeasurement + version: 1 + - id: currentMeasurement + version: 1 \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-meter-consumption-report.yml b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-meter-consumption-report.yml new file mode 100644 index 0000000000..f65fce5d42 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/profiles/frient-power-meter-consumption-report.yml @@ -0,0 +1,16 @@ +name: frient-power-meter-consumption-report +components: +- id: main + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/can_handle.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/can_handle.lua new file mode 100644 index 0000000000..85ee8c086f --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_frient_power_meter = function(opts, driver, device, zb_rx) + local FINGERPRINTS = require("frient.EMIZB-151.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return true, require("frient.EMIZB-151") + end + end + + return false +end + +return is_frient_power_meter diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/fingerprints.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/fingerprints.lua new file mode 100644 index 0000000000..e6af73d53e --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_POWER_METER_FINGERPRINTS = { + { model = "EMIZB-151"} +} + +return ZIGBEE_POWER_METER_FINGERPRINTS \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/init.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/init.lua new file mode 100644 index 0000000000..e7b2bae204 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/EMIZB-151/init.lua @@ -0,0 +1,258 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local zigbee_constants = require "st.zigbee.constants" +local capabilities = require "st.capabilities" + +local clusters = require "st.zigbee.zcl.clusters" +local SimpleMetering = clusters.SimpleMetering +local ElectricalMeasurement = clusters.ElectricalMeasurement +local utils = require "frient.utils" + +local data_types = require "st.zigbee.data_types" +local LAST_REPORT_TIME = "LAST_REPORT_TIME" +local SIMPLE_METERING_DEFAULT_DIVISOR = 1000 + +local AC_VOLTAGE_MULTIPLIER_KEY = "_electrical_measurement_ac_voltage_multiplier" +local AC_CURRENT_MULTIPLIER_KEY = "_electrical_measurement_ac_current_multiplier" +local AC_VOLTAGE_DIVISOR_KEY = "_electrical_measurement_ac_voltage_divisor" +local AC_CURRENT_DIVISOR_KEY = "_electrical_measurement_ac_current_divisor" + +local CurrentSummationReceived = 0x0001 + +local ATTRIBUTES = { + { + cluster = SimpleMetering.ID, + attribute = CurrentSummationReceived, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint48, + reportable_change = 1 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.ActivePowerPhB.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Int16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.ActivePowerPhC.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Int16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSVoltage.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSVoltagePhB.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSVoltagePhC.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSCurrent.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSCurrentPhB.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 5 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSCurrentPhC.ID, + minimum_interval = 5, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 5 + } +} + +local device_init = function(self, device) + for _, attribute in ipairs(ATTRIBUTES) do + device:add_configured_attribute(attribute) + end + + if device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == nil then + device:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, SIMPLE_METERING_DEFAULT_DIVISOR, { persist = true }) + end +end + +local do_configure = function(self, device) + device:refresh() + device:configure() + + -- Divisor and multipler for PowerMeter + device:send(SimpleMetering.attributes.Divisor:read(device)) + device:send(SimpleMetering.attributes.Multiplier:read(device)) +end + +local instantaneous_demand_handler = function(driver, device, value, zb_rx) + local raw_value = value.value + local multiplier = device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 + local divisor = device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) or SIMPLE_METERING_DEFAULT_DIVISOR + + raw_value = raw_value * multiplier / divisor * 1000 + + -- The result is already in watts, no need to multiply by 1000 + device:emit_event(capabilities.powerMeter.power({ value = raw_value, unit = "W" })) +end + +local current_summation_delivered_handler = function(driver, device, value, zb_rx) + local raw_value = value.value + + -- Handle potential overflow values + if raw_value < 0 or raw_value >= 0xFFFFFFFFFFFF then + return + end + + local multiplier = device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 + local divisor = device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) or SIMPLE_METERING_DEFAULT_DIVISOR + + raw_value = raw_value * multiplier / divisor * 1000 + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, capabilities.energyMeter.energy({ value = raw_value, unit = "Wh" })) + + 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 - current_power_consumption.energy, 0.0) + end + + local current_time = os.time() + local last_report_time = device:get_field(LAST_REPORT_TIME) or 0 + local next_report_time = last_report_time + 60 * 15 -- 15 mins, the minimum interval allowed between reports + if current_time < next_report_time then + return + end + + device:emit_event_for_endpoint( + zb_rx.address_header.src_endpoint.value, + capabilities.powerConsumptionReport.powerConsumption({ + start = utils.epoch_to_iso8601(last_report_time), + ["end"] = utils.epoch_to_iso8601(current_time - 1), + deltaEnergy = delta_energy, + energy = raw_value + }) + ) + device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) +end + +local current_summation_received_handler = function(driver, device, value, zb_rx) + local raw_value = value.value + + -- Handle potential overflow values + if raw_value < 0 or raw_value >= 0xFFFFFFFFFFFF then + return + end + + local multiplier = device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 + local divisor = device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) or 1000 + + raw_value = raw_value * multiplier / divisor * 1000 + device:emit_component_event(device.profile.components['production'], capabilities.energyMeter.energy({ value = raw_value, unit = "Wh" })) +end + +local electrical_measurement_ac_voltage_multiplier_handler = function(driver, device, multiplier, zb_rx) + local raw_value = multiplier.value + device:set_field(AC_VOLTAGE_MULTIPLIER_KEY, raw_value, { persist = true }) +end + +local electrical_measurement_ac_voltage_divisor_handler = function(driver, device, divisor, zb_rx) + local raw_value = divisor.value + if raw_value == 0 then + return + end + device:set_field(AC_VOLTAGE_DIVISOR_KEY, raw_value, { persist = true }) +end + +local electrical_measurement_ac_current_multiplier_handler = function(driver, device, multiplier, zb_rx) + local raw_value = multiplier.value + device:set_field(AC_CURRENT_MULTIPLIER_KEY, raw_value, { persist = true }) +end + +local electrical_measurement_ac_current_divisor_handler = function(driver, device, divisor, zb_rx) + local raw_value = divisor.value + if raw_value == 0 then + return + end + device:set_field(AC_CURRENT_DIVISOR_KEY, raw_value, { persist = true }) +end + +local measurement_handler = function(component, multiplier_key, divisor_key, emit_fn, unit) + local handler = function(driver, device, value, zb_rx) + local raw_value = value.value + -- By default emit raw value + local multiplier = device:get_field(multiplier_key) or 1 + local divisor = device:get_field(divisor_key) or 1 + + raw_value = raw_value * multiplier / divisor + + device:emit_component_event(device.profile.components[component], emit_fn({ value = raw_value, unit = unit })) + end + + return handler +end + +local frient_emi = { + NAME = "EMIZB-151", + lifecycle_handlers = { + init = device_init, + doConfigure = do_configure + }, + zigbee_handlers = { + cluster = { + }, + attr = { + [SimpleMetering.ID] = { + [CurrentSummationReceived] = current_summation_received_handler, + [SimpleMetering.attributes.CurrentSummationDelivered.ID] = current_summation_delivered_handler, + [SimpleMetering.attributes.InstantaneousDemand.ID] = instantaneous_demand_handler, + }, + [ElectricalMeasurement.ID] = { + [ElectricalMeasurement.attributes.ACVoltageDivisor.ID] = electrical_measurement_ac_voltage_divisor_handler, + [ElectricalMeasurement.attributes.ACVoltageMultiplier.ID] = electrical_measurement_ac_voltage_multiplier_handler, + [ElectricalMeasurement.attributes.ACCurrentDivisor.ID] = electrical_measurement_ac_current_divisor_handler, + [ElectricalMeasurement.attributes.ACCurrentMultiplier.ID] = electrical_measurement_ac_current_multiplier_handler, + [ElectricalMeasurement.attributes.ActivePower.ID] = measurement_handler("phaseA", zigbee_constants.ELECTRICAL_MEASUREMENT_MULTIPLIER_KEY, zigbee_constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, capabilities.powerMeter.power, "W"), + [ElectricalMeasurement.attributes.RMSVoltage.ID] = measurement_handler("phaseA", AC_VOLTAGE_MULTIPLIER_KEY, AC_VOLTAGE_DIVISOR_KEY, capabilities.voltageMeasurement.voltage, "V"), + [ElectricalMeasurement.attributes.RMSCurrent.ID] = measurement_handler("phaseA", AC_CURRENT_MULTIPLIER_KEY, AC_CURRENT_DIVISOR_KEY, capabilities.currentMeasurement.current, "A"), + [ElectricalMeasurement.attributes.ActivePowerPhB.ID] = measurement_handler("phaseB", zigbee_constants.ELECTRICAL_MEASUREMENT_MULTIPLIER_KEY, zigbee_constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, capabilities.powerMeter.power, "W"), + [ElectricalMeasurement.attributes.RMSVoltagePhB.ID] = measurement_handler("phaseB", AC_VOLTAGE_MULTIPLIER_KEY, AC_VOLTAGE_DIVISOR_KEY, capabilities.voltageMeasurement.voltage, "V"), + [ElectricalMeasurement.attributes.RMSCurrentPhB.ID] = measurement_handler("phaseB", AC_CURRENT_MULTIPLIER_KEY, AC_CURRENT_DIVISOR_KEY, capabilities.currentMeasurement.current, "A"), + [ElectricalMeasurement.attributes.ActivePowerPhC.ID] = measurement_handler("phaseC", zigbee_constants.ELECTRICAL_MEASUREMENT_MULTIPLIER_KEY, zigbee_constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, capabilities.powerMeter.power, "W"), + [ElectricalMeasurement.attributes.RMSVoltagePhC.ID] = measurement_handler("phaseC", AC_VOLTAGE_MULTIPLIER_KEY, AC_VOLTAGE_DIVISOR_KEY, capabilities.voltageMeasurement.voltage, "V"), + [ElectricalMeasurement.attributes.RMSCurrentPhC.ID] = measurement_handler("phaseC", AC_CURRENT_MULTIPLIER_KEY, AC_CURRENT_DIVISOR_KEY, capabilities.currentMeasurement.current, "A") + } + } + }, + can_handle = require("frient.EMIZB-151.can_handle") +} + +return frient_emi \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/fingerprints.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/fingerprints.lua index 5bc09f600d..1e00c4434e 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/frient/fingerprints.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/fingerprints.lua @@ -2,8 +2,10 @@ -- Licensed under the Apache License, Version 2.0 local ZIGBEE_POWER_METER_FINGERPRINTS = { - { model = "ZHEMI101" }, - { model = "EMIZB-132" }, + { model = "ZHEMI101", }, + { model = "EMIZB-132", }, + { model = "EMIZB-141", MIN_BAT = 2.3 , MAX_BAT = 3.0 }, + { model = "EMIZB-151", } } 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 5933faf5cb..e87e3f909d 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/init.lua @@ -1,28 +1,184 @@ --- Copyright 2025 SmartThings, Inc. +-- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local zigbee_constants = require "st.zigbee.constants" +local capabilities = require "st.capabilities" +local cluster_base = require "st.zigbee.cluster_base" +local battery_defaults = require "st.zigbee.defaults.battery_defaults" -local constants = require "st.zigbee.constants" -local configurations = require "configurations" +local clusters = require "st.zigbee.zcl.clusters" +local SimpleMetering = clusters.SimpleMetering +local PowerConfiguration = clusters.PowerConfiguration +local utils = require "frient.utils" +local LAST_REPORT_TIME = "LAST_REPORT_TIME" +local data_types = require "st.zigbee.data_types" -local do_configure = function(self, device) +local log = require "log" +local DEVELCO_MANUFACTURER_CODE = 0x1015 +local SIMPLE_METERING_DEFAULT_DIVISOR = 1000 + +local ZIGBEE_POWER_METER_FINGERPRINTS = require("frient.fingerprints") + +local device_init = function(self, device) + for _, fingerprint in ipairs(ZIGBEE_POWER_METER_FINGERPRINTS) do + if device:get_model() == fingerprint.model and fingerprint.MIN_BAT then + battery_defaults.build_linear_voltage_init(fingerprint.MIN_BAT, fingerprint.MAX_BAT)(self, device) + end + end +end + +local do_refresh = function(self, device) device:refresh() + if device:supports_capability(capabilities.battery) then + device:send(PowerConfiguration.attributes.BatteryVoltage:read(device)) + end +end + +local do_configure = function(self, device) device:configure() + for _, fingerprint in ipairs(ZIGBEE_POWER_METER_FINGERPRINTS) do + if device:get_model() == fingerprint.model and device.preferences then + -- Only write manufacturer-specific attributes when preferences exist for this device. + if device.preferences.pulseConfiguration ~= nil then + local pulseConfiguration = tonumber(device.preferences.pulseConfiguration) or 1000 + device:send(cluster_base.write_manufacturer_specific_attribute(device, SimpleMetering.ID, 0x0300, DEVELCO_MANUFACTURER_CODE, data_types.Uint16, pulseConfiguration):to_endpoint(0x02)) + end + + if device.preferences.currentSummation ~= nil then + local currentSummation = tonumber(device.preferences.currentSummation) or 0 + device:send(cluster_base.write_manufacturer_specific_attribute(device, SimpleMetering.ID, 0x0301, DEVELCO_MANUFACTURER_CODE, data_types.Uint48, currentSummation):to_endpoint(0x02)) + end + end + end + + -- Divisor and multipler for PowerMeter + device:send(SimpleMetering.attributes.Divisor:read(device)) + device:send(SimpleMetering.attributes.Multiplier:read(device)) + + device.thread:call_with_delay(5, function() + do_refresh(self, device) + end) end -local device_init = function(self, device) - device:set_field(constants.SIMPLE_METERING_DIVISOR_KEY, 1000, {persist = true}) - device:set_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, 10000, {persist = true}) +local function info_changed(driver, device, event, args) + for name, value in pairs(device.preferences) do + if (device.preferences[name] ~= nil and args.old_st_store.preferences[name] ~= device.preferences[name]) then + if (name == "pulseConfiguration") then + local pulseConfiguration = tonumber(device.preferences.pulseConfiguration) + device:send(cluster_base.write_manufacturer_specific_attribute(device, SimpleMetering.ID, 0x0300, DEVELCO_MANUFACTURER_CODE, data_types.Uint16, pulseConfiguration):to_endpoint(0x02)) + end + if (name == "currentSummation") then + local currentSummation = tonumber(device.preferences.currentSummation) + device:send(cluster_base.write_manufacturer_specific_attribute(device, SimpleMetering.ID, 0x0301, DEVELCO_MANUFACTURER_CODE, data_types.Uint48, currentSummation):to_endpoint(0x02)) + end + end + end + device.thread:call_with_delay(5, function() + do_refresh(driver, device) + end) +end + +local function simple_metering_divisor_handler(driver, device, divisor, zb_rx) + local new_divisor = SIMPLE_METERING_DEFAULT_DIVISOR + local header = zb_rx.body and zb_rx.body.zcl_header + if header and header.frame_ctrl:is_mfg_specific_set() then + log.debug_with({ hub_logs = true }, string.format("Ignoring manufacturer-specific divisor report: %s", tostring(divisor.value))) + elseif (divisor.value and divisor.value == 0) then + log.warn_with({ hub_logs = true }, "Simple metering divisor reported as 0; forcing divisor to 1000") + elseif (divisor.value and divisor.value > 0) then + new_divisor = divisor.value + end + device:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, new_divisor, { persist = true }) +end + +local function instantaneous_demand_handler(driver, device, value, zb_rx) + local raw_value = value.value + --- demand = demand received * Multipler/Divisor + local multiplier = device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 + local divisor = device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) or SIMPLE_METERING_DEFAULT_DIVISOR + if raw_value < -8388607 or raw_value >= 8388607 then + raw_value = 0 + end + + raw_value = raw_value * multiplier / divisor * 1000 + + local raw_value_watts = raw_value + device:emit_event(capabilities.powerMeter.power({ value = raw_value_watts, unit = "W" })) +end + +local function energy_meter_handler(driver, device, value, zb_rx) + local raw_value = value.value + local multiplier = device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 + local divisor = device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) or SIMPLE_METERING_DEFAULT_DIVISOR + + if raw_value < 0 or raw_value >= 0xFFFFFFFFFFFF then + return + end + + raw_value = (raw_value * multiplier) / divisor + + local offset = device:get_field(zigbee_constants.ENERGY_METER_OFFSET) or 0 + if raw_value < 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(zigbee_constants.ENERGY_METER_OFFSET, offset, { persist = true }) + end + raw_value = raw_value - offset + raw_value = raw_value * 1000 -- the unit of these values should be 'Wh' + + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, capabilities.energyMeter.energy({ value = raw_value, unit = "Wh" })) + + 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 - current_power_consumption.energy, 0.0) + end + + local current_time = os.time() + local last_report_time = device:get_field(LAST_REPORT_TIME) or 0 + local next_report_time = last_report_time + 60 * 15 -- 15 mins, the minimum interval allowed between reports + if current_time < next_report_time then + return + end + + device:emit_event_for_endpoint( + zb_rx.address_header.src_endpoint.value, + capabilities.powerConsumptionReport.powerConsumption({ + start = utils.epoch_to_iso8601(last_report_time), + ["end"] = utils.epoch_to_iso8601(current_time - 1), + deltaEnergy = delta_energy, + energy = raw_value + }) + ) + device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) end local frient_power_meter_handler = { NAME = "frient power meter handler", lifecycle_handlers = { - init = configurations.power_reconfig_wrapper(device_init), + init = device_init, doConfigure = do_configure, + infoChanged = info_changed + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zigbee_handlers = { + cluster = { + }, + attr = { + [SimpleMetering.ID] = { + [SimpleMetering.attributes.CurrentSummationDelivered.ID] = energy_meter_handler, + [SimpleMetering.attributes.InstantaneousDemand.ID] = instantaneous_demand_handler, + [SimpleMetering.attributes.Divisor.ID] = simple_metering_divisor_handler + } + } }, + sub_drivers = require("frient.sub_drivers"), can_handle = require("frient.can_handle"), } diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/sub_drivers.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/sub_drivers.lua new file mode 100644 index 0000000000..3698af85d7 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2026 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.EMIZB-151") +} + +return sub_drivers diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/utils.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/utils.lua new file mode 100644 index 0000000000..70e7684855 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/utils.lua @@ -0,0 +1,10 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local utils = {} + +utils.epoch_to_iso8601 = function(time) + return os.date("!%Y-%m-%dT%H:%M:%SZ", time) +end + +return utils \ 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 f15fae7905..6aa3d4b8c1 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/init.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/init.lua @@ -41,6 +41,7 @@ local zigbee_power_meter_driver_template = { capabilities.powerMeter, capabilities.energyMeter, capabilities.powerConsumptionReport, + capabilities.battery, }, zigbee_handlers = { global = { diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_battery_consumption_report.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_battery_consumption_report.lua new file mode 100644 index 0000000000..6a312c1906 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_battery_consumption_report.lua @@ -0,0 +1,299 @@ +-- Copyright 2026 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 ElectricalMeasurement = clusters.ElectricalMeasurement +local SimpleMetering = clusters.SimpleMetering +local PowerConfiguration = clusters.PowerConfiguration +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 DEVELCO_MANUFACTURER_CODE = 0x1015 +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("frient-power-energy-battery-consumption-report.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + model = "EMIZB-141", + server_clusters = { ElectricalMeasurement.ID, PowerConfiguration.ID, SimpleMetering.ID } + } + } + } +) + + +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( + "InstantaneousDemand Report should be handled.", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }, + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.InstantaneousDemand:build_test_attr_report(mock_device, 2700) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 2700.0, unit = "W" })) + } + } +) + +test.register_coroutine_test( + "lifecycle configure event should configure the 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, + cluster_base.write_manufacturer_specific_attribute(mock_device, SimpleMetering.ID, 0x0300, DEVELCO_MANUFACTURER_CODE, data_types.Uint16, 1000):to_endpoint(0x02) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, SimpleMetering.ID, 0x0301, DEVELCO_MANUFACTURER_CODE, data_types.Uint48, 0):to_endpoint(0x02) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Divisor:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Multiplier: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, + PowerConfiguration.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, + 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.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, + 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.CurrentSummationDelivered:configure_reporting( + mock_device, 5, 3600, 1 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:configure_reporting( + mock_device, 30, 21600, 1 + ) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +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, SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, SimpleMetering.attributes.InstantaneousDemand:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ActivePower:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) } + } + }, + { + inner_block_ordering = "relaxed" + } +) + +test.register_coroutine_test( + "infochanged to check for necessary preferences settings: pulseConfiguration, currentSummation", + function() + local updates = { + preferences = { + pulseConfiguration = 400, + currentSummation = 500 + } + } + + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute( + mock_device, + SimpleMetering.ID, + 0x0300, + DEVELCO_MANUFACTURER_CODE, + data_types.Uint16, + 400 + ):to_endpoint(0x02) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute( + mock_device, + SimpleMetering.ID, + 0x0301, + DEVELCO_MANUFACTURER_CODE, + data_types.Uint48, + 500 + ):to_endpoint(0x02) + }) + + test.socket.zigbee:__set_channel_ordering("relaxed") + + end +) + +test.register_coroutine_test( + "CurrentSummationDelivered Report should be handled.", + function() + local current_time = os.time() - 60 * 16 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + start = "1969-12-31T23:44:00Z", + ["end"] = "1969-12-31T23:59:59Z", + deltaEnergy = 0.0, + energy = 2700.0 + }) + ) + ) + end +) + +test.register_coroutine_test( + "CurrentSummationDelivered report should be handled without powerConsumptionReport because 15 min didn't pass since last report", + function() + local current_time = os.time() - 60 * 14 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_consumption_report.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_consumption_report.lua new file mode 100644 index 0000000000..2679329fd3 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_consumption_report.lua @@ -0,0 +1,272 @@ +-- Copyright 2026 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 ElectricalMeasurement = clusters.ElectricalMeasurement +local SimpleMetering = clusters.SimpleMetering +local PowerConfiguration = clusters.PowerConfiguration +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 DEVELCO_MANUFACTURER_CODE = 0x1015 +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("frient-power-energy-consumption-report.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + model = "ZHEMI101", + server_clusters = { ElectricalMeasurement.ID, PowerConfiguration.ID, SimpleMetering.ID } + } + } + } +) + + +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( + "InstantaneousDemand Report should be handled.", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }, + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.InstantaneousDemand:build_test_attr_report(mock_device, 2700) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 2700.0, unit = "W" })) + } + } +) + +test.register_coroutine_test( + "lifecycle configure event should configure the 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, + cluster_base.write_manufacturer_specific_attribute(mock_device, SimpleMetering.ID, 0x0300, DEVELCO_MANUFACTURER_CODE, data_types.Uint16, 1000):to_endpoint(0x02) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, SimpleMetering.ID, 0x0301, DEVELCO_MANUFACTURER_CODE, data_types.Uint48, 0):to_endpoint(0x02) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Divisor:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Multiplier: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, + 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.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, + 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.CurrentSummationDelivered:configure_reporting( + mock_device, 5, 3600, 1 + ) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +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, SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, SimpleMetering.attributes.InstantaneousDemand:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ActivePower:read(mock_device) } + } + }, + { + inner_block_ordering = "relaxed" + } +) + +test.register_coroutine_test( + "infochanged to check for necessary preferences settings: pulseConfiguration, currentSummation", + function() + local updates = { + preferences = { + pulseConfiguration = 400, + currentSummation = 500 + } + } + + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute( + mock_device, + SimpleMetering.ID, + 0x0300, + DEVELCO_MANUFACTURER_CODE, + data_types.Uint16, + 400 + ):to_endpoint(0x02) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute( + mock_device, + SimpleMetering.ID, + 0x0301, + DEVELCO_MANUFACTURER_CODE, + data_types.Uint48, + 500 + ):to_endpoint(0x02) + }) + + test.socket.zigbee:__set_channel_ordering("relaxed") + + end +) + +test.register_coroutine_test( + "CurrentSummationDelivered Report should be handled.", + function() + local current_time = os.time() - 60 * 16 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + start = "1969-12-31T23:44:00Z", + ["end"] = "1969-12-31T23:59:59Z", + deltaEnergy = 0.0, + energy = 2700.0 + }) + ) + ) + end +) + +test.register_coroutine_test( + "CurrentSummationDelivered report should be handled without powerConsumptionReport because 15 min didn't pass since last report", + function() + local current_time = os.time() - 60 * 14 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_current_voltage.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_current_voltage.lua new file mode 100644 index 0000000000..75f9da6961 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_current_voltage.lua @@ -0,0 +1,361 @@ +-- Copyright 2026 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 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 CurrentSummationReceived = 0x0001 +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local zigbee_constants = require "st.zigbee.constants" +zigbee_constants.ELECTRICAL_MEASUREMENT_AC_VOLTAGE_MULTIPLIER_KEY = "_electrical_measurement_ac_voltage_multiplier" +zigbee_constants.ELECTRICAL_MEASUREMENT_AC_CURRENT_MULTIPLIER_KEY = "_electrical_measurement_ac_current_multiplier" +zigbee_constants.ELECTRICAL_MEASUREMENT_AC_VOLTAGE_DIVISOR_KEY = "_electrical_measurement_ac_voltage_divisor" +zigbee_constants.ELECTRICAL_MEASUREMENT_AC_CURRENT_DIVISOR_KEY = "_electrical_measurement_ac_current_divisor" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("frient-power-energy-current-voltage.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + model = "EMIZB-151", + server_clusters = { ElectricalMeasurement.ID, SimpleMetering.ID } + } + } + } +) + +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) + +local function expected_refresh_commands() + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_attribute( + mock_device, + data_types.ClusterId(SimpleMetering.ID), + CurrentSummationReceived + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered: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, + 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.RMSCurrent: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.RMSCurrentPhC: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.RMSVoltagePhB: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.ActivePower: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.ActivePowerPhC:read(mock_device) + }) +end + + + + +test.register_coroutine_test( + "Refresh should read all necessary attributes", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", command = "refresh", args = {} } }) + + expected_refresh_commands() + end +) + +test.register_coroutine_test( + "ALl reports (for all phases) should be handled properly", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ACVoltageMultiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ACVoltageDivisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ACCurrentMultiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ACCurrentDivisor:build_test_attr_report(mock_device, 1000) }) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 30) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 30.0, unit = "Wh"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.InstantaneousDemand:build_test_attr_report(mock_device, 40) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 40.0, unit = "W"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_device, 50) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseA", capabilities.powerMeter.power({ value = 50.0, unit = "W"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.RMSVoltage:build_test_attr_report(mock_device, 50) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseA", capabilities.voltageMeasurement.voltage({ value = 0.05, unit = "V"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.RMSCurrent:build_test_attr_report(mock_device, 60) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseA", capabilities.currentMeasurement.current({ value = 0.06, unit = "A"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ActivePowerPhB:build_test_attr_report(mock_device, 70) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseB", capabilities.powerMeter.power({ value = 70.0, unit = "W"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.RMSVoltagePhB:build_test_attr_report(mock_device, 80) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseB", capabilities.voltageMeasurement.voltage({ value = 0.08, unit = "V"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.RMSCurrentPhB:build_test_attr_report(mock_device, 90) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseB", capabilities.currentMeasurement.current({ value = 0.09, unit = "A"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.ActivePowerPhC:build_test_attr_report(mock_device, 100) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseC", capabilities.powerMeter.power({ value = 100.0, unit = "W"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.RMSVoltagePhC:build_test_attr_report(mock_device, 110) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseC", capabilities.voltageMeasurement.voltage({ value = 0.11, unit = "V"})) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, ElectricalMeasurement.attributes.RMSCurrentPhC:build_test_attr_report(mock_device, 120) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("phaseC", capabilities.currentMeasurement.current({ value = 0.12, unit = "A"})) + ) + + end +) + +test.register_coroutine_test( + "CurrentSummationDelivered Report should be handled.", + function() + local current_time = os.time() - 60 * 16 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + start = "1969-12-31T23:44:00Z", + ["end"] = "1969-12-31T23:59:59Z", + deltaEnergy = 0.0, + energy = 2700.0 + }) + ) + ) + end +) + +test.register_coroutine_test( + "CurrentSummationDelivered report should be handled without powerConsumptionReport because 15 min didn't pass since last report", + function() + local current_time = os.time() - 60 * 14 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + end +) + +test.register_coroutine_test( + "lifecycle configure event should configure the device", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + + expected_refresh_commands() + + 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, + 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.RMSCurrent:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrentPhB:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrentPhC:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltage:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltagePhB:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltagePhC:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + 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, + ElectricalMeasurement.attributes.ActivePowerPhB:configure_reporting( + mock_device, 5, 3600, 5 + ) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePowerPhC: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, + SimpleMetering.attributes.Divisor:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Multiplier:read(mock_device) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.configure_reporting( + mock_device, + data_types.ClusterId(SimpleMetering.ID), + data_types.AttributeId(CurrentSummationReceived), + data_types.ZigbeeDataType(data_types.Uint48.ID), + 5, + 3600, + 1 + ) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua new file mode 100644 index 0000000000..1409c8e749 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua @@ -0,0 +1,200 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +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 constants = require "st.zigbee.constants" + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("frient-power-meter-consumption-report.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Develco Products A/S", + model = "EMIZB-132", + server_clusters = {SimpleMetering.ID, ElectricalMeasurement.ID} + } + } +}) + +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) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "frient device_init sets divisor fields", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.wait_for_events() + end +) + +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, SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, SimpleMetering.attributes.InstantaneousDemand:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, ElectricalMeasurement.attributes.ActivePower:read(mock_device) } + } + }, + { + inner_block_ordering = "relaxed" + } +) + +test.register_coroutine_test( + "frient instantaneous demand report emits power", + function() + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.InstantaneousDemand:build_test_attr_report(mock_device, 40) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 40.0, unit = "W" })) + ) + end +) + +test.register_coroutine_test( + "frient current summation delivered emits energy and consumption report", + function() + local current_time = os.time() - 60 * 16 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + start = "1969-12-31T23:44:00Z", + ["end"] = "1969-12-31T23:59:59Z", + deltaEnergy = 0.0, + energy = 2700.0 + }) + ) + ) + end +) + +test.register_coroutine_test( + "frient current summation delivered skips consumption report when interval is short", + function() + local current_time = os.time() - 60 * 14 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 1000) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Multiplier:build_test_attr_report(mock_device, 1) }) + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 2700) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 2700.0, unit = "Wh" })) + ) + end +) + +test.register_coroutine_test( + "frient divisor report updates divisor field", + function() + test.socket.zigbee:__queue_receive({ mock_device.id, SimpleMetering.attributes.Divisor:build_test_attr_report(mock_device, 0) }) + test.wait_for_events() + assert(mock_device:get_field(constants.SIMPLE_METERING_DIVISOR_KEY) == 1000, + "SIMPLE_METERING_DIVISOR_KEY should be 1000") + end +) + +test.register_coroutine_test( + "frient 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, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, SimpleMetering.ID) + }) + 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, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ElectricalMeasurement.ID) + }) + 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, + 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, + SimpleMetering.attributes.Divisor:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Multiplier:read(mock_device) + }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 19 + } +) + +test.run_registered_tests() From 76d9dbf8a43041f69e2636ca802e160dd9b14d8c Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 11 Mar 2026 19:15:29 +0900 Subject: [PATCH 188/538] Update digital key event (#2829) Signed-off-by: Hunsup Jung --- .../SmartThings/matter-lock/src/new-matter-lock/init.lua | 6 +++--- .../matter-lock/src/test/test_new_matter_lock.lua | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) 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 11aa2b0884..1f1407a96b 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -2449,11 +2449,11 @@ local function lock_op_event_handler(driver, device, ib, response) elseif opSource.value == Source.RFID then opSource = "rfid" elseif opSource.value == Source.BIOMETRIC then - opSource = "keypad" + opSource = nil -- It will be updated R2 elseif opSource.value == Source.ALIRO then - opSource = nil + opSource = "digitalKey" else - opSource =nil + opSource = nil end if userIdx ~= nil then diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 738248fd8e..5837b835db 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -861,7 +861,7 @@ test.register_message_test( message = mock_device:generate_test_message( "main", capabilities.lock.lock.unlocked( - {data = {method = "keypad", userIndex = 1}, state_change = true} + {data = {userIndex = 1}, state_change = true} ) ), }, @@ -888,7 +888,7 @@ test.register_message_test( message = mock_device:generate_test_message( "main", capabilities.lock.lock.unlocked( - {data = {userIndex = 1}, state_change = true} + {data = {method = "digitalKey", userIndex = 1}, state_change = true} ) ), } From 1a1c3f67d9bcedbb017e10a5578c32b6dcc41769 Mon Sep 17 00:00:00 2001 From: Sanghee Kim Date: Wed, 11 Mar 2026 18:34:15 +0900 Subject: [PATCH 189/538] Matter Ikea Dual Button: Added missing refresh on embedded device config --- .../matter-switch/profiles/ikea-2-button-battery.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml b/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml index 0b256f3b65..011ecc1683 100644 --- a/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml +++ b/drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml @@ -36,6 +36,9 @@ deviceConfig: - component: main capability: battery version: 1 + - component: main + capability: refresh + version: 1 - component: button2 capability: button version: 1 From 7a985d06495a2019acd169a61e0969c2bb215a14 Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:50:51 +0900 Subject: [PATCH 190/538] Postpone updating commandResult (#2765) Signed-off-by: Hunsup Jung --- .../matter-lock/src/new-matter-lock/init.lua | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) 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 1f1407a96b..00aed22adf 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -1609,17 +1609,6 @@ local function set_pin_response_handler(driver, device, ib, response) add_credential_to_table(device, userIdx, credIdx, "pin") end - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - credentialIndex = credIdx, - statusCode = status - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - -- If User Type is Guest and device support schedule, add default schedule local week_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.WEEK_DAY_ACCESS_SCHEDULES}) local year_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.YEAR_DAY_ACCESS_SCHEDULES}) @@ -1643,6 +1632,16 @@ local function set_pin_response_handler(driver, device, ib, response) ) ) else + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + credentialIndex = credIdx, + statusCode = status + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) end return @@ -2318,6 +2317,20 @@ local function set_year_day_schedule_handler(driver, device, ib, response) end if cmdName == "defaultSchedule" then + local cmdName = "addCredential" + local credIdx = device:get_field(lock_utils.CRED_INDEX) + + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + credentialIndex = credIdx, + statusCode = status + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) return end From 6a68df717af785af0512f697b7f1da80022a8c15 Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:32:16 +0900 Subject: [PATCH 191/538] Limit the length of User Name (#2764) Signed-off-by: Hunsup Jung --- ...-Rename-the-variable-and-reduce-code.patch | 77 +++++++++++++++++++ .../matter-lock/src/new-matter-lock/init.lua | 40 ++++++---- .../src/test/test_new_matter_lock.lua | 8 +- 3 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 0001-Rename-the-variable-and-reduce-code.patch diff --git a/0001-Rename-the-variable-and-reduce-code.patch b/0001-Rename-the-variable-and-reduce-code.patch new file mode 100644 index 0000000000..f52f7882ef --- /dev/null +++ b/0001-Rename-the-variable-and-reduce-code.patch @@ -0,0 +1,77 @@ +From f19da9191362d70a020ab6852233eb327c19f906 Mon Sep 17 00:00:00 2001 +From: Hunsup Jung +Date: Sat, 7 Mar 2026 15:36:45 +0900 +Subject: [PATCH] Rename the variable and reduce code + +Signed-off-by: Hunsup Jung +--- + .../matter-lock/src/new-matter-lock/init.lua | 25 +++++++++---------- + 1 file changed, 12 insertions(+), 13 deletions(-) + +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 d9b9c9f6..37a9493a 100644 +--- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua ++++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +@@ -29,7 +29,8 @@ local PowerSource = clusters.PowerSource + + local INITIAL_CREDENTIAL_INDEX = 1 + local ALL_INDEX = 0xFFFE +-local NAME_MAX_L = 10 ++-- maximum as defined by the Matter specification ++local MAX_USER_NAME_LENGTH = 10 + local MIN_EPOCH_S = 0 + local MAX_EPOCH_S = 0xffffffff + local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 +@@ -1274,10 +1275,7 @@ local function handle_update_user(driver, device, command) + local cmdName = "updateUser" + local userIdx = command.args.userIndex + local userName = command.args.userName +- local userNameMatter = userName +- if #userNameMatter > NAME_MAX_L then +- userNameMatter = string.sub(userNameMatter, 1, NAME_MAX_L) +- end ++ local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) + local userType = command.args.userType + local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER + if userType == "guest" then +@@ -1307,7 +1305,7 @@ local function handle_update_user(driver, device, command) + device:send( + DoorLock.server.commands.SetUser( + device, ep, +- DoorLock.types.DataOperationTypeEnum.MODIFY, -- Operation Type: Add(0), Modify(2) ++ DoorLock.types.DataOperationTypeEnum.MODIFY, + userIdx, + userNameMatter, + nil, -- Unique ID +@@ -1355,6 +1353,7 @@ local function get_user_response_handler(driver, device, ib, response) + -- Found available user index + if status == nil or status == DoorLock.types.UserStatusEnum.AVAILABLE then + local userName = device:get_field(lock_utils.USER_NAME) ++ local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) + local userType = device:get_field(lock_utils.USER_TYPE) + local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER + if userType == "guest" then +@@ -1368,13 +1367,13 @@ local function get_user_response_handler(driver, device, ib, response) + device:send( + DoorLock.server.commands.SetUser( + device, ep, +- DoorLock.types.DataOperationTypeEnum.ADD, -- Operation Type: Add(0), Modify(2) +- userIdx, -- User Index +- userName, -- User Name +- nil, -- Unique ID +- nil, -- User Status +- userTypeMatter, -- User Type +- nil -- Credential Rule ++ DoorLock.types.DataOperationTypeEnum.ADD, ++ userIdx, ++ userNameMatter, ++ nil, -- Unique ID ++ nil, -- User Status ++ userTypeMatter, ++ nil -- Credential Rule + ) + ) + elseif userIdx >= maxUser then -- There's no available user index +-- +2.39.5 (Apple Git-154) + 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 00aed22adf..8f2a99fd5d 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -18,6 +18,8 @@ local PowerSource = clusters.PowerSource local INITIAL_CREDENTIAL_INDEX = 1 local ALL_INDEX = 0xFFFE +-- maximum as defined by the Matter specification +local MAX_USER_NAME_LENGTH = 10 local MIN_EPOCH_S = 0 local MAX_EPOCH_S = 0xffffffff local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 @@ -1220,6 +1222,7 @@ local function handle_update_user(driver, device, command) local cmdName = "updateUser" local userIdx = command.args.userIndex local userName = command.args.userName + local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) local userType = command.args.userType local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER if userType == "guest" then @@ -1249,13 +1252,13 @@ local function handle_update_user(driver, device, command) device:send( DoorLock.server.commands.SetUser( device, ep, - DoorLock.types.DataOperationTypeEnum.MODIFY, -- Operation Type: Add(0), Modify(2) - userIdx, -- User Index - userName, -- User Name - nil, -- Unique ID - nil, -- User Status - userTypeMatter, -- User Type - nil -- Credential Rule + DoorLock.types.DataOperationTypeEnum.MODIFY, + userIdx, + userNameMatter, + nil, -- Unique ID + nil, -- User Status + userTypeMatter, + nil -- Credential Rule ) ) end @@ -1297,6 +1300,7 @@ local function get_user_response_handler(driver, device, ib, response) -- Found available user index if status == nil or status == DoorLock.types.UserStatusEnum.AVAILABLE then local userName = device:get_field(lock_utils.USER_NAME) + local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) local userType = device:get_field(lock_utils.USER_TYPE) local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER if userType == "guest" then @@ -1310,13 +1314,13 @@ local function get_user_response_handler(driver, device, ib, response) device:send( DoorLock.server.commands.SetUser( device, ep, - DoorLock.types.DataOperationTypeEnum.ADD, -- Operation Type: Add(0), Modify(2) - userIdx, -- User Index - userName, -- User Name - nil, -- Unique ID - nil, -- User Status - userTypeMatter, -- User Type - nil -- Credential Rule + DoorLock.types.DataOperationTypeEnum.ADD, + userIdx, + userNameMatter, + nil, -- Unique ID + nil, -- User Status + userTypeMatter, + nil -- Credential Rule ) ) elseif userIdx >= maxUser then -- There's no available user index @@ -1477,14 +1481,16 @@ local function handle_add_credential(driver, device, command) -- Get parameters local cmdName = "addCredential" local userIdx = command.args.userIndex - if userIdx == 0 then - userIdx = nil - end local userType = command.args.userType local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER if userType == "guest" then userTypeMatter = DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER end + if userIdx == 0 then + userIdx = nil + else + userTypeMatter = nil + end local credential = { credential_type = DoorLock.types.CredentialTypeEnum.PIN, credential_index = INITIAL_CREDENTIAL_INDEX diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 5837b835db..43d6db2084 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -1309,7 +1309,7 @@ test.register_coroutine_test( "654123", -- credential_data 1, -- user_index nil, -- user_status - DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + nil -- user_type ), } ) @@ -1397,7 +1397,7 @@ test.register_coroutine_test( "654123", -- credential_data 1, -- user_index nil, -- user_status - DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + nil -- user_type ), } ) @@ -1450,7 +1450,7 @@ test.register_coroutine_test( "654123", -- credential_data 1, -- user_index nil, -- user_status - DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + nil -- user_type ), } ) @@ -1510,7 +1510,7 @@ test.register_coroutine_test( "654123", -- credential_data 1, -- user_index nil, -- user_status - DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + nil -- user_type ), } ) From bfd15a766e63108062dc7389ac65f2b0c4be9861 Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:41:12 +0900 Subject: [PATCH 192/538] Remove garbage file (#2835) Signed-off-by: Hunsup Jung --- ...-Rename-the-variable-and-reduce-code.patch | 77 ------------------- 1 file changed, 77 deletions(-) delete mode 100644 0001-Rename-the-variable-and-reduce-code.patch diff --git a/0001-Rename-the-variable-and-reduce-code.patch b/0001-Rename-the-variable-and-reduce-code.patch deleted file mode 100644 index f52f7882ef..0000000000 --- a/0001-Rename-the-variable-and-reduce-code.patch +++ /dev/null @@ -1,77 +0,0 @@ -From f19da9191362d70a020ab6852233eb327c19f906 Mon Sep 17 00:00:00 2001 -From: Hunsup Jung -Date: Sat, 7 Mar 2026 15:36:45 +0900 -Subject: [PATCH] Rename the variable and reduce code - -Signed-off-by: Hunsup Jung ---- - .../matter-lock/src/new-matter-lock/init.lua | 25 +++++++++---------- - 1 file changed, 12 insertions(+), 13 deletions(-) - -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 d9b9c9f6..37a9493a 100644 ---- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua -+++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua -@@ -29,7 +29,8 @@ local PowerSource = clusters.PowerSource - - local INITIAL_CREDENTIAL_INDEX = 1 - local ALL_INDEX = 0xFFFE --local NAME_MAX_L = 10 -+-- maximum as defined by the Matter specification -+local MAX_USER_NAME_LENGTH = 10 - local MIN_EPOCH_S = 0 - local MAX_EPOCH_S = 0xffffffff - local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 -@@ -1274,10 +1275,7 @@ local function handle_update_user(driver, device, command) - local cmdName = "updateUser" - local userIdx = command.args.userIndex - local userName = command.args.userName -- local userNameMatter = userName -- if #userNameMatter > NAME_MAX_L then -- userNameMatter = string.sub(userNameMatter, 1, NAME_MAX_L) -- end -+ local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) - local userType = command.args.userType - local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER - if userType == "guest" then -@@ -1307,7 +1305,7 @@ local function handle_update_user(driver, device, command) - device:send( - DoorLock.server.commands.SetUser( - device, ep, -- DoorLock.types.DataOperationTypeEnum.MODIFY, -- Operation Type: Add(0), Modify(2) -+ DoorLock.types.DataOperationTypeEnum.MODIFY, - userIdx, - userNameMatter, - nil, -- Unique ID -@@ -1355,6 +1353,7 @@ local function get_user_response_handler(driver, device, ib, response) - -- Found available user index - if status == nil or status == DoorLock.types.UserStatusEnum.AVAILABLE then - local userName = device:get_field(lock_utils.USER_NAME) -+ local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) - local userType = device:get_field(lock_utils.USER_TYPE) - local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER - if userType == "guest" then -@@ -1368,13 +1367,13 @@ local function get_user_response_handler(driver, device, ib, response) - device:send( - DoorLock.server.commands.SetUser( - device, ep, -- DoorLock.types.DataOperationTypeEnum.ADD, -- Operation Type: Add(0), Modify(2) -- userIdx, -- User Index -- userName, -- User Name -- nil, -- Unique ID -- nil, -- User Status -- userTypeMatter, -- User Type -- nil -- Credential Rule -+ DoorLock.types.DataOperationTypeEnum.ADD, -+ userIdx, -+ userNameMatter, -+ nil, -- Unique ID -+ nil, -- User Status -+ userTypeMatter, -+ nil -- Credential Rule - ) - ) - elseif userIdx >= maxUser then -- There's no available user index --- -2.39.5 (Apple Git-154) - From 79f764c2100525fc8c682e148331a29d6f1fcae1 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Fri, 6 Mar 2026 11:49:04 -0600 Subject: [PATCH 193/538] move AttributeList read to subscribe --- .../matter-lock/src/new-matter-lock/init.lua | 40 +++++++++---------- .../src/test/test_matter_lock_modular.lua | 12 +++--- .../src/test/test_new_matter_lock_battery.lua | 9 +++-- 3 files changed, 31 insertions(+), 30 deletions(-) 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 8f2a99fd5d..bc38370d37 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -133,6 +133,11 @@ end local function device_init(driver, device) device:set_component_to_endpoint_fn(component_to_endpoint) + if #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) == 0 then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist = true}) + elseif device:get_field(profiling_data.BATTERY_SUPPORT) == nil then + device:add_subscribed_attribute(clusters.PowerSource.attributes.AttributeList) + end for cap_id, attributes in pairs(subscribed_attributes) do if device:supports_capability_by_id(cap_id) then for _, attr in ipairs(attributes) do @@ -152,12 +157,6 @@ local function device_init(driver, device) local function device_added(driver, device) device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) - 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(device)) - else - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, { persist = true }) - end end local function match_profile_modular(driver, device) @@ -590,23 +589,22 @@ end -- Power Source Attribute List -- --------------------------------- local function handle_power_source_attribute_list(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, { persist = true }) - match_profile(driver, device) - return - elseif attr.value == 0x0E then - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_LEVEL, { persist = true }) - match_profile(driver, device) - return + local latest_battery_support = device:get_field(profiling_data.BATTERY_SUPPORT) + for _, attr in ipairs(ib.data.elements or {}) do + if attr.value == clusters.PowerSource.attributes.BatPercentRemaining.ID then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_PERCENTAGE, {persist=true}) + break -- BATTERY_PERCENTAGE is highest priority. break early if found + elseif attr.value == clusters.PowerSource.attributes.BatChargeLevel.ID then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_LEVEL, {persist=true}) end end - - -- neither BatChargeLevel nor BatPercentRemaining were found. Re-profiling without battery. - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, { persist = true }) - match_profile(driver, device) + -- in the case that 1) no battery has been set, and 2) the returned ib does not include battery attributes, ignore battery + if latest_battery_support == nil and not device:get_field(profiling_data.BATTERY_SUPPORT) then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist=true}) + end + if latest_battery_support == nil or latest_battery_support ~= device:get_field(profiling_data.BATTERY_SUPPORT) then + match_profile(driver, device) + end end ------------------------------- diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index c4c226fbbe..6cc4709637 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -208,6 +208,8 @@ local function test_init() subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device)) + -- add test device test.mock_device.add_test_device(mock_device) -- actual onboarding flow @@ -215,7 +217,6 @@ local function test_init() test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device.id, clusters.PowerSource.attributes.AttributeList:read()}) 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" }) @@ -229,6 +230,7 @@ local function test_init_unlatch() subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_unlatch)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_unlatch)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_unlatch)) -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_unlatch) -- actual onboarding flow @@ -236,7 +238,6 @@ local function test_init_unlatch() test.socket.capability:__expect_send( mock_device_unlatch:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_unlatch.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_unlatch.id, "init" }) test.socket.matter:__expect_send({mock_device_unlatch.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_unlatch.id, "doConfigure" }) @@ -256,6 +257,7 @@ local function test_init_user_pin() subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin)) -- add test device test.mock_device.add_test_device(mock_device_user_pin) -- actual onboarding flow @@ -263,7 +265,6 @@ local function test_init_user_pin() test.socket.capability:__expect_send( mock_device_user_pin:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_user_pin.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin.id, "init" }) test.socket.matter:__expect_send({mock_device_user_pin.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin.id, "doConfigure" }) @@ -285,6 +286,7 @@ local function test_init_user_pin_schedule_unlatch() subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin_schedule_unlatch)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin_schedule_unlatch)) -- add test device test.mock_device.add_test_device(mock_device_user_pin_schedule_unlatch) -- actual onboarding flow @@ -292,7 +294,6 @@ local function test_init_user_pin_schedule_unlatch() test.socket.capability:__expect_send( mock_device_user_pin_schedule_unlatch:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_user_pin_schedule_unlatch.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin_schedule_unlatch.id, "init" }) test.socket.matter:__expect_send({mock_device_user_pin_schedule_unlatch.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin_schedule_unlatch.id, "doConfigure" }) @@ -306,6 +307,7 @@ local function test_init_modular() subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_modular)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_modular)) -- add test device test.mock_device.add_test_device(mock_device_modular) -- actual onboarding flow @@ -313,7 +315,6 @@ local function test_init_modular() test.socket.capability:__expect_send( mock_device_modular:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_modular.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_modular.id, "init" }) test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_modular.id, "doConfigure" }) @@ -630,6 +631,7 @@ test.register_coroutine_test( subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_modular)) subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device_modular)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_modular)) test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) test.socket.capability:__expect_send( mock_device_modular:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua index 03a51a3150..8ce6e61be5 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua @@ -170,6 +170,7 @@ local function test_init() subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device)) -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) @@ -178,7 +179,6 @@ local function test_init() test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device.id, clusters.PowerSource.attributes.AttributeList:read()}) 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" }) @@ -192,6 +192,7 @@ local function test_init_unlatch() subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_unlatch)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_unlatch)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_unlatch)) -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_unlatch) test.socket.matter:__expect_send({mock_device_unlatch.id, subscribe_request}) @@ -200,7 +201,6 @@ local function test_init_unlatch() test.socket.capability:__expect_send( mock_device_unlatch:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_unlatch.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_unlatch.id, "init" }) test.socket.matter:__expect_send({mock_device_unlatch.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_unlatch.id, "doConfigure" }) @@ -220,6 +220,7 @@ local function test_init_user_pin() subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin)) -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_user_pin) test.socket.matter:__expect_send({mock_device_user_pin.id, subscribe_request}) @@ -228,7 +229,6 @@ local function test_init_user_pin() test.socket.capability:__expect_send( mock_device_user_pin:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_user_pin.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin.id, "init" }) test.socket.matter:__expect_send({mock_device_user_pin.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin.id, "doConfigure" }) @@ -250,6 +250,8 @@ local function test_init_user_pin_schedule_unlatch() subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin_schedule_unlatch)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin_schedule_unlatch)) + -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_user_pin_schedule_unlatch) test.socket.matter:__expect_send({mock_device_user_pin_schedule_unlatch.id, subscribe_request}) @@ -258,7 +260,6 @@ local function test_init_user_pin_schedule_unlatch() test.socket.capability:__expect_send( mock_device_user_pin_schedule_unlatch:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - test.socket.matter:__expect_send({mock_device_user_pin_schedule_unlatch.id, clusters.PowerSource.attributes.AttributeList:read()}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin_schedule_unlatch.id, "init" }) test.socket.matter:__expect_send({mock_device_user_pin_schedule_unlatch.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_user_pin_schedule_unlatch.id, "doConfigure" }) From ee57c1298b234b9f26c5a5efb8e26e7fcc80391d Mon Sep 17 00:00:00 2001 From: Sanghee Kim <55477180+sangheedotkim@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:54:04 +0900 Subject: [PATCH 194/538] Matter Ikea Scroll: Changed to use custom presentation (#2825) For custom plugin to provide better device-specific user experience --- .../matter-switch/profiles/ikea-scroll.yml | 49 ++----------------- 1 file changed, 3 insertions(+), 46 deletions(-) diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml index 166b0a62f1..973c789ac6 100644 --- a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml +++ b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml @@ -33,49 +33,6 @@ components: version: 1 categories: - name: Button -deviceConfig: - icons: - - group: main - iconUrl: 'icon://button_wheel' - dashboard: - states: - - component: main - capability: button - version: 1 - detailView: - - component: main - capability: button - version: 1 - - component: main - capability: knob - version: 1 - - component: main - capability: battery - version: 1 - - component: group2 - capability: button - version: 1 - - component: group2 - capability: knob - version: 1 - - component: group3 - capability: button - version: 1 - - component: group3 - capability: knob - version: 1 - automation: - conditions: - - component: main - capability: button - version: 1 - - component: main - capability: battery - version: 1 - - component: group2 - capability: button - version: 1 - - component: group3 - capability: button - version: 1 - actions: [] +metadata: + mnmn: SmartThingsEdge + vid: ikea-scroll \ No newline at end of file From 84c4d1d1f6b4ffc40f29a78310c6fad519c4579f Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:11:47 +0900 Subject: [PATCH 195/538] Add lockAlarm configuration to do_configure (#2763) Signed-off-by: HunsupJung --- .../matter-lock/src/new-matter-lock/init.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 bc38370d37..b9fe8f3bb1 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -262,8 +262,10 @@ local function info_changed(driver, device, event, args) end end device:subscribe() - device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) - device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is madatory + if device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then + device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) + device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory + end end local function profiling_data_still_required(device) @@ -287,6 +289,10 @@ end local function do_configure(driver, device) match_profile(driver, device) + device.thread:call_with_delay(5, function() + device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) + device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory + end) end local function driver_switched(driver, device) From ab917347fcf3facb2856c563acef3f1477d455f7 Mon Sep 17 00:00:00 2001 From: seojune79 <119141897+seojune79@users.noreply.github.com> Date: Wed, 25 Mar 2026 02:34:00 +0900 Subject: [PATCH 196/538] [Aqara/Locks] Improvement of credential info management (#2712) * Update the system to manage credentialInfoTable within the persist area * Improvement of credential info management during Hub switch-over or replacement. * Remove whitespace * Revision of credential data management * Modify to save the current data if PERSIST_DATA is nil. * Fix code indentation * Adding test cases * Remove the unused variables --------- Co-authored-by: Carter Swedal --- .../Aqara/aqara-lock/src/credential_utils.lua | 30 ++++++++ drivers/Aqara/aqara-lock/src/init.lua | 1 + .../src/test/test_aqara_lock_L100.lua | 77 +++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua diff --git a/drivers/Aqara/aqara-lock/src/credential_utils.lua b/drivers/Aqara/aqara-lock/src/credential_utils.lua index 5725d5f55a..df0e904886 100644 --- a/drivers/Aqara/aqara-lock/src/credential_utils.lua +++ b/drivers/Aqara/aqara-lock/src/credential_utils.lua @@ -6,6 +6,31 @@ local lockCredentialInfo = capabilities["stse.lockCredentialInfo"] local credential_utils = {} local HOST_COUNT = "__host_count" +local PERSIST_DATA = "__persist_area" + +credential_utils.eventResource = function(table) + local credentialResource = {} + for key, value in pairs(table) do + credentialResource[key] = value + end + return credentialResource +end + +credential_utils.backup_data = function(device) -- Back up data the persistent + local credentialInfoTable = utils.deep_copy(device:get_latest_state("main", lockCredentialInfo.ID, + lockCredentialInfo.credentialInfo.NAME, {})) + device:set_field(PERSIST_DATA, credentialInfoTable, { persist = true }) +end + +credential_utils.sync = function(driver, device) + local table = device:get_field(PERSIST_DATA) or nil + if table ~= nil then + device:emit_event(lockCredentialInfo.credentialInfo(credential_utils.eventResource(table), + { visibility = { displayed = false } })) + else + credential_utils.backup_data(device) + end +end credential_utils.save_data = function(driver) driver.datastore:save() @@ -28,6 +53,7 @@ credential_utils.update_remote_control_status = function(driver, device, added) end device:set_field(HOST_COUNT, host_cnt, { persist = true }) + credential_utils.backup_data(device) credential_utils.save_data(driver) end @@ -38,6 +64,7 @@ credential_utils.sync_all_credential_info = function(driver, device, command) end end device:emit_event(lockCredentialInfo.credentialInfo(command.args.credentialInfo, { visibility = { displayed = false } })) + credential_utils.backup_data(device) credential_utils.save_data(driver) end @@ -73,6 +100,7 @@ credential_utils.upsert_credential_info = function(driver, device, command) end device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) + credential_utils.backup_data(device) credential_utils.save_data(driver) end @@ -95,6 +123,7 @@ credential_utils.delete_user = function(driver, device, command) end device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) + credential_utils.backup_data(device) credential_utils.save_data(driver) end @@ -116,6 +145,7 @@ credential_utils.delete_credential = function(driver, device, command) end device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) + credential_utils.backup_data(device) credential_utils.save_data(driver) end diff --git a/drivers/Aqara/aqara-lock/src/init.lua b/drivers/Aqara/aqara-lock/src/init.lua index e9eb32c9b4..9afe6bb77e 100644 --- a/drivers/Aqara/aqara-lock/src/init.lua +++ b/drivers/Aqara/aqara-lock/src/init.lua @@ -86,6 +86,7 @@ local function device_init(self, device) end device:emit_event(capabilities.battery.quantity(battery_quantity)) device:emit_event(capabilities.batteryLevel.quantity(battery_quantity)) + credential_utils.sync(self, device) end local function device_added(self, device) diff --git a/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua b/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua new file mode 100644 index 0000000000..3801183490 --- /dev/null +++ b/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua @@ -0,0 +1,77 @@ +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 remoteControlStatus = capabilities.remoteControlStatus +local antiLockStatus = capabilities["stse.antiLockStatus"] +test.add_package_capability("antiLockStatus.yaml") +local lockCredentialInfo = capabilities["stse.lockCredentialInfo"] +test.add_package_capability("lockCredentialInfo.yaml") +local lockAlarm = capabilities["lockAlarm"] +test.add_package_capability("lockAlarm.yaml") +local Battery = capabilities.battery +local BatteryLevel = capabilities.batteryLevel +local Lock = capabilities.lock + +local PRI_CLU = 0xFCC0 + +local HOST_COUNT = "__host_count" +local PERSIST_DATA = "__persist_area" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-lock-battery.yml"), + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Lumi", + model = "aqara.lock.akr001", + server_clusters = { PRI_CLU } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + local SUPPORTED_ALARM_VALUES = { "damaged", "forcedOpeningAttempt", "unableToLockTheDoor", "notClosedForALongTime", + "highTemperature", "attemptsExceeded" } + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + lockAlarm.supportedAlarmValues(SUPPORTED_ALARM_VALUES, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + Lock.supportedUnlockDirections({"fromInside", "fromOutside"}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.type("AA"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.type("AA"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.quantity(6))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.quantity(6))) + local credentialInfoData = { + { credentialId = 1, credentialType = "keypad", userId = "1", userLabel = "june", userType = "host" } + } + mock_device:set_field(PERSIST_DATA, credentialInfoData, { persist = true }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + lockCredentialInfo.credentialInfo(credentialInfoData, { visibility = { displayed = false } }))) + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle added lifecycle - only regular user", + function() + mock_device:set_field(HOST_COUNT, 1, { persist = true }) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + remoteControlStatus.remoteControlEnabled('true', { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.battery(100))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.battery("normal"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + lockAlarm.alarm.clear({ visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + antiLockStatus.antiLockStatus('unknown', { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Lock.lock("locked"))) + end +) + +test.run_registered_tests() From 0e25f2f23183230ca19a38a509e38f9def3edf94 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Wed, 25 Feb 2026 16:14:23 -0600 Subject: [PATCH 197/538] in infoChanged handler, update profile_changed across drivers --- .../matter-lock/src/new-matter-lock/init.lua | 50 +++++++- .../src/test/test_matter_lock_modular.lua | 26 +++-- .../air_quality_sensor_utils/utils.lua | 50 ++++++-- .../sub_drivers/air_quality_sensor/init.lua | 3 +- ...test_matter_air_quality_sensor_modular.lua | 101 ++++++++++++++++ .../SmartThings/matter-switch/src/init.lua | 3 +- .../sub_drivers/camera/camera_utils/utils.lua | 18 --- .../src/sub_drivers/camera/init.lua | 2 +- .../src/switch_utils/device_configuration.lua | 1 - .../matter-switch/src/switch_utils/fields.lua | 2 - .../matter-switch/src/switch_utils/utils.lua | 44 +++++++ .../src/test/test_matter_light_fan.lua | 71 +++++++++-- .../matter-thermostat/src/init.lua | 2 +- .../test/test_matter_thermostat_modular.lua | 110 ++++++++++++++++++ .../src/thermostat_utils/utils.lua | 44 +++++++ 15 files changed, 468 insertions(+), 59 deletions(-) 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 b9fe8f3bb1..fdae5ec432 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -24,8 +24,6 @@ local MIN_EPOCH_S = 0 local MAX_EPOCH_S = 0xffffffff local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 -local MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED" - local RESPONSE_STATUS_MAP = { [DoorLock.types.DlStatus.SUCCESS] = "success", [DoorLock.types.DlStatus.FAILURE] = "failure", @@ -204,7 +202,6 @@ local function match_profile_modular(driver, device) table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) - device:set_field(MODULAR_PROFILE_UPDATED, true) end local function match_profile_switch(driver, device) @@ -242,11 +239,54 @@ local function match_profile_switch(driver, device) device:try_update_metadata({profile = profile_name}) end +--- Deeply compare two values. +--- Handles metatables. Can optionally ignore cycle checking and/or function differences. +--- +--- @param a any +--- @param b any +--- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } +--- @param seen table|nil +--- @return boolean +local function deep_equals(a, b, opts, seen) + if a == b then return true end -- same object + if type(a) ~= type(b) then return false end -- different type + if type(a) == "function" and opts and opts.ignore_functions then return true end + if type(a) ~= "table" then return false end -- same type but not table, thus was already compared + + -- check for cycles in table references and preserve reference topology. + if not (opts and opts.ignore_cycles) then + seen = seen or {} + seen[a] = seen[a] or {} + if seen[a][b] then + return seen[a][b] + end + seen[a][b] = true + end + + -- Compare keys/values from a + for k, v in next, a do + if not deep_equals(v, rawget(b, k), opts, seen) then + return false + end + end + + -- Ensure b doesn't have extra keys + for k in next, b do + if rawget(a, k) == nil then + return false + end + end + + -- Compare metatables + local mt_a = getmetatable(a) + local mt_b = getmetatable(b) + return deep_equals(mt_a, mt_b, opts, seen) +end + local function info_changed(driver, device, event, args) - if device.profile.id == args.old_st_store.profile.id and not device:get_field(MODULAR_PROFILE_UPDATED) then + if deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then return end - device:set_field(MODULAR_PROFILE_UPDATED, nil) for cap_id, attributes in pairs(subscribed_attributes) do if device:supports_capability_by_id(cap_id) then for _, attr in ipairs(attributes) do diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index 6cc4709637..468863938d 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -608,16 +608,26 @@ test.register_coroutine_test( mock_device_modular:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) ) mock_device_modular:expect_metadata_update({ profile = "lock-modular-embedded-unlatch", optional_component_capabilities = {{"main", {"lockUsers", "lockCredentials", "lockSchedules", "battery"}}} }) + end, + { test_init = test_init_modular } +) - local updated_device_profile = t_utils.get_profile_definition("lock-modular-embedded-unlatch.yml", - {enabled_optional_capabilities = {{ "main", {"lockUsers", "lockCredentials", "lockSchedules", "battery"}}, - },} - ) - updated_device_profile.id = "00000000-1111-2222-3333-000000000010" - - test.wait_for_events() - test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({ profile = updated_device_profile })) +test.register_coroutine_test( + "No component-capability update and no profile ID update should not cause a re-subscribe in infoChanged handler", function() + -- simulate no actual change + test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({})) + end, + { test_init = test_init_modular } +) +test.register_coroutine_test( + "Component-capability update without profile ID update should cause re-subscribe in infoChanged handler", function() + test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed( + {profile = {id = "00000000-1111-2222-3333-000000000010", components = { main = {capabilities={ + ["lock"]= {id="lock", version=1}, ["lockAlarm"] = {id="lockAlarm", version=1}, ["remoteControlStatus"] = {id="remoteControlStatus", version=1}, + ["lockUsers"] = {id="lockUsers", version=1}, ["lockCredentials"] = {id="lockCredentials", version=1}, ["lockSchedules"] = {id="lockSchedules", version=1}, + ["battery"] = {id="battery", version=1}, ["firmwareUpdate"] = {id="firmwareUpdate", version=1}, ["refresh"] = {id="refresh", version=1}}}}}}) + ) local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_modular) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device_modular)) 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 index 95ca80964c..e23b9f7eeb 100644 --- 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 @@ -77,22 +77,48 @@ function AirQualitySensorUtils.set_supported_health_concern_values(device) end end -function AirQualitySensorUtils.profile_changed(synced_components, prev_components) - if #synced_components ~= #prev_components then - return true +--- Deeply compare two values. +--- Handles metatables. Can optionally ignore cycle checking and/or function differences. +--- +--- @param a any +--- @param b any +--- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } +--- @param seen table|nil +--- @return boolean +function AirQualitySensorUtils.deep_equals(a, b, opts, seen) + if a == b then return true end -- same object + if type(a) ~= type(b) then return false end -- different type + if type(a) == "function" and opts and opts.ignore_functions then return true end + if type(a) ~= "table" then return false end -- same type but not table, thus was already compared + + -- check for cycles in table references and preserve reference topology. + if not (opts and opts.ignore_cycles) then + seen = seen or {} + seen[a] = seen[a] or {} + if seen[a][b] then + return seen[a][b] + end + seen[a][b] = 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 + + -- Compare keys/values from a + for k, v in next, a do + if not AirQualitySensorUtils.deep_equals(v, rawget(b, k), opts, seen) then + return false end - for _, capability in pairs(component.capabilities or {}) do - if prev_components[component.id][capability.id] == nil then - return true - end + end + + -- Ensure b doesn't have extra keys + for k in next, b do + if rawget(a, k) == nil then + return false end end - return false + + -- Compare metatables + local mt_a = getmetatable(a) + local mt_b = getmetatable(b) + return AirQualitySensorUtils.deep_equals(mt_a, mt_b, opts, seen) end return AirQualitySensorUtils 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 index 98b8430c98..e8d0cd4701 100644 --- 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 @@ -66,8 +66,7 @@ function AirQualitySensorLifecycleHandlers.device_init(driver, device) end function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, args) - if device.profile.id ~= args.old_st_store.profile.id or - aqs_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) then + if not aqs_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) 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) 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 5b6b43f2f5..9f2c4c636a 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 @@ -82,6 +82,36 @@ local mock_device_common = test.mock_device.build_test_matter_device({ } }) +local mock_device_modular_fingerprint = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("aqs-modular.yml", + {enabled_optional_capabilities = {{"main", {}}}}), + 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.AirQuality.ID, cluster_type = "SERVER", feature_map = 3}, + {cluster_id = clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 1}, + }, + device_types = { + {device_type_id = 0x002C, device_type_revision = 1} -- Air Quality Sensor + } + } + } +}) + 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" }) @@ -94,6 +124,18 @@ local function test_init_all() test.socket.matter:__expect_send({mock_device_all.id, subscribe_request}) end +local function test_init_modular_fingerprint() + test.mock_device.add_test_device(mock_device_modular_fingerprint) + test.socket.device_lifecycle:__queue_receive({ mock_device_modular_fingerprint.id, "init" }) + test.socket.capability:__expect_send(mock_device_modular_fingerprint: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_modular_fingerprint) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.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" }) @@ -243,6 +285,30 @@ local function get_subscribe_request_common() return subscribe_request end +local function get_subscribe_request_tvoc() + local subscribed_attributes = { + [capabilities.airQualityHealthConcern.ID] = { + clusters.AirQuality.attributes.AirQuality + }, + [capabilities.tvocMeasurement.ID] = { + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue, + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit, + }, + } + 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_modular_fingerprint) + else + subscribe_request:merge(attribute:subscribe(mock_device_modular_fingerprint)) + end + end + end + return subscribe_request +end + + -- run the profile configuration tests 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"}) @@ -349,5 +415,40 @@ test.register_coroutine_test( { test_init = test_init_common } ) +test.register_coroutine_test( + "Component-capability update without profile ID update should cause re-subscribe in infoChanged handler", + function() + local expected_metadata_modular_disabled = { + optional_component_capabilities={ + { + "main", + { + "tvocMeasurement", + }, + }, + }, + profile="aqs-modular", + } + local subscribe_request_tvoc = get_subscribe_request_tvoc() + local updated_device_profile = t_utils.get_profile_definition("aqs-modular.yml", + {enabled_optional_capabilities = expected_metadata_modular_disabled.optional_component_capabilities} + ) + updated_device_profile.id = "00000000-1111-2222-3333-000000000006" + test.socket.device_lifecycle:__queue_receive(mock_device_modular_fingerprint:generate_info_changed({ profile = updated_device_profile })) + test.socket.capability:__expect_send(mock_device_modular_fingerprint:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "unhealthy", "moderate", "slightlyUnhealthy"}, {visibility={displayed=false}}))) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, subscribe_request_tvoc}) + end, + { test_init = test_init_modular_fingerprint } +) + +test.register_coroutine_test( + "No component-capability update and no profile ID update should not cause a re-subscribe in infoChanged handler", + function() + -- simulate no actual change + test.socket.device_lifecycle:__queue_receive(mock_device_modular_fingerprint:generate_info_changed({})) + end, + { test_init = test_init_modular_fingerprint } +) + -- run tests test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index c2f8f1c5ee..b1c6a8df8b 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -60,8 +60,7 @@ function SwitchLifecycleHandlers.driver_switched(driver, device) end function SwitchLifecycleHandlers.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 - device:set_field(fields.MODULAR_PROFILE_UPDATED, nil) + if not switch_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then if device.network_type == device_lib.NETWORK_TYPE_MATTER then device:subscribe() button_cfg.configure_buttons(device, 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 index 12341f493e..5793c1d9fc 100644 --- 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 @@ -134,24 +134,6 @@ function CameraUtils.build_supported_resolutions(device, max_encoded_pixel_rate, return resolutions 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 = {} diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index 179ed54742..24ca154950 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -47,7 +47,7 @@ function CameraLifecycleHandlers.driver_switched(driver, device) end function CameraLifecycleHandlers.info_changed(driver, device, event, args) - if camera_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) then + if not switch_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 18219ef459..ab2e075eb7 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -241,7 +241,6 @@ function DeviceConfiguration.match_profile(driver, device) 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 2b315c6528..65a962ca2c 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -148,8 +148,6 @@ SwitchFields.ELECTRICAL_SENSOR_EPS = "__electrical_sensor_eps" --- for an Electrical Sensor EP with a "primary" endpoint, used during device profiling. SwitchFields.ELECTRICAL_TAGS = "__electrical_tags" -SwitchFields.MODULAR_PROFILE_UPDATED = "__modular_profile_updated" - SwitchFields.profiling_data = { POWER_TOPOLOGY = "__power_topology", BATTERY_SUPPORT = "__battery_support", diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 9fd4c77c84..1afecb318f 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -326,6 +326,50 @@ function utils.create_multi_press_values_list(size, supportsHeld) return list end +--- Deeply compare two values. +--- Handles metatables. Can optionally ignore cycle checking and/or function differences. +--- +--- @param a any +--- @param b any +--- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } +--- @param seen table|nil +--- @return boolean +function utils.deep_equals(a, b, opts, seen) + if a == b then return true end -- same object + if type(a) ~= type(b) then return false end -- different type + if type(a) == "function" and opts and opts.ignore_functions then return true end + if type(a) ~= "table" then return false end -- same type but not table, thus was already compared + + -- check for cycles in table references and preserve reference topology. + if not (opts and opts.ignore_cycles) then + seen = seen or {} + seen[a] = seen[a] or {} + if seen[a][b] then + return seen[a][b] + end + seen[a][b] = true + end + + -- Compare keys/values from a + for k, v in next, a do + if not utils.deep_equals(v, rawget(b, k), opts, seen) then + return false + end + end + + -- Ensure b doesn't have extra keys + for k in next, b do + if rawget(a, k) == nil then + return false + end + end + + -- Compare metatables + local mt_a = getmetatable(a) + local mt_b = getmetatable(b) + return utils.deep_equals(mt_a, mt_b, opts, seen) +end + function utils.detect_bridge(device) return #utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.AGGREGATOR) > 0 end 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 e1af3ad52b..c3598a38ad 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 @@ -16,7 +16,8 @@ local mock_device_ep2 = 2 local mock_device = test.mock_device.build_test_matter_device({ label = "Matter Fan Light", - profile = t_utils.get_profile_definition("fan-modular.yml", {}), + profile = t_utils.get_profile_definition("fan-modular.yml", + {enabled_optional_capabilities = {{"main", {"fanSpeedPercent", "fanMode"}}}}), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -58,6 +59,40 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) +local mock_device_capabilities_disabled = test.mock_device.build_test_matter_device({ + label = "Matter Fan Light", + profile = t_utils.get_profile_definition("fan-modular.yml", + {enabled_optional_capabilities = {{"main", {}}}}), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + matter_version = { + software = 1, + hardware = 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 = mock_device_ep2, + clusters = { + {cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 15}, + }, + device_types = { + {device_type_id = 0x002B, device_type_revision = 1,} -- Fan + } + } + } +}) + local CLUSTER_SUBSCRIBE_LIST ={ clusters.OnOff.attributes.OnOff, clusters.LevelControl.attributes.CurrentLevel, @@ -110,16 +145,38 @@ local function test_init() }) 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) +test.register_coroutine_test( + "Component-capability update without profile ID update should cause re-subscribe in infoChanged handler", function() + local cluster_subscribe_list ={ + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode, + clusters.FanControl.attributes.PercentCurrent, + } + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_capabilities_disabled) + for i, clus in ipairs(cluster_subscribe_list) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_capabilities_disabled)) end + end + test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed( + {profile = {id = "00000000-1111-2222-3333-000000000004", components = { main = {capabilities={["fanSpeedPercent"] = {id="fanSpeedPercent", version=1}, ["fanMode"] = {id="fanMode", version=1}, ["firmwareUpdate"] = {id="firmwareUpdate", version=1}, ["refresh"] = {id="refresh", version=1}}}}}}) + ) + test.socket.matter:__expect_send({mock_device_capabilities_disabled.id, subscribe_request}) + end, + { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } +) + +test.register_coroutine_test( + "No component-capability update and no profile ID update should not cause a re-subscribe in infoChanged handler", function() + -- simulate no actual change + test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed({})) + end, + { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } +) + + test.register_coroutine_test( "Switch capability should send the appropriate commands", function() test.socket.capability:__queue_receive( diff --git a/drivers/SmartThings/matter-thermostat/src/init.lua b/drivers/SmartThings/matter-thermostat/src/init.lua index 54cb3318f6..d438ba0952 100644 --- a/drivers/SmartThings/matter-thermostat/src/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/init.lua @@ -116,7 +116,7 @@ function ThermostatLifecycleHandlers.info_changed(driver, device, event, args) device:extend_device("supports_capability_by_id", thermostat_utils.supports_capability_by_id_modular) end - if device.profile.id ~= args.old_st_store.profile.id then + if not thermostat_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then thermostat_utils.handle_thermostat_operating_state_info(device) device:subscribe() 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 bf985671cf..16e5be7660 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 @@ -47,6 +47,43 @@ local mock_device_basic = test.mock_device.build_test_matter_device({ } }) +local mock_device_modular = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("thermostat-modular.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.FanControl.ID, cluster_type = "SERVER", feature_map = 0}, + { + cluster_id = clusters.Thermostat.ID, + cluster_revision=5, + cluster_type="SERVER", + feature_map=3, -- Heat and Cool features + }, + {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER", feature_map = 0}, + }, + device_types = { + {device_type_id = 0x0301, device_type_revision = 1} -- Thermostat + } + } + } +}) + -- create test_init functions local function initialize_mock_device(generic_mock_device, generic_subscribed_attributes) local subscribe_request = generic_subscribed_attributes[1]:subscribe(generic_mock_device) @@ -143,5 +180,78 @@ test.register_coroutine_test( { test_init = test_init } ) +local function initialize_subscribe_request(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) + else + subscribe_request:merge(attribute:subscribe(mock_device)) + end + end + end + return subscribe_request +end + + +local function test_init_modular_fingerprint() + test.mock_device.add_test_device(mock_device_modular) + test.socket.device_lifecycle:__queue_receive({ mock_device_modular.id, "init" }) + local subscribe_request = initialize_subscribe_request(mock_device_modular, { + [clusters.Thermostat.ID] = { + clusters.Thermostat.attributes.LocalTemperature, + clusters.Thermostat.attributes.SystemMode, + clusters.Thermostat.attributes.ControlSequenceOfOperation, + }, + [clusters.TemperatureMeasurement.ID] = { + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue, + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + }, + }) + test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) +end + +test.register_coroutine_test( +"Component-capability update without profile ID update should cause re-subscribe in infoChanged handler", function() + local subscribe_request = initialize_subscribe_request(mock_device_modular, { + [clusters.Thermostat.ID] = { + clusters.Thermostat.attributes.LocalTemperature, + clusters.Thermostat.attributes.SystemMode, + clusters.Thermostat.attributes.ControlSequenceOfOperation, + }, + [clusters.TemperatureMeasurement.ID] = { + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue, + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + }, + [clusters.FanControl.ID] = { + clusters.FanControl.attributes.FanMode, + clusters.FanControl.attributes.FanModeSequence, + }, + }) + local expected_metadata_modular = { + optional_component_capabilities={{"main", {"fanMode"}}}, + profile="thermostat-modular", + } + local updated_device_profile = t_utils.get_profile_definition("thermostat-modular.yml", + {enabled_optional_capabilities = expected_metadata_modular.optional_component_capabilities} + ) + updated_device_profile.id = "00000000-1111-2222-3333-000000000003" + test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({ profile = updated_device_profile })) + test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) + end, + { test_init = test_init_modular_fingerprint } +) + +test.register_coroutine_test( + "No component-capability update and no profile ID update should not cause a re-subscribe in infoChanged handler", function() + -- simulate no actual change + test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({})) + end, + { test_init = function() test.mock_device.add_test_device(mock_device_modular) end } +) + -- run tests test.run_registered_tests() diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua index 7773c9ba02..dd298bc987 100644 --- a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua @@ -72,6 +72,50 @@ function ThermostatUtils.get_total_cumulative_energy_imported(device) return total_energy end +--- Deeply compare two values. +--- Handles metatables. Can optionally ignore cycle checking and/or function differences. +--- +--- @param a any +--- @param b any +--- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } +--- @param seen table|nil +--- @return boolean +function ThermostatUtils.deep_equals(a, b, opts, seen) + if a == b then return true end -- same object + if type(a) ~= type(b) then return false end -- different type + if type(a) == "function" and opts and opts.ignore_functions then return true end + if type(a) ~= "table" then return false end -- same type but not table, thus was already compared + + -- check for cycles in table references and preserve reference topology. + if not (opts and opts.ignore_cycles) then + seen = seen or {} + seen[a] = seen[a] or {} + if seen[a][b] then + return seen[a][b] + end + seen[a][b] = true + end + + -- Compare keys/values from a + for k, v in next, a do + if not ThermostatUtils.deep_equals(v, rawget(b, k), opts, seen) then + return false + end + end + + -- Ensure b doesn't have extra keys + for k in next, b do + if rawget(a, k) == nil then + return false + end + end + + -- Compare metatables + local mt_a = getmetatable(a) + local mt_b = getmetatable(b) + return ThermostatUtils.deep_equals(mt_a, mt_b, opts, seen) +end + function ThermostatUtils.get_endpoints_by_device_type(device, device_type) local endpoints = {} for _, ep in ipairs(device.endpoints) do From 39d2c19d6a6e966b2bc1288828aa57cc09a9d9fe Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 25 Mar 2026 11:18:28 +0900 Subject: [PATCH 198/538] Support keypair generation (#2815) Signed-off-by: Hunsup Jung --- .../matter-lock/src/lock_utils.lua | 176 ++++- .../matter-lock/src/new-matter-lock/init.lua | 238 +++--- .../src/test/test_aqara_matter_lock.lua | 3 + .../src/test/test_matter_lock_modular.lua | 77 +- .../src/test/test_matter_lock_unlatch.lua | 3 + .../src/test/test_new_matter_lock.lua | 3 + .../src/test/test_new_matter_lock_aliro.lua | 682 ++++++++++++++++++ .../src/test/test_new_matter_lock_battery.lua | 11 +- 8 files changed, 1026 insertions(+), 167 deletions(-) create mode 100644 drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua diff --git a/drivers/SmartThings/matter-lock/src/lock_utils.lua b/drivers/SmartThings/matter-lock/src/lock_utils.lua index 94e95c196f..fcea79d7f5 100644 --- a/drivers/SmartThings/matter-lock/src/lock_utils.lua +++ b/drivers/SmartThings/matter-lock/src/lock_utils.lua @@ -1,6 +1,9 @@ -- Copyright 2022 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local security = require "st.security" +local PUB_KEY_PREFIX = "04" + local lock_utils = { -- Lock device field names LOCK_CODES = "lockCodes", @@ -39,7 +42,11 @@ local lock_utils = { ENDPOINT_KEY_INDEX = "endpointKeyIndex", ENDPOINT_KEY_TYPE = "endpointKeyType", DEVICE_KEY_ID = "deviceKeyId", - COMMAND_REQUEST_ID = "commandRequestId" + COMMAND_REQUEST_ID = "commandRequestId", + MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED", + ALIRO_READER_CONFIG_UPDATED = "aliroReaderConfigUpdated", + LATEST_DOOR_LOCK_FEATURE_MAP = "latestDoorLockFeatureMap", + IS_MODULAR_PROFILE = "isModularProfile" } local capabilities = require "st.capabilities" local json = require "st.json" @@ -102,4 +109,171 @@ end -- keys are the code slots that ST uses -- user_index and credential_index are used in the matter commands -- +function lock_utils.get_field_for_endpoint(device, field, endpoint) + return device:get_field(string.format("%s_%d", field, endpoint)) +end + +function lock_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 lock_utils.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 ~= "lock" and capability.id ~= "lockAlarm" and capability.id ~= "remoteControlStatus" and + 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 + +-- This function check busy_state and if busy_state is false, set it to true(current time) +function lock_utils.is_busy_state_set(device) + local c_time = os.time() + local busy_state = device:get_field(lock_utils.BUSY_STATE) or false + if busy_state == false or c_time - busy_state > 10 then + device:set_field(lock_utils.BUSY_STATE, c_time, {persist = true}) + return false + else + return true + end +end + +function lock_utils.hex_string_to_octet_string(hex_string) + if hex_string == nil then + return nil + end + local octet_string = "" + for i = 1, #hex_string, 2 do + local hex = hex_string:sub(i, i + 1) + octet_string = octet_string .. string.char(tonumber(hex, 16)) + end + return octet_string +end + +function lock_utils.create_group_id_resolving_key() + math.randomseed(os.time()) + local result = string.format("%02x", math.random(0, 255)) + for i = 1, 15 do + result = result .. string.format("%02x", math.random(0, 255)) + end + return result +end + +function lock_utils.generate_keypair(device) + local request_opts = { + key_algorithm = { + type = "ec", + curve = "prime256v1" + }, + signature_algorithm = "sha256", + return_formats = { + pem = true, + der = true + }, + subject = { + common_name = "reader config" + }, + validity_days = 36500, + x509_extensions = { + key_usage = { + critical = true, + digital_signature = true + }, + certificate_policies = { + critical = true, + policy_2030_5_self_signed_client = true + } + } + } + local status = security.generate_self_signed_cert(request_opts) + if not status or not status.key_der then + device.log.error("generate_self_signed_cert returned no data") + return nil, nil + end + + local der = status.key_der + local privKey, pubKey = nil, nil + -- Helper: Parse ASN.1 length (handles 1-byte and multi-byte lengths) + local function get_length(data, start_pos) + local b = string.byte(data, start_pos) + if not b then return nil, start_pos end + + if b < 0x80 then + return b, start_pos + 1 + else + local num_bytes = b - 0x80 + local len = 0 + for i = 1, num_bytes do + len = (len * 256) + string.byte(data, start_pos + i) + end + return len, start_pos + 1 + num_bytes + end + end + -- Start parsing after the initial SEQUENCE tag (0x30) + -- Most keys start: [0x30][Length]. We find the first length to find the start of content. + local _, pos = get_length(der, 2) + + while pos < #der do + local tag = string.byte(der, pos) + local len, content_start = get_length(der, pos + 1) + if not len then break end + if tag == 0x04 then + -- PRIVATE KEY: Octet String + privKey = utils.bytes_to_hex_string(string.sub(der, content_start, content_start + len - 1)) + elseif tag == 0xA1 then + -- PUBLIC KEY Wrapper: Explicit Tag [1] + -- Inside 0xA1 is a BIT STRING (0x03) + local inner_tag = string.byte(der, content_start) + if inner_tag == 0x03 then + local bit_len, bit_start = get_length(der, content_start + 1) + -- BIT STRINGS have a "leading null byte" (unused bits indicator) + -- We skip that byte (bit_start) and the 0x04 EC prefix to get the raw X/Y coordinates + local actual_key_start = bit_start + 2 + local actual_key_len = bit_len - 2 + pubKey = PUB_KEY_PREFIX .. utils.bytes_to_hex_string(string.sub(der, actual_key_start, actual_key_start + actual_key_len - 1)) + end + end + -- Move pointer to the next tag + pos = content_start + len + end + + if not privKey or not pubKey then + device.log.error("Failed to extract keys from DER") + end + return privKey, pubKey +end + return lock_utils 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 fdae5ec432..cca7a56b27 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -1,7 +1,6 @@ -- Copyright 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 - local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local im = require "st.matter.interaction_model" @@ -49,7 +48,6 @@ local ALIRO_KEY_TYPE_TO_CRED_ENUM_MAP = { ["nonEvictableEndpointKey"] = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY } - local battery_support = { NO_BATTERY = "NO_BATTERY", BATTERY_LEVEL = "BATTERY_LEVEL", @@ -60,6 +58,7 @@ local profiling_data = { BATTERY_SUPPORT = "__BATTERY_SUPPORT", } +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local subscribed_attributes = { [capabilities.lock.ID] = { DoorLock.attributes.LockState @@ -111,7 +110,6 @@ local subscribed_events = { } } - local function find_default_endpoint(device, cluster) local res = device.MATTER_DEFAULT_ENDPOINT local eps = device:get_endpoints(cluster) @@ -143,6 +141,7 @@ local function device_init(driver, device) end end end + device:add_subscribed_attribute(DoorLockFeatureMapAttr) for cap_id, events in pairs(subscribed_events) do if device:supports_capability_by_id(cap_id) then for _, e in ipairs(events) do @@ -157,6 +156,61 @@ local function device_added(driver, device) device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) end +local function set_reader_config(device) + local reader_config_updated = device:get_field(lock_utils.ALIRO_READER_CONFIG_UPDATED) or nil + if reader_config_updated == "TRUE" or reader_config_updated == "IN_PROGRESS" then return end + + local cmdName = "setReaderConfig" + local groupId = lock_utils.create_group_id_resolving_key() + local groupResolvingKey = nil + local aliro_ble_uwb_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.ALIROBLEUWB}) + if #aliro_ble_uwb_eps > 0 then + groupResolvingKey = lock_utils.create_group_id_resolving_key() + end + local privKey, pubKey = lock_utils.generate_keypair(device) + if not privKey or not pubKey then + local command_result_info = { + commandName = cmdName, + statusCode = "failure" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.VERIFICATION_KEY, pubKey, {persist = true}) + device:set_field(lock_utils.GROUP_ID, groupId, {persist = true}) + device:set_field(lock_utils.GROUP_RESOLVING_KEY, groupResolvingKey, {persist = true}) + + -- Send command + local ep = find_default_endpoint(device, clusters.DoorLock.ID) + device:send( + DoorLock.server.commands.SetAliroReaderConfig( + device, ep, + lock_utils.hex_string_to_octet_string(privKey), + lock_utils.hex_string_to_octet_string(pubKey), + lock_utils.hex_string_to_octet_string(groupId), + lock_utils.hex_string_to_octet_string(groupResolvingKey) + ) + ) + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "IN_PROGRESS", {persist = true}) +end + local function match_profile_modular(driver, device) local enabled_optional_component_capability_pairs = {} local main_component_capabilities = {} @@ -165,7 +219,11 @@ local function match_profile_modular(driver, device) for _, ep_cluster in pairs(device_ep.clusters) do if ep_cluster.cluster_id == DoorLock.ID then local clus_has_feature = function(feature_bitmap) - return DoorLock.are_features_supported(feature_bitmap, ep_cluster.feature_map) + return DoorLock.are_features_supported( + feature_bitmap, + lock_utils.get_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, device_ep.endpoint_id) or + ep_cluster.feature_map + ) end if clus_has_feature(DoorLock.types.Feature.USER) then table.insert(main_component_capabilities, capabilities.lockUsers.ID) @@ -201,7 +259,9 @@ local function match_profile_modular(driver, device) end table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) - device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) + if lock_utils.optional_capabilities_list_changed(enabled_optional_component_capability_pairs, device.profile.components) then + device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) + end end local function match_profile_switch(driver, device) @@ -302,6 +362,9 @@ local function info_changed(driver, device, event, args) end end device:subscribe() + if device:supports_capability_by_id(capabilities.lockAliro.ID) then + set_reader_config(device) + end if device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory @@ -317,18 +380,17 @@ local function profiling_data_still_required(device) return false end -local function match_profile(driver, device) +local function match_profile(driver, device, ignore_static_profiling) if profiling_data_still_required(device) then return end - if version.api >= 15 and version.rpc >= 9 then match_profile_modular(driver, device) - else + elseif ignore_static_profiling ~= true then match_profile_switch(driver, device) end end local function do_configure(driver, device) - match_profile(driver, device) + match_profile(driver, device, false) device.thread:call_with_delay(5, function() device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory @@ -336,19 +398,7 @@ local function do_configure(driver, device) end local function driver_switched(driver, device) - match_profile(driver, device) -end - --- This function check busy_state and if busy_state is false, set it to true(current time) -local function is_busy_state_set(device) - local c_time = os.time() - local busy_state = device:get_field(lock_utils.BUSY_STATE) or false - if busy_state == false or c_time - busy_state > 10 then - device:set_field(lock_utils.BUSY_STATE, c_time, {persist = true}) - return false - else - return true - end + match_profile(driver, device, false) end -- Matter Handler @@ -449,7 +499,7 @@ local function set_cota_credential(device, credential_index) end -- Check Busy State - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then device.log.debug("delaying setting COTA credential since a credential is currently being set") device.thread:call_with_delay(2, function(t) set_cota_credential(device, credential_index) @@ -524,21 +574,6 @@ local function max_year_schedule_of_user_handler(driver, device, ib, response) device:emit_event(capabilities.lockSchedules.yearDaySchedulesPerUser(ib.data.value, {visibility = {displayed = false}})) end ----------------- --- Aliro Util -- ----------------- -local function hex_string_to_octet_string(hex_string) - if hex_string == nil then - return nil - end - local octet_string = "" - for i = 1, #hex_string, 2 do - local hex = hex_string:sub(i, i + 1) - octet_string = octet_string .. string.char(tonumber(hex, 16)) - end - return octet_string -end - ----------------------------------- -- Aliro Reader Verification Key -- ----------------------------------- @@ -547,6 +582,7 @@ local function aliro_reader_verification_key_handler(driver, device, ib, respons device:emit_event(capabilities.lockAliro.readerVerificationKey( utils.bytes_to_hex_string(ib.data.value), {visibility = {displayed = false}} )) + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "TRUE", {persist = true}) end end @@ -631,6 +667,18 @@ local function max_aliro_endpoint_key_handler(driver, device, ib, response) end end +------------------------------ +-- Feature Map of Door Lock -- +------------------------------ +local function door_lock_feature_map_handler(driver, device, ib, response) + if ib.data.value == nil then return end + local feature_map = lock_utils.get_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id) or nil + if feature_map ~= ib.data.value then + lock_utils.set_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id, ib.data.value, { persist = true }) + match_profile(driver, device, true) + end +end + --------------------------------- -- Power Source Attribute List -- --------------------------------- @@ -649,7 +697,7 @@ local function handle_power_source_attribute_list(driver, device, ib, response) device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist=true}) end if latest_battery_support == nil or latest_battery_support ~= device:get_field(profiling_data.BATTERY_SUPPORT) then - match_profile(driver, device) + match_profile(driver, device, false) end end @@ -1236,7 +1284,7 @@ local function handle_add_user(driver, device, command) local userType = command.args.userType -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -1274,7 +1322,7 @@ local function handle_update_user(driver, device, command) end -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -1433,7 +1481,7 @@ local function handle_delete_user(driver, device, command) local userIdx = command.args.userIndex -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -1461,7 +1509,7 @@ local function handle_delete_all_users(driver, device, command) local cmdName = "deleteAllUsers" -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -1542,7 +1590,7 @@ local function handle_add_credential(driver, device, command) local credData = command.args.credentialData -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -1590,7 +1638,7 @@ local function handle_update_credential(driver, device, command) local credData = command.args.credentialData -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -1835,7 +1883,7 @@ local function set_issuer_key_response_handler(driver, device, ib, response) device, ep, DoorLock.types.DataOperationTypeEnum.ADD, credential, -- Credential - hex_string_to_octet_string(credData), -- Credential Data + lock_utils.hex_string_to_octet_string(credData), -- Credential Data userIdx, -- User Index nil, -- User Status userType -- User Type @@ -1934,7 +1982,7 @@ local function set_endpoint_key_response_handler(driver, device, ib, response) device, ep, DoorLock.types.DataOperationTypeEnum.ADD, credential, -- Credential - hex_string_to_octet_string(credData), -- Credential Data + lock_utils.hex_string_to_octet_string(credData), -- Credential Data userIdx, -- User Index nil, -- User Status userType -- User Type @@ -1983,7 +2031,7 @@ local function handle_delete_credential(driver, device, command) } -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2015,7 +2063,7 @@ local function handle_delete_all_credentials(driver, device, command) } -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2128,7 +2176,7 @@ local function handle_set_week_day_schedule(driver, device, command) local endMinute = schedule.endMinute -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2222,7 +2270,7 @@ local function handle_clear_week_day_schedule(driver, device, command) local userIdx = command.args.userIndex -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2318,7 +2366,7 @@ local function handle_set_year_day_schedule(driver, device, command) local localEndTime = command.args.schedule.localEndTime -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2417,7 +2465,7 @@ local function handle_clear_year_day_schedule(driver, device, command) local userIdx = command.args.userIndex -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2545,7 +2593,7 @@ local function handle_set_reader_config(driver, device, command) end -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, statusCode = "busy" @@ -2556,6 +2604,22 @@ local function handle_set_reader_config(driver, device, command) return end + local reader_config_updated = device:get_field(lock_utils.ALIRO_READER_CONFIG_UPDATED) or nil + if reader_config_updated == "IN_PROGRESS" then + return + elseif reader_config_updated == "TRUE" then + -- Update commandResult + local command_result_info = { + commandName = cmdName, + statusCode = "success" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + return + end + -- Save values to field device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) device:set_field(lock_utils.VERIFICATION_KEY, verificationKey, {persist = true}) @@ -2567,54 +2631,27 @@ local function handle_set_reader_config(driver, device, command) device:send( DoorLock.server.commands.SetAliroReaderConfig( device, ep, - hex_string_to_octet_string(signingKey), - hex_string_to_octet_string(verificationKey), - hex_string_to_octet_string(groupId), -- Group identification - hex_string_to_octet_string(groupResolvingKey) -- Group resolving key + lock_utils.hex_string_to_octet_string(signingKey), + lock_utils.hex_string_to_octet_string(verificationKey), + lock_utils.hex_string_to_octet_string(groupId), + lock_utils.hex_string_to_octet_string(groupResolvingKey) ) ) + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "IN_PROGRESS", {persist = true}) end local function set_aliro_reader_config_handler(driver, device, ib, response) -- Get result local cmdName = device:get_field(lock_utils.COMMAND_NAME) - local verificationKey = device:get_field(lock_utils.VERIFICATION_KEY) - local groupId = device:get_field(lock_utils.GROUP_ID) - local groupResolvingKey = device:get_field(lock_utils.GROUP_RESOLVING_KEY) - - local status = "success" - if ib.status == DoorLock.types.DlStatus.FAILURE then - status = "failure" - elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then - status = "invalidCommand" - elseif ib.status == DoorLock.types.DlStatus.SUCCESS then - if verificationKey ~= nil then - device:emit_event(capabilities.lockAliro.readerVerificationKey( - verificationKey, - { - state_change = true, - visibility = {displayed = false} - } - )) - end - if groupId ~= nil then - device:emit_event(capabilities.lockAliro.readerGroupIdentifier( - groupId, - { - state_change = true, - visibility = {displayed = false} - } - )) - end - if groupResolvingKey ~= nil then - device:emit_event(capabilities.lockAliro.groupResolvingKey( - groupResolvingKey, - { - state_change = true, - visibility = {displayed = false} - } - )) + local status = "failure" + if ib.status == DoorLock.types.DlStatus.SUCCESS then + status = "success" + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "TRUE", {persist = true}) + else + if ib.status == DoorLock.types.DlStatus.INVALID_FIELD then + status = "invalidCommand" end + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, nil, {persist = true}) end -- Update commandResult @@ -2654,7 +2691,7 @@ local function handle_set_issuer_key(driver, device, command) end -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, userIndex = userIdx, @@ -2681,7 +2718,7 @@ local function handle_set_issuer_key(driver, device, command) device, ep, DoorLock.types.DataOperationTypeEnum.ADD, -- Data Operation Type: Add(0), Modify(2) credential, -- Credential - hex_string_to_octet_string(issuerKey), -- Credential Data + lock_utils.hex_string_to_octet_string(issuerKey), -- Credential Data userIdx, -- User Index nil, -- User Status userType -- User Type @@ -2696,7 +2733,7 @@ local function handle_clear_issuer_key(driver, device, command) local reqId = command.args.requestId -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, userIndex = userIdx, @@ -2757,7 +2794,7 @@ local function handle_set_endpoint_key(driver, device, command) end -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, userIndex = userIdx, @@ -2815,7 +2852,7 @@ local function handle_set_endpoint_key(driver, device, command) device, ep, dataOpType, -- Data Operation Type: Add(0), Modify(2) credential, -- Credential - hex_string_to_octet_string(endpointKey), -- Credential Data + lock_utils.hex_string_to_octet_string(endpointKey), -- Credential Data userIdx, -- User Index nil, -- User Status userType -- User Type @@ -2832,7 +2869,7 @@ local function handle_clear_endpoint_key(driver, device, command) local reqId = command.args.requestId -- Check busy state - if is_busy_state_set(device) then + if lock_utils.is_busy_state_set(device) then local command_result_info = { commandName = cmdName, userIndex = userIdx, @@ -2910,6 +2947,7 @@ local new_matter_lock_handler = { [DoorLock.attributes.AliroBLEAdvertisingVersion.ID] = aliro_ble_advertising_version_handler, [DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported.ID] = max_aliro_credential_issuer_key_handler, [DoorLock.attributes.NumberOfAliroEndpointKeysSupported.ID] = max_aliro_endpoint_key_handler, + [DoorLockFeatureMapAttr.ID] = door_lock_feature_map_handler, }, [PowerSource.ID] = { [PowerSource.attributes.AttributeList.ID] = handle_power_source_attribute_list, diff --git a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua index c5da1b600e..dec8911484 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua @@ -7,6 +7,7 @@ test.set_rpc_version(0) local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("lock-user-pin.yml"), @@ -42,6 +43,7 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = clusters.DoorLock.ID} local function test_init() test.disable_startup_messages() -- subscribe request @@ -52,6 +54,7 @@ local function test_init() subscribe_request:merge(clusters.DoorLock.attributes.MaxPINCodeLength:subscribe(mock_device)) subscribe_request:merge(clusters.DoorLock.attributes.MinPINCodeLength:subscribe(mock_device)) subscribe_request:merge(clusters.DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(clusters.DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(clusters.DoorLock.events.DoorLockAlarm:subscribe(mock_device)) subscribe_request:merge(clusters.DoorLock.events.LockUserChange:subscribe(mock_device)) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index 468863938d..bb217dcdae 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -1,13 +1,12 @@ -- 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 cluster_base = require "st.matter.cluster_base" local DoorLock = clusters.DoorLock local mock_device = test.mock_device.build_test_matter_device({ @@ -200,12 +199,13 @@ local mock_device_modular = test.mock_device.build_test_matter_device({ } }) - +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local function test_init() test.disable_startup_messages() -- subscribe request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device)) @@ -228,9 +228,11 @@ local function test_init_unlatch() -- subscribe request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_unlatch) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_unlatch)) + subscribe_request:merge(cluster_base.subscribe(mock_device_unlatch, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_unlatch)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_unlatch)) + -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_unlatch) -- actual onboarding flow @@ -254,10 +256,12 @@ local function test_init_user_pin() subscribe_request:merge(DoorLock.attributes.MaxPINCodeLength:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.attributes.MinPINCodeLength:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device_user_pin)) + subscribe_request:merge(cluster_base.subscribe(mock_device_user_pin, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin)) + -- add test device test.mock_device.add_test_device(mock_device_user_pin) -- actual onboarding flow @@ -283,10 +287,12 @@ local function test_init_user_pin_schedule_unlatch() subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device_user_pin_schedule_unlatch)) + subscribe_request:merge(cluster_base.subscribe(mock_device_user_pin_schedule_unlatch, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin_schedule_unlatch)) + -- add test device test.mock_device.add_test_device(mock_device_user_pin_schedule_unlatch) -- actual onboarding flow @@ -305,9 +311,11 @@ local function test_init_modular() -- subscribe request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_modular) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_modular)) + subscribe_request:merge(cluster_base.subscribe(mock_device_modular, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_modular)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_modular)) + -- add test device test.mock_device.add_test_device(mock_device_modular) -- actual onboarding flow @@ -323,36 +331,6 @@ end test.set_test_init_function(test_init) -test.register_coroutine_test( - "Test lock profile change when attributes related to BAT feature is not available.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, - { - uint32(0), - uint32(1), - uint32(2), - uint32(31), - uint32(65528), - uint32(65529), - uint32(65531), - uint32(65532), - uint32(65533), - }) - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) - ) - mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {}}} }) - end -) - test.register_coroutine_test( "Test modular lock profile change when BatChargeLevel attribute is available", function() @@ -416,37 +394,6 @@ test.register_coroutine_test( end ) -test.register_coroutine_test( - "Test modular lock profile change with unlatch when attributes related to BAT feature is not available.", - function() - test.socket.matter:__queue_receive( - { - mock_device_unlatch.id, - clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device_unlatch, 1, - { - uint32(0), - uint32(1), - uint32(2), - uint32(31), - uint32(65528), - uint32(65529), - uint32(65531), - uint32(65532), - uint32(65533), - }) - } - ) - test.socket.capability:__expect_send( - mock_device_unlatch:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "unlatched", "not fully locked"}, {visibility = {displayed = false}})) - ) - test.socket.capability:__expect_send( - mock_device_unlatch:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) - ) - mock_device_unlatch:expect_metadata_update({ profile = "lock-modular-embedded-unlatch", optional_component_capabilities = {{"main", {}}} }) - end, - { test_init = test_init_unlatch } -) - test.register_coroutine_test( "Test lock-unlatch profile change with unlatch when BatChargeLevel attribute is available", function() @@ -637,11 +584,13 @@ test.register_coroutine_test( subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device_modular)) + subscribe_request:merge(cluster_base.subscribe(mock_device_modular, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_modular)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_modular)) subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device_modular)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_modular)) + test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) test.socket.capability:__expect_send( mock_device_modular:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua index 642ca3bf7a..0a8c9acb09 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua @@ -7,6 +7,7 @@ test.set_rpc_version(0) local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" local DoorLock = clusters.DoorLock local types = DoorLock.types @@ -43,11 +44,13 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local function test_init() test.disable_startup_messages() -- subscribe_request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) -- add test device, handle initial subscribe diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 43d6db2084..dbdb4f246c 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -7,6 +7,7 @@ test.set_rpc_version(0) local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" local DoorLock = clusters.DoorLock local types = DoorLock.types local lock_utils = require "lock_utils" @@ -44,6 +45,7 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local function test_init() test.disable_startup_messages() -- subscribe request @@ -56,6 +58,7 @@ local function test_init() subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device)) subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua new file mode 100644 index 0000000000..04d051b93e --- /dev/null +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua @@ -0,0 +1,682 @@ +-- Copyright 2023 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" +local DoorLock = clusters.DoorLock +local OctetString1 = require "st.matter.data_types.OctetString1" + +local enabled_optional_component_capability_pairs = {{ + "main", + { + capabilities.lockUsers.ID, + capabilities.lockSchedules.ID, + capabilities.lockAliro.ID + } +}} +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition( + "lock-modular.yml", + {enabled_optional_capabilities = enabled_optional_component_capability_pairs} + ), + manufacturer_info = { + vendor_id = 0x135D, + product_id = 0x00C1, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x2510, -- WDSCH & YDSCH & USR & ALIRO + } + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} +local function test_init() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) + subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroReaderVerificationKey:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroReaderGroupIdentifier:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroReaderGroupSubIdentifier:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroGroupResolvingKey:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroBLEAdvertisingVersion:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfAliroEndpointKeysSupported:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) + subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) + subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) + subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) + test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) + ) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle received AliroReaderVerificationKey from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroReaderVerificationKey:build_test_report_data( + mock_device, 1, + "\x04\xA9\xCB\xE4\x18\xEB\x09\x66\x16\x43\xE2\xA4\xA8\x46\xB8\xED\xFE\x27\x86\x98\x30\x2E\x9F\xB4\x3E\x9B\xFF\xD3\xE3\x10\xCC\x2C\x2C\x7F\xF4\x02\xE0\x6E\x40\xEA\x3C\xE1\x29\x43\x52\x73\x36\x68\x3F\xC5\xB1\xCB\x0C\x6A\x7C\x3F\x0B\x5A\xFF\x78\x35\xDF\x21\xC6\x24" + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.readerVerificationKey( + "04a9cbe418eb09661643e2a4a846b8edfe278698302e9fb43e9bffd3e310cc2c2c7ff402e06e40ea3ce12943527336683fc5b1cb0c6a7c3f0b5aff7835df21c624", + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroReaderGroupIdentifier from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroReaderGroupIdentifier:build_test_report_data( + mock_device, 1, + "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8" + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.readerGroupIdentifier( + "e24f1b205ba923b32cd13dc009e993a8", + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroExpeditedTransactionSupportedProtocolVersions from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions:build_test_report_data( + mock_device, 1, + {OctetString1("\x00\x09"), OctetString1("\x01\x00")} + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.expeditedTransactionProtocolVersions( + {"0.9", "1.0"}, + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroSupportedBLEUWBProtocolVersions from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions:build_test_report_data( + mock_device, 1, + {OctetString1("\x00\x09"), OctetString1("\x01\x00")} + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.bleUWBProtocolVersions( + {"0.9", "1.0"}, + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroReaderVerificationKey from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported:build_test_report_data( + mock_device, 1, + 35 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.maxCredentialIssuerKeys( + 35, + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroGroupResolvingKey from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroGroupResolvingKey:build_test_report_data( + mock_device, 1, + "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8" + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.groupResolvingKey( + "e24f1b205ba923b32cd13dc009e993a8", + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroBLEAdvertisingVersion from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroBLEAdvertisingVersion:build_test_report_data( + mock_device, 1, + 1 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.bleAdvertisingVersion( + "1", + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received NumberOfAliroEndpointKeysSupported from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.NumberOfAliroEndpointKeysSupported:build_test_report_data( + mock_device, 1, + 10 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.maxEndpointKeys( + 10, + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle Set Card Id command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setCardId", + args = {"3icub18c8pr00"} + }, + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.cardId("3icub18c8pr00", {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle Set Reader Config command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setReaderConfig", + args = { + "1a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac", + "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", + "e24f1b205ba923b32cd13dc009e993a8", + nil + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetAliroReaderConfig( + mock_device, 1, -- endpoint + "\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC", + "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", + "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8", + nil + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.SetAliroReaderConfig:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS -- status + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + {commandName="setReaderConfig", statusCode="success"}, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end +) + +test.register_coroutine_test( + "Handle Set Endpoint Key command and Clear Endpoint Key command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setEndpointKey", + args = { + 0, + "vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + "nonEvictableEndpointKey", + "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", + "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2" + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY, + credential_index = 1 + } + ), -- credential + "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users( + {{userIndex=1, userType="adminMember"}}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.credentials( + {{ + keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + keyIndex=1, + keyType="nonEvictableEndpointKey", + userIndex=1 + }}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setEndpointKey", + keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", + statusCode="success", + userIndex=1 + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "clearEndpointKey", + args = {1, "vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", "nonEvictableEndpointKey"} + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.ClearCredential( + mock_device, 1, -- endpoint + DoorLock.types.CredentialStruct( + {credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY, credential_index = 1} + ) + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.ClearCredential:build_test_command_response( + mock_device, 1 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.credentials({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.weekDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.yearDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="clearEndpointKey", + keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + statusCode="success", + userIndex=1 + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end +) + +test.register_coroutine_test( + "Handle Set Issuer Key command and Clear Issuer Key command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setIssuerKey", + args = { + 0, + "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", + "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2" + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, + credential_index = 1 + } + ), -- credential + "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users( + {{userIndex=1, userType="adminMember"}}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.credentials( + {{ + keyIndex=1, + keyType="issuerKey", + userIndex=1 + }}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setIssuerKey", + requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", + statusCode="success", + userIndex=1 + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "clearIssuerKey", + args = {1, "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2"} + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.ClearCredential( + mock_device, 1, -- endpoint + DoorLock.types.CredentialStruct( + {credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, credential_index = 1} + ) + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.ClearCredential:build_test_command_response( + mock_device, 1 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.credentials({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.weekDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.yearDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="clearIssuerKey", + requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", + statusCode="success", + userIndex=1 + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua index 8ce6e61be5..64e93165c3 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua @@ -1,14 +1,13 @@ -- Copyright 2023 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" local clusters = require "st.matter.clusters" local t_utils = require "integration_test.utils" local uint32 = require "st.matter.data_types.Uint32" - +local cluster_base = require "st.matter.cluster_base" local DoorLock = clusters.DoorLock local mock_device = test.mock_device.build_test_matter_device({ @@ -163,14 +162,17 @@ local mock_device_user_pin_schedule_unlatch = test.mock_device.build_test_matter } }) +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local function test_init() test.disable_startup_messages() -- subscribe request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device)) + -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) @@ -190,9 +192,11 @@ local function test_init_unlatch() -- subscribe request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_unlatch) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_unlatch)) + subscribe_request:merge(cluster_base.subscribe(mock_device_unlatch, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_unlatch)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_unlatch)) + -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_unlatch) test.socket.matter:__expect_send({mock_device_unlatch.id, subscribe_request}) @@ -217,10 +221,12 @@ local function test_init_user_pin() subscribe_request:merge(DoorLock.attributes.MaxPINCodeLength:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.attributes.MinPINCodeLength:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device_user_pin)) + subscribe_request:merge(cluster_base.subscribe(mock_device_user_pin, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin)) subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_user_pin)) + -- add test device, handle initial subscribe test.mock_device.add_test_device(mock_device_user_pin) test.socket.matter:__expect_send({mock_device_user_pin.id, subscribe_request}) @@ -247,6 +253,7 @@ local function test_init_user_pin_schedule_unlatch() subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device_user_pin_schedule_unlatch)) + subscribe_request:merge(cluster_base.subscribe(mock_device_user_pin_schedule_unlatch, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_user_pin_schedule_unlatch)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_user_pin_schedule_unlatch)) From ed80cbaf0d332b84e53d695e18c7ecf1269d5b85 Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 25 Mar 2026 23:49:02 +0900 Subject: [PATCH 199/538] Support DPS feature (#2750) Signed-off-by: Hunsup Jung Co-authored-by: Harrison Carter --- .../lock-modular-embedded-unlatch.yml | 3 + .../matter-lock/profiles/lock-modular.yml | 3 + .../matter-lock/src/lock_utils.lua | 3 +- .../matter-lock/src/new-matter-lock/init.lua | 64 +++- .../matter-lock/src/test/test_door_state.lua | 278 ++++++++++++++++++ 5 files changed, 348 insertions(+), 3 deletions(-) create mode 100644 drivers/SmartThings/matter-lock/src/test/test_door_state.lua diff --git a/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml b/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml index 3d9e68b44c..253374212b 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml @@ -6,6 +6,9 @@ components: version: 1 - id: lockAlarm version: 1 + - id: doorState + version: 1 + optional: true - id: remoteControlStatus version: 1 - id: lockUsers diff --git a/drivers/SmartThings/matter-lock/profiles/lock-modular.yml b/drivers/SmartThings/matter-lock/profiles/lock-modular.yml index 3a8a53bf70..7a664767dd 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-modular.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-modular.yml @@ -4,6 +4,9 @@ components: capabilities: - id: lock version: 1 + - id: doorState + version: 1 + optional: true - id: lockAlarm version: 1 - id: remoteControlStatus diff --git a/drivers/SmartThings/matter-lock/src/lock_utils.lua b/drivers/SmartThings/matter-lock/src/lock_utils.lua index fcea79d7f5..94a0e747c4 100644 --- a/drivers/SmartThings/matter-lock/src/lock_utils.lua +++ b/drivers/SmartThings/matter-lock/src/lock_utils.lua @@ -45,8 +45,7 @@ local lock_utils = { COMMAND_REQUEST_ID = "commandRequestId", MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED", ALIRO_READER_CONFIG_UPDATED = "aliroReaderConfigUpdated", - LATEST_DOOR_LOCK_FEATURE_MAP = "latestDoorLockFeatureMap", - IS_MODULAR_PROFILE = "isModularProfile" + LATEST_DOOR_LOCK_FEATURE_MAP = "latestDoorLockFeatureMap" } local capabilities = require "st.capabilities" local json = require "st.json" 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 cca7a56b27..5720856242 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -56,6 +56,7 @@ local battery_support = { local profiling_data = { BATTERY_SUPPORT = "__BATTERY_SUPPORT", + ENABLE_DOOR_STATE = "__ENABLE_DOOR_STATE" } local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} @@ -63,6 +64,9 @@ local subscribed_attributes = { [capabilities.lock.ID] = { DoorLock.attributes.LockState }, + [capabilities.doorState.ID] = { + DoorLock.attributes.DoorState + }, [capabilities.remoteControlStatus.ID] = { DoorLock.attributes.OperatingMode }, @@ -129,6 +133,11 @@ end local function device_init(driver, device) device:set_component_to_endpoint_fn(component_to_endpoint) + if #device:get_endpoints(clusters.DoorLock.ID, {feature_bitmap = clusters.DoorLock.types.Feature.DOOR_POSITION_SENSOR}) == 0 then + device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true}) + else + device:add_subscribed_attribute(clusters.DoorLock.attributes.DoorState) + end if #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) == 0 then device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist = true}) elseif device:get_field(profiling_data.BATTERY_SUPPORT) == nil then @@ -258,6 +267,10 @@ local function match_profile_modular(driver, device) table.insert(main_component_capabilities, capabilities.battery.ID) end + if device:get_field(profiling_data.ENABLE_DOOR_STATE) then + table.insert(main_component_capabilities, capabilities.doorState.ID) + end + table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) if lock_utils.optional_capabilities_list_changed(enabled_optional_component_capability_pairs, device.profile.components) then device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) @@ -365,7 +378,10 @@ local function info_changed(driver, device, event, args) if device:supports_capability_by_id(capabilities.lockAliro.ID) then set_reader_config(device) end - if device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then + if device:supports_capability(capabilities.doorState) and device:get_latest_state("main", capabilities.doorState.ID, capabilities.doorState.supportedDoorStates.NAME) == nil then + device:emit_event(capabilities.doorState.supportedDoorStates({"open", "closed"}, {visibility = {displayed = false}})) -- open and closed are mandatory + end + if device:supports_capability(capabilities.lockAlarm) and device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory end @@ -427,6 +443,45 @@ local function lock_state_handler(driver, device, ib, response) end) end +local function door_state_handler(driver, device, ib, response) + if ib.data.value == nil then + -- early return on nil data. Also, if ENABLE_DOOR_STATE is unset, set it to false and attempt profile matching. + if device:get_field(profiling_data.ENABLE_DOOR_STATE) == nil then + device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true}) + match_profile(driver, device) + end + return + elseif device:supports_capability(capabilities.doorState) == false then + -- if a non-nil report comes in and the doorState capability is unsupported, set ENABLE_DOOR_STATE to true and attempt profile matching. + device:set_field(profiling_data.ENABLE_DOOR_STATE, true, {persist = true}) + match_profile(driver, device) + return + end + + local DoorStateEnum = DoorLock.types.DoorStateEnum + local doorState = capabilities.doorState.doorState + local DOOR_STATE_MAP = { + [DoorStateEnum.DOOR_OPEN] = doorState.open, + [DoorStateEnum.DOOR_CLOSED] = doorState.closed, + [DoorStateEnum.DOOR_JAMMED] = doorState.jammed, + [DoorStateEnum.DOOR_FORCED_OPEN] = doorState.forcedOpen, + [DoorStateEnum.DOOR_UNSPECIFIED_ERROR] = doorState.unspecifiedError, + [DoorStateEnum.DOOR_AJAR] = doorState.ajar + } + local door_state = DOOR_STATE_MAP[ib.data.value] or doorState.unspecifiedError -- fallback to unspecifiedError if we receive a value we don't recognize + device:emit_event(door_state()) + + -- add new door states to supportedDoorStates list if not already present. + local supported_door_states = utils.deep_copy(device:get_latest_state("main", capabilities.doorState.ID, capabilities.doorState.supportedDoorStates.NAME) or {}) + for _, state in pairs(supported_door_states) do + if state == door_state.NAME then + return + end + end + table.insert(supported_door_states, door_state.NAME) + device:emit_event(capabilities.doorState.supportedDoorStates(supported_door_states, {visibility = {displayed = false}})) +end + --------------------- -- Operating Modes -- --------------------- @@ -675,6 +730,12 @@ local function door_lock_feature_map_handler(driver, device, ib, response) local feature_map = lock_utils.get_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id) or nil if feature_map ~= ib.data.value then lock_utils.set_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id, ib.data.value, { persist = true }) + -- If the DPS feature is changed, check the DoorState value and call the match_profile. + if ib.data.value & clusters.DoorLock.types.Feature.DOOR_POSITION_SENSOR == 0 then + device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true}) + else + device:set_field(profiling_data.ENABLE_DOOR_STATE, true, {persist = true}) + end match_profile(driver, device, true) end end @@ -2931,6 +2992,7 @@ local new_matter_lock_handler = { attr = { [DoorLock.ID] = { [DoorLock.attributes.LockState.ID] = lock_state_handler, + [DoorLock.attributes.DoorState.ID] = door_state_handler, [DoorLock.attributes.OperatingMode.ID] = operating_modes_handler, [DoorLock.attributes.NumberOfTotalUsersSupported.ID] = total_users_supported_handler, [DoorLock.attributes.NumberOfPINUsersSupported.ID] = pin_users_supported_handler, diff --git a/drivers/SmartThings/matter-lock/src/test/test_door_state.lua b/drivers/SmartThings/matter-lock/src/test/test_door_state.lua new file mode 100644 index 0000000000..c105346b15 --- /dev/null +++ b/drivers/SmartThings/matter-lock/src/test/test_door_state.lua @@ -0,0 +1,278 @@ +-- 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 clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" +local DoorLock = clusters.DoorLock + +local mock_device_door_state_disabled = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("lock-modular.yml"), + manufacturer_info = { + vendor_id = 0x115f, + product_id = 0x2802, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x20, -- DPS + } + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + +local enabled_optional_component_capability_pairs = {{ "main", {capabilities.doorState.ID} }} +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition( + "lock-modular.yml", + {enabled_optional_capabilities = enabled_optional_component_capability_pairs} + ), + manufacturer_info = { + vendor_id = 0x115f, + product_id = 0x2802, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x20, -- DPS + } + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} +local function test_init() + test.disable_startup_messages() + local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) + subscribe_request:merge(DoorLock.attributes.DoorState:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device_door_state_disabled, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) + subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) + subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) + test.mock_device.add_test_device(mock_device) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + 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({ provisioning_state = "PROVISIONED" }) +end + +test.set_test_init_function(test_init) + +local function test_init_door_state_disabled() + test.disable_startup_messages() + local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_door_state_disabled) + subscribe_request:merge(DoorLock.attributes.DoorState:subscribe(mock_device_door_state_disabled)) + subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_door_state_disabled)) + subscribe_request:merge(cluster_base.subscribe(mock_device_door_state_disabled, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) + subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_door_state_disabled)) + subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_door_state_disabled)) + test.mock_device.add_test_device(mock_device_door_state_disabled) + test.socket.device_lifecycle:__queue_receive({ mock_device_door_state_disabled.id, "added" }) + test.socket.capability:__expect_send( + mock_device_door_state_disabled:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + test.socket.device_lifecycle:__queue_receive({ mock_device_door_state_disabled.id, "init" }) + test.socket.matter:__expect_send({mock_device_door_state_disabled.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_door_state_disabled.id, "doConfigure" }) + mock_device_door_state_disabled:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + +test.register_coroutine_test( + "Check that the device is updated with correct capabilities based on the profile and attributes.", + function () + test.socket.matter:__queue_receive({ + mock_device_door_state_disabled.id, + DoorLock.attributes.DoorState:build_test_report_data(mock_device_door_state_disabled, 1, DoorLock.attributes.DoorState.DOOR_CLOSED) + }) + test.socket.capability:__expect_send( + mock_device_door_state_disabled:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_device_door_state_disabled:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) + ) + + mock_device_door_state_disabled:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {"doorState"}}}}) + end, + { test_init = test_init_door_state_disabled } +) + + +test.register_coroutine_test( + "Handle received DoorState.DOOR_CLOSED from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_CLOSED + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.closed()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"closed"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_JAMMED from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_JAMMED + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.jammed()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"jammed"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_FORCED_OPEN from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_FORCED_OPEN + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.forcedOpen()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"forcedOpen"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_UNSPECIFIED_ERROR from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_UNSPECIFIED_ERROR + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.unspecifiedError()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"unspecifiedError"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_AJAR from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_AJAR + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.ajar()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"ajar"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_OPEN from Matter device, and then DoorState.DOOR_AJAR, ensuring supportedDoorStates is updated to include both states.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_OPEN + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"open"}, {visibility={displayed=false}})) + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_AJAR + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.ajar()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"open", "ajar"}, {visibility={displayed=false}})) + ) + end +) + +test.run_registered_tests() From 4f8bdd0f555eb93943c4e84c9b4405f1cede692a Mon Sep 17 00:00:00 2001 From: Tom Manley Date: Wed, 25 Mar 2026 13:22:19 -0500 Subject: [PATCH 200/538] WWST-10552 Update IKEA Matter device labels to match the catalog https://smartthings.atlassian.net/browse/WWST-10552 --- drivers/SmartThings/matter-sensor/fingerprints.yml | 10 +++++----- drivers/SmartThings/matter-switch/fingerprints.yml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index 4295c87a93..e3b483cad5 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -86,27 +86,27 @@ matterManufacturer: deviceProfileName: temperature-humidity-battery # Ikea - id: "4476/32773" - deviceLabel: TIMMERFLOTTE temp/hmd sensor + deviceLabel: TIMMERFLOTTE Temperature/Humidity Sensor vendorId: 0x117C productId: 0x8005 deviceProfileName: temperature-humidity-battery - id: "4476/32774" - deviceLabel: KLIPPBOK Matter water leak sensor smart + deviceLabel: KLIPPBOK Water Leak Sensor vendorId: 0x117C productId: 0x8006 deviceProfileName: leak-battery - id: "4476/12288" - deviceLabel: MYGGSPRAY wrlss mtn sensor + deviceLabel: MYGGSPRAY Motion Sensor vendorId: 0x117C productId: 0x3000 deviceProfileName: motion-illuminance-battery - id: "4476/12289" - deviceLabel: ALPSTUGA Matter air quality sensor smart + deviceLabel: ALPSTUGA Air Quality Sensor vendorId: 0x117C productId: 0x3001 deviceProfileName: aqs-modular-temp-humidity - id: "4476/32775" - deviceLabel: MYGGBETT Matter door/window sensor smart + deviceLabel: MYGGBETT Door/Window Sensor vendorId: 0x117C productId: 0x8007 deviceProfileName: contact-battery diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 115b06f325..852a67432d 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -896,12 +896,12 @@ matterManufacturer: deviceProfileName: plug-binary #Ikea - id: "4476/32768" - deviceLabel: BILRESA scroll wheel + deviceLabel: BILRESA Scroll Wheel vendorId: 0x117C productId: 0x8000 deviceProfileName: ikea-scroll - id: "4476/32769" - deviceLabel: BILRESA dual button + deviceLabel: BILRESA Dual Button vendorId: 0x117C productId: 0x8001 deviceProfileName: ikea-2-button-battery From 0ecff797ed30774b0e49ee77e39d39ffb9a7a193 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Mon, 23 Mar 2026 21:54:19 -0500 Subject: [PATCH 201/538] Reprofile on SW change of camera --- .../src/sub_drivers/camera/init.lua | 7 ++ .../src/test/test_matter_bridge.lua | 84 +++++++++++++++++++ .../src/test/test_matter_camera.lua | 59 +++++++++++++ 3 files changed, 150 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index 24ca154950..91ebe03ebc 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -47,6 +47,13 @@ function CameraLifecycleHandlers.driver_switched(driver, device) end function CameraLifecycleHandlers.info_changed(driver, device, event, args) + local software_version_changed = device.matter_version ~= nil and args.old_st_store.matter_version ~= nil and + device.matter_version.software ~= args.old_st_store.matter_version.software + + if software_version_changed then + camera_cfg.match_profile(device, false, false) + end + if not switch_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then camera_cfg.initialize_camera_capabilities(device) device:subscribe() 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 75830d55a7..42ec81e888 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua @@ -59,6 +59,26 @@ local mock_bridge = test.mock_device.build_test_matter_device({ } }) +local mock_basic_bridge = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("matter-bridge.yml"), + matter_version = { hardware = 1, software = 1 }, + 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 = 0x000E, device_type_revision = 1 } -- Aggregator + } + } + } +}) + local function test_init_mock_bridge() test.mock_device.add_test_device(mock_bridge) test.socket.device_lifecycle:__queue_receive({ mock_bridge.id, "added" }) @@ -67,6 +87,14 @@ local function test_init_mock_bridge() mock_bridge:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end +local function test_init_mock_basic_bridge() + test.mock_device.add_test_device(mock_basic_bridge) + test.socket.device_lifecycle:__queue_receive({ mock_basic_bridge.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_basic_bridge.id, "init" }) + test.socket.device_lifecycle:__queue_receive({ mock_basic_bridge.id, "doConfigure" }) + mock_basic_bridge:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + test.register_coroutine_test( "Profile should not change for devices with aggregator device type (bridges)", function() @@ -77,4 +105,60 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Camera reprofiling should happen after software version change when camera endpoint appears", + function() + local updated_endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x000E, device_type_revision = 1 } -- Aggregator + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = clusters.CameraAvStreamManagement.ID, + feature_map = clusters.CameraAvStreamManagement.types.Feature.VIDEO, + cluster_type = "SERVER" + }, + { cluster_id = clusters.PushAvStreamTransport.ID, cluster_type = "SERVER" } + }, + device_types = { + { device_type_id = 0x0142, device_type_revision = 1 } -- Camera + } + } + } + + test.socket.device_lifecycle:__queue_receive( + mock_basic_bridge:generate_info_changed({ + matter_version = { hardware = 1, software = 2 }, + endpoints = updated_endpoints + }) + ) + + mock_basic_bridge:expect_metadata_update({ + profile = "camera", + optional_component_capabilities = { + { + "main", + { + "videoCapture2", + "cameraViewportSettings", + "videoStreamSettings" + } + } + } + }) + end, + { + test_init = test_init_mock_basic_bridge, + min_api_version = 19 + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 2eee0b778e..a11f7f944b 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -346,6 +346,65 @@ end -- Matter Handler UTs +test.register_coroutine_test( + "Software version change should trigger camera reprofiling when camera endpoint is present", + function() + test.socket.device_lifecycle:__queue_receive( + mock_device:generate_info_changed({ matter_version = { hardware = 1, software = 2 } }) + ) + + mock_device:expect_metadata_update({ + optional_component_capabilities = { + { + "main", + { + "videoCapture2", + "cameraViewportSettings", + "videoStreamSettings", + "localMediaStorage", + "audioRecording", + "cameraPrivacyMode", + "imageControl", + "hdr", + "nightVision", + "mechanicalPanTiltZoom", + "zoneManagement", + "webrtc", + "motionSensor", + "sounds" + } + }, + { + "speaker", + { + "audioMute", + "audioVolume" + } + }, + { + "microphone", + { + "audioMute", + "audioVolume" + } + }, + { + "doorbell", + { + "button" + } + } + }, + profile = "camera" + }) + + test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)}) + end, + { + min_api_version = 19 + } +) + test.register_coroutine_test( "Reports mapping to EnabledState capability data type should generate appropriate events", function() From 6b38aef6985e27e92dd29ef6d8ca1a3b57f43a46 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Fri, 27 Mar 2026 13:17:42 -0500 Subject: [PATCH 202/538] WWSTCERT-10808 Sombra Shades WM25/L-Z (#2848) --- drivers/SmartThings/zigbee-window-treatment/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml b/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml index e15dd6f3a2..c59d96f93f 100644 --- a/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml +++ b/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml @@ -138,6 +138,11 @@ zigbeeManufacturer: manufacturer: HOPOsmart model: A2230011 deviceProfileName: window-shade-only + - id: "Sombra Shades/WM25/L-Z" + deviceLabel: Sombra Shades Window Treatment + manufacturer: Sombra Shades + model: WM25/L-Z + deviceProfileName: window-treatment-battery zigbeeGeneric: - id: "genericShade" From 144ddea03e4500f445864f52c25a9e49bbd6d2b7 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Thu, 26 Mar 2026 14:17:27 -0500 Subject: [PATCH 203/538] update default mired values in stateless color temp step handler --- .../src/switch_handlers/capability_handlers.lua | 5 +++-- .../matter-switch/src/switch_utils/fields.lua | 10 ++++++++-- .../matter-switch/src/test/test_stateless_step.lua | 6 +++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua index 3c65e3e8c0..eec323a335 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua @@ -125,11 +125,12 @@ end function CapabilityHandlers.handle_step_color_temperature_by_percent(driver, device, cmd) local step_percent_change = cmd.args and cmd.args.stepSize or 0 if step_percent_change == 0 then return end + step_percent_change = st_utils.clamp_value(step_percent_change, -100, 100) local endpoint_id = device:component_to_endpoint(cmd.component) -- before the Matter 1.3 lua libs update (HUB FW 55), there was no ColorControl StepModeEnum type defined local step_mode = step_percent_change > 0 and (clusters.ColorControl.types.StepModeEnum and clusters.ColorControl.types.StepModeEnum.DOWN or 3) or (clusters.ColorControl.types.StepModeEnum and clusters.ColorControl.types.StepModeEnum.UP or 1) - local min_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) or fields.COLOR_TEMPERATURE_MIRED_MIN -- default min mireds - local max_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MAX, endpoint_id) or fields.COLOR_TEMPERATURE_MIRED_MAX -- default max mireds + local min_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) or fields.DEFAULT_MIRED_MIN + local max_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MAX, endpoint_id) or fields.DEFAULT_MIRED_MAX local step_size_in_mireds = st_utils.round((max_mireds - min_mireds) * (math.abs(step_percent_change)/100.0)) device:send(clusters.ColorControl.server.commands.StepColorTemperature(device, endpoint_id, step_mode, step_size_in_mireds, fields.TRANSITION_TIME_FAST, min_mireds, max_mireds, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF)) end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 65a962ca2c..1432b18c74 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -15,8 +15,14 @@ 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 = st_utils.round(SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MIN) -SwitchFields.COLOR_TEMPERATURE_MIRED_MIN = st_utils.round(SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MAX) +SwitchFields.COLOR_TEMPERATURE_MIRED_MAX = st_utils.round(SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MIN) -- 1000 Mireds +SwitchFields.COLOR_TEMPERATURE_MIRED_MIN = st_utils.round(SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MAX) -- 67 Mireds + +-- These values are the config bounds in the default Matter profiles (e.g. light-level-colorTemperature, light-color-level) +local DEFAULT_KELVIN_MIN = 2200 +local DEFAULT_KELVIN_MAX = 6500 +SwitchFields.DEFAULT_MIRED_MIN = st_utils.round(SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/DEFAULT_KELVIN_MAX) -- 154 Mireds +SwitchFields.DEFAULT_MIRED_MAX = st_utils.round(SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/DEFAULT_KELVIN_MIN) -- 455 Mireds SwitchFields.SWITCH_LEVEL_LIGHTING_MIN = 1 SwitchFields.CURRENT_HUESAT_ATTR_MIN = 0 diff --git a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua index 0c5f19f597..345d8ad232 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua @@ -67,7 +67,7 @@ test.register_message_test( direction = "send", message = { mock_device_color_temp.id, - clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.DOWN, 187, fields.TRANSITION_TIME_FAST, fields.COLOR_TEMPERATURE_MIRED_MIN, fields.COLOR_TEMPERATURE_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.DOWN, 60, fields.TRANSITION_TIME_FAST, fields.DEFAULT_MIRED_MIN, fields.DEFAULT_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) }, }, { @@ -83,7 +83,7 @@ test.register_message_test( direction = "send", message = { mock_device_color_temp.id, - clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.DOWN, 840, fields.TRANSITION_TIME_FAST, fields.COLOR_TEMPERATURE_MIRED_MIN, fields.COLOR_TEMPERATURE_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.DOWN, 271, fields.TRANSITION_TIME_FAST, fields.DEFAULT_MIRED_MIN, fields.DEFAULT_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) }, }, { @@ -99,7 +99,7 @@ test.register_message_test( direction = "send", message = { mock_device_color_temp.id, - clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.UP, 467, fields.TRANSITION_TIME_FAST, fields.COLOR_TEMPERATURE_MIRED_MIN, fields.COLOR_TEMPERATURE_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.UP, 151, fields.TRANSITION_TIME_FAST, fields.DEFAULT_MIRED_MIN, fields.DEFAULT_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) }, } }, From 905bf505980d8a04029b101995308288de823324 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 24 Mar 2026 16:23:48 -0500 Subject: [PATCH 204/538] Setting up driver tests to run against previous versions of lua libs through jenkins pipeline --- .github/workflows/jenkins-driver-tests.yml | 44 ++++++++++++++++++++++ .github/workflows/run-tests.yml | 43 +-------------------- 2 files changed, 45 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/jenkins-driver-tests.yml diff --git a/.github/workflows/jenkins-driver-tests.yml b/.github/workflows/jenkins-driver-tests.yml new file mode 100644 index 0000000000..ec96330d39 --- /dev/null +++ b/.github/workflows/jenkins-driver-tests.yml @@ -0,0 +1,44 @@ +name: Run Jenkins driver tests +on: + pull_request: + paths: + - 'drivers/**' + +jobs: + trigger-driver-test: + strategy: + matrix: + version: + [ 60 ] + + runs-on: ubuntu-latest + steps: + + - name: Create Commit Status (Pending) + id: status + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + with: + script: | + core.setOutput('status_url', (await github.rest.repos.createCommitStatus({ + owner: context.repo.owner, + repo: context.repo.repo, + sha: context.sha, + state: 'pending', + description: 'Jenkins job triggered...', + context: 'Driver Tests (${{ matrix.version }})' + })).data.url); + - name: Trigger Jenkins Generic Webhook + env: + JENKINS_WEBHOOK_TOKEN: ${{ secrets.JENKINS_WEBHOOK_TOKEN }} + JENKINS_WEBHOOK_URL: ${{ secrets.JENKINS_WEBHOOK_URL }} + STATUS_URL: ${{ steps.status.outputs.status_url }} + run: | + set +x + curl -s -o /dev/null -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${JENKINS_WEBHOOK_TOKEN}" \ + -d "{\"status_url\": \"$STATUS_URL\", + \"version\": ${{ matrix.version }}, + \"commit\": ${{ github.event.pull_request.head.sha }} }" \ + "${JENKINS_WEBHOOK_URL}" + set -x diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e58afd5178..f6a03154cb 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -39,51 +39,10 @@ jobs: id: cache_key run: echo "CACHE_KEY=${{ steps.lib-version.outputs.url }}-v1" >> $GITHUB_OUTPUT - get-dev-artifact: - runs-on: ubuntu-latest - outputs: - cache_key: ${{ steps.cache_key.outputs.CACHE_KEY }} - if: ${{ contains(join(github.event.pull_request.labels.*.name), 'release-') && github.event.pull_request.head.repo.fork != 'true' }} - steps: - - name: Get the version from the label - id: label-version - run: | - echo "${{ join(github.event.pull_request.labels.*.name) }}" | grep -oP "release-\d+.\d+" | xargs > out - echo "LIBRARY_VERSION=$(cat out)" >> $GITHUB_OUTPUT - mkdir /home/runner/work/lua_libs - - name: Find latest artifact - id: latest - env: - ARTIFACTORY_URL: ${{ format('https://smartthings.jfrog.io/artifactory/edge-driver-libs/{0}/', steps.label-version.outputs.LIBRARY_VERSION) }} - ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} - ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} - run: | - wget --user=$ARTIFACTORY_USERNAME --password=$ARTIFACTORY_PASSWORD $ARTIFACTORY_URL -q -O - | grep '.zip' | awk -F' ' '{print $3"-"$4"\t"$2}' | sort -t - -k3n -k2M -k1n -k4n | tail -1 | grep -o 'lua_libs_[a-z0-9_]*.zip' | head -1 > out - echo "ZIP_FILE=$(cat out)" >> $GITHUB_OUTPUT - - name: Try to retrieve cache - id: cached-libs - uses: actions/cache@v3 - with: - path: '/home/runner/work/lua_libs' - key: ${{ steps.latest.outputs.ZIP_FILE }}-v1 - - name: Download and unpack specified version - if: steps.cached-libs.outputs.cache-hit != 'true' - env: - ARTIFACTORY_URL: ${{ format('https://smartthings.jfrog.io/artifactory/edge-driver-libs/{0}/{1}', steps.label-version.outputs.LIBRARY_VERSION, steps.latest.outputs.ZIP_FILE) }} - ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} - ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} - working-directory: '/home/runner/work/lua_libs' - run: | - wget --user=$ARTIFACTORY_USERNAME --password=$ARTIFACTORY_PASSWORD $ARTIFACTORY_URL -O lua_libs.zip - unzip lua_libs.zip - - name: Set output - id: cache_key - run: echo "CACHE_KEY=${{ steps.latest.outputs.ZIP_FILE }}-v1" >> $GITHUB_OUTPUT - run-driver-tests: runs-on: ubuntu-latest needs: - [ get-latest-release-artifact, get-dev-artifact ] + [ get-latest-release-artifact ] if: ${{ always() && contains(needs.*.result, 'success') && !contains(needs.*.result, 'failure') }} steps: - name: Set cache key From 30ec193508d3f914c219d3caed5c95cb58c7305b Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Mon, 30 Mar 2026 13:09:53 -0500 Subject: [PATCH 205/538] Revert "Support DPS feature (#2750)" This reverts commit edfba7e5004731486a88d61024e6657df674294b. Also removes the 'IS_MODULAR_PROFILE' field which is no longer needed. --- .../lock-modular-embedded-unlatch.yml | 3 - .../matter-lock/profiles/lock-modular.yml | 3 - .../matter-lock/src/lock_utils.lua | 2 +- .../matter-lock/src/new-matter-lock/init.lua | 64 +--- .../matter-lock/src/test/test_door_state.lua | 278 ------------------ 5 files changed, 2 insertions(+), 348 deletions(-) delete mode 100644 drivers/SmartThings/matter-lock/src/test/test_door_state.lua diff --git a/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml b/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml index 253374212b..3d9e68b44c 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml @@ -6,9 +6,6 @@ components: version: 1 - id: lockAlarm version: 1 - - id: doorState - version: 1 - optional: true - id: remoteControlStatus version: 1 - id: lockUsers diff --git a/drivers/SmartThings/matter-lock/profiles/lock-modular.yml b/drivers/SmartThings/matter-lock/profiles/lock-modular.yml index 7a664767dd..3a8a53bf70 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-modular.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-modular.yml @@ -4,9 +4,6 @@ components: capabilities: - id: lock version: 1 - - id: doorState - version: 1 - optional: true - id: lockAlarm version: 1 - id: remoteControlStatus diff --git a/drivers/SmartThings/matter-lock/src/lock_utils.lua b/drivers/SmartThings/matter-lock/src/lock_utils.lua index 94a0e747c4..ed804363b6 100644 --- a/drivers/SmartThings/matter-lock/src/lock_utils.lua +++ b/drivers/SmartThings/matter-lock/src/lock_utils.lua @@ -45,7 +45,7 @@ local lock_utils = { COMMAND_REQUEST_ID = "commandRequestId", MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED", ALIRO_READER_CONFIG_UPDATED = "aliroReaderConfigUpdated", - LATEST_DOOR_LOCK_FEATURE_MAP = "latestDoorLockFeatureMap" + LATEST_DOOR_LOCK_FEATURE_MAP = "latestDoorLockFeatureMap", } local capabilities = require "st.capabilities" local json = require "st.json" 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 5720856242..cca7a56b27 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -56,7 +56,6 @@ local battery_support = { local profiling_data = { BATTERY_SUPPORT = "__BATTERY_SUPPORT", - ENABLE_DOOR_STATE = "__ENABLE_DOOR_STATE" } local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} @@ -64,9 +63,6 @@ local subscribed_attributes = { [capabilities.lock.ID] = { DoorLock.attributes.LockState }, - [capabilities.doorState.ID] = { - DoorLock.attributes.DoorState - }, [capabilities.remoteControlStatus.ID] = { DoorLock.attributes.OperatingMode }, @@ -133,11 +129,6 @@ end local function device_init(driver, device) device:set_component_to_endpoint_fn(component_to_endpoint) - if #device:get_endpoints(clusters.DoorLock.ID, {feature_bitmap = clusters.DoorLock.types.Feature.DOOR_POSITION_SENSOR}) == 0 then - device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true}) - else - device:add_subscribed_attribute(clusters.DoorLock.attributes.DoorState) - end if #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) == 0 then device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist = true}) elseif device:get_field(profiling_data.BATTERY_SUPPORT) == nil then @@ -267,10 +258,6 @@ local function match_profile_modular(driver, device) table.insert(main_component_capabilities, capabilities.battery.ID) end - if device:get_field(profiling_data.ENABLE_DOOR_STATE) then - table.insert(main_component_capabilities, capabilities.doorState.ID) - end - table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) if lock_utils.optional_capabilities_list_changed(enabled_optional_component_capability_pairs, device.profile.components) then device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) @@ -378,10 +365,7 @@ local function info_changed(driver, device, event, args) if device:supports_capability_by_id(capabilities.lockAliro.ID) then set_reader_config(device) end - if device:supports_capability(capabilities.doorState) and device:get_latest_state("main", capabilities.doorState.ID, capabilities.doorState.supportedDoorStates.NAME) == nil then - device:emit_event(capabilities.doorState.supportedDoorStates({"open", "closed"}, {visibility = {displayed = false}})) -- open and closed are mandatory - end - if device:supports_capability(capabilities.lockAlarm) and device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then + if device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory end @@ -443,45 +427,6 @@ local function lock_state_handler(driver, device, ib, response) end) end -local function door_state_handler(driver, device, ib, response) - if ib.data.value == nil then - -- early return on nil data. Also, if ENABLE_DOOR_STATE is unset, set it to false and attempt profile matching. - if device:get_field(profiling_data.ENABLE_DOOR_STATE) == nil then - device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true}) - match_profile(driver, device) - end - return - elseif device:supports_capability(capabilities.doorState) == false then - -- if a non-nil report comes in and the doorState capability is unsupported, set ENABLE_DOOR_STATE to true and attempt profile matching. - device:set_field(profiling_data.ENABLE_DOOR_STATE, true, {persist = true}) - match_profile(driver, device) - return - end - - local DoorStateEnum = DoorLock.types.DoorStateEnum - local doorState = capabilities.doorState.doorState - local DOOR_STATE_MAP = { - [DoorStateEnum.DOOR_OPEN] = doorState.open, - [DoorStateEnum.DOOR_CLOSED] = doorState.closed, - [DoorStateEnum.DOOR_JAMMED] = doorState.jammed, - [DoorStateEnum.DOOR_FORCED_OPEN] = doorState.forcedOpen, - [DoorStateEnum.DOOR_UNSPECIFIED_ERROR] = doorState.unspecifiedError, - [DoorStateEnum.DOOR_AJAR] = doorState.ajar - } - local door_state = DOOR_STATE_MAP[ib.data.value] or doorState.unspecifiedError -- fallback to unspecifiedError if we receive a value we don't recognize - device:emit_event(door_state()) - - -- add new door states to supportedDoorStates list if not already present. - local supported_door_states = utils.deep_copy(device:get_latest_state("main", capabilities.doorState.ID, capabilities.doorState.supportedDoorStates.NAME) or {}) - for _, state in pairs(supported_door_states) do - if state == door_state.NAME then - return - end - end - table.insert(supported_door_states, door_state.NAME) - device:emit_event(capabilities.doorState.supportedDoorStates(supported_door_states, {visibility = {displayed = false}})) -end - --------------------- -- Operating Modes -- --------------------- @@ -730,12 +675,6 @@ local function door_lock_feature_map_handler(driver, device, ib, response) local feature_map = lock_utils.get_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id) or nil if feature_map ~= ib.data.value then lock_utils.set_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id, ib.data.value, { persist = true }) - -- If the DPS feature is changed, check the DoorState value and call the match_profile. - if ib.data.value & clusters.DoorLock.types.Feature.DOOR_POSITION_SENSOR == 0 then - device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true}) - else - device:set_field(profiling_data.ENABLE_DOOR_STATE, true, {persist = true}) - end match_profile(driver, device, true) end end @@ -2992,7 +2931,6 @@ local new_matter_lock_handler = { attr = { [DoorLock.ID] = { [DoorLock.attributes.LockState.ID] = lock_state_handler, - [DoorLock.attributes.DoorState.ID] = door_state_handler, [DoorLock.attributes.OperatingMode.ID] = operating_modes_handler, [DoorLock.attributes.NumberOfTotalUsersSupported.ID] = total_users_supported_handler, [DoorLock.attributes.NumberOfPINUsersSupported.ID] = pin_users_supported_handler, diff --git a/drivers/SmartThings/matter-lock/src/test/test_door_state.lua b/drivers/SmartThings/matter-lock/src/test/test_door_state.lua deleted file mode 100644 index c105346b15..0000000000 --- a/drivers/SmartThings/matter-lock/src/test/test_door_state.lua +++ /dev/null @@ -1,278 +0,0 @@ --- 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 clusters = require "st.matter.clusters" -local cluster_base = require "st.matter.cluster_base" -local DoorLock = clusters.DoorLock - -local mock_device_door_state_disabled = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("lock-modular.yml"), - manufacturer_info = { - vendor_id = 0x115f, - product_id = 0x2802, - }, - endpoints = { - { - endpoint_id = 0, - clusters = { - { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, - }, - device_types = { - { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode - } - }, - { - endpoint_id = 1, - clusters = { - { - cluster_id = DoorLock.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = 0x20, -- DPS - } - }, - device_types = { - { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock - } - } - } -}) - -local enabled_optional_component_capability_pairs = {{ "main", {capabilities.doorState.ID} }} -local mock_device = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition( - "lock-modular.yml", - {enabled_optional_capabilities = enabled_optional_component_capability_pairs} - ), - manufacturer_info = { - vendor_id = 0x115f, - product_id = 0x2802, - }, - endpoints = { - { - endpoint_id = 0, - clusters = { - { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, - }, - device_types = { - { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode - } - }, - { - endpoint_id = 1, - clusters = { - { - cluster_id = DoorLock.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = 0x20, -- DPS - } - }, - device_types = { - { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock - } - } - } -}) - -local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} -local function test_init() - test.disable_startup_messages() - local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) - subscribe_request:merge(DoorLock.attributes.DoorState:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) - subscribe_request:merge(cluster_base.subscribe(mock_device_door_state_disabled, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) - subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) - subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) - test.mock_device.add_test_device(mock_device) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) - ) - 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({ provisioning_state = "PROVISIONED" }) -end - -test.set_test_init_function(test_init) - -local function test_init_door_state_disabled() - test.disable_startup_messages() - local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_door_state_disabled) - subscribe_request:merge(DoorLock.attributes.DoorState:subscribe(mock_device_door_state_disabled)) - subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_door_state_disabled)) - subscribe_request:merge(cluster_base.subscribe(mock_device_door_state_disabled, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) - subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_door_state_disabled)) - subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_door_state_disabled)) - test.mock_device.add_test_device(mock_device_door_state_disabled) - test.socket.device_lifecycle:__queue_receive({ mock_device_door_state_disabled.id, "added" }) - test.socket.capability:__expect_send( - mock_device_door_state_disabled:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) - ) - test.socket.device_lifecycle:__queue_receive({ mock_device_door_state_disabled.id, "init" }) - test.socket.matter:__expect_send({mock_device_door_state_disabled.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device_door_state_disabled.id, "doConfigure" }) - mock_device_door_state_disabled:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -end - -test.register_coroutine_test( - "Check that the device is updated with correct capabilities based on the profile and attributes.", - function () - test.socket.matter:__queue_receive({ - mock_device_door_state_disabled.id, - DoorLock.attributes.DoorState:build_test_report_data(mock_device_door_state_disabled, 1, DoorLock.attributes.DoorState.DOOR_CLOSED) - }) - test.socket.capability:__expect_send( - mock_device_door_state_disabled:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) - ) - test.socket.capability:__expect_send( - mock_device_door_state_disabled:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) - ) - - mock_device_door_state_disabled:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {"doorState"}}}}) - end, - { test_init = test_init_door_state_disabled } -) - - -test.register_coroutine_test( - "Handle received DoorState.DOOR_CLOSED from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_CLOSED - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.closed()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"closed"}, {visibility={displayed=false}})) - ) - end -) - -test.register_coroutine_test( - "Handle received DoorState.DOOR_JAMMED from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_JAMMED - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.jammed()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"jammed"}, {visibility={displayed=false}})) - ) - end -) - -test.register_coroutine_test( - "Handle received DoorState.DOOR_FORCED_OPEN from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_FORCED_OPEN - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.forcedOpen()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"forcedOpen"}, {visibility={displayed=false}})) - ) - end -) - -test.register_coroutine_test( - "Handle received DoorState.DOOR_UNSPECIFIED_ERROR from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_UNSPECIFIED_ERROR - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.unspecifiedError()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"unspecifiedError"}, {visibility={displayed=false}})) - ) - end -) - -test.register_coroutine_test( - "Handle received DoorState.DOOR_AJAR from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_AJAR - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.ajar()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"ajar"}, {visibility={displayed=false}})) - ) - end -) - -test.register_coroutine_test( - "Handle received DoorState.DOOR_OPEN from Matter device, and then DoorState.DOOR_AJAR, ensuring supportedDoorStates is updated to include both states.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_OPEN - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.open()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"open"}, {visibility={displayed=false}})) - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_AJAR - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.ajar()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"open", "ajar"}, {visibility={displayed=false}})) - ) - end -) - -test.run_registered_tests() From 851da697aa08f14d6d9945d082bcd5ac25d616cb Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Mon, 30 Mar 2026 13:09:53 -0500 Subject: [PATCH 206/538] Revert "Support DPS feature (#2750)" This reverts commit edfba7e5004731486a88d61024e6657df674294b. Also removes the 'IS_MODULAR_PROFILE' field which is no longer needed. --- .../lock-modular-embedded-unlatch.yml | 3 - .../matter-lock/profiles/lock-modular.yml | 3 - .../matter-lock/src/lock_utils.lua | 2 +- .../matter-lock/src/new-matter-lock/init.lua | 64 +--- .../matter-lock/src/test/test_door_state.lua | 278 ------------------ 5 files changed, 2 insertions(+), 348 deletions(-) delete mode 100644 drivers/SmartThings/matter-lock/src/test/test_door_state.lua diff --git a/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml b/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml index 253374212b..3d9e68b44c 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml @@ -6,9 +6,6 @@ components: version: 1 - id: lockAlarm version: 1 - - id: doorState - version: 1 - optional: true - id: remoteControlStatus version: 1 - id: lockUsers diff --git a/drivers/SmartThings/matter-lock/profiles/lock-modular.yml b/drivers/SmartThings/matter-lock/profiles/lock-modular.yml index 7a664767dd..3a8a53bf70 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-modular.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-modular.yml @@ -4,9 +4,6 @@ components: capabilities: - id: lock version: 1 - - id: doorState - version: 1 - optional: true - id: lockAlarm version: 1 - id: remoteControlStatus diff --git a/drivers/SmartThings/matter-lock/src/lock_utils.lua b/drivers/SmartThings/matter-lock/src/lock_utils.lua index 94a0e747c4..ed804363b6 100644 --- a/drivers/SmartThings/matter-lock/src/lock_utils.lua +++ b/drivers/SmartThings/matter-lock/src/lock_utils.lua @@ -45,7 +45,7 @@ local lock_utils = { COMMAND_REQUEST_ID = "commandRequestId", MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED", ALIRO_READER_CONFIG_UPDATED = "aliroReaderConfigUpdated", - LATEST_DOOR_LOCK_FEATURE_MAP = "latestDoorLockFeatureMap" + LATEST_DOOR_LOCK_FEATURE_MAP = "latestDoorLockFeatureMap", } local capabilities = require "st.capabilities" local json = require "st.json" 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 5720856242..cca7a56b27 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -56,7 +56,6 @@ local battery_support = { local profiling_data = { BATTERY_SUPPORT = "__BATTERY_SUPPORT", - ENABLE_DOOR_STATE = "__ENABLE_DOOR_STATE" } local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} @@ -64,9 +63,6 @@ local subscribed_attributes = { [capabilities.lock.ID] = { DoorLock.attributes.LockState }, - [capabilities.doorState.ID] = { - DoorLock.attributes.DoorState - }, [capabilities.remoteControlStatus.ID] = { DoorLock.attributes.OperatingMode }, @@ -133,11 +129,6 @@ end local function device_init(driver, device) device:set_component_to_endpoint_fn(component_to_endpoint) - if #device:get_endpoints(clusters.DoorLock.ID, {feature_bitmap = clusters.DoorLock.types.Feature.DOOR_POSITION_SENSOR}) == 0 then - device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true}) - else - device:add_subscribed_attribute(clusters.DoorLock.attributes.DoorState) - end if #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) == 0 then device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist = true}) elseif device:get_field(profiling_data.BATTERY_SUPPORT) == nil then @@ -267,10 +258,6 @@ local function match_profile_modular(driver, device) table.insert(main_component_capabilities, capabilities.battery.ID) end - if device:get_field(profiling_data.ENABLE_DOOR_STATE) then - table.insert(main_component_capabilities, capabilities.doorState.ID) - end - table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) if lock_utils.optional_capabilities_list_changed(enabled_optional_component_capability_pairs, device.profile.components) then device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) @@ -378,10 +365,7 @@ local function info_changed(driver, device, event, args) if device:supports_capability_by_id(capabilities.lockAliro.ID) then set_reader_config(device) end - if device:supports_capability(capabilities.doorState) and device:get_latest_state("main", capabilities.doorState.ID, capabilities.doorState.supportedDoorStates.NAME) == nil then - device:emit_event(capabilities.doorState.supportedDoorStates({"open", "closed"}, {visibility = {displayed = false}})) -- open and closed are mandatory - end - if device:supports_capability(capabilities.lockAlarm) and device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then + if device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory end @@ -443,45 +427,6 @@ local function lock_state_handler(driver, device, ib, response) end) end -local function door_state_handler(driver, device, ib, response) - if ib.data.value == nil then - -- early return on nil data. Also, if ENABLE_DOOR_STATE is unset, set it to false and attempt profile matching. - if device:get_field(profiling_data.ENABLE_DOOR_STATE) == nil then - device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true}) - match_profile(driver, device) - end - return - elseif device:supports_capability(capabilities.doorState) == false then - -- if a non-nil report comes in and the doorState capability is unsupported, set ENABLE_DOOR_STATE to true and attempt profile matching. - device:set_field(profiling_data.ENABLE_DOOR_STATE, true, {persist = true}) - match_profile(driver, device) - return - end - - local DoorStateEnum = DoorLock.types.DoorStateEnum - local doorState = capabilities.doorState.doorState - local DOOR_STATE_MAP = { - [DoorStateEnum.DOOR_OPEN] = doorState.open, - [DoorStateEnum.DOOR_CLOSED] = doorState.closed, - [DoorStateEnum.DOOR_JAMMED] = doorState.jammed, - [DoorStateEnum.DOOR_FORCED_OPEN] = doorState.forcedOpen, - [DoorStateEnum.DOOR_UNSPECIFIED_ERROR] = doorState.unspecifiedError, - [DoorStateEnum.DOOR_AJAR] = doorState.ajar - } - local door_state = DOOR_STATE_MAP[ib.data.value] or doorState.unspecifiedError -- fallback to unspecifiedError if we receive a value we don't recognize - device:emit_event(door_state()) - - -- add new door states to supportedDoorStates list if not already present. - local supported_door_states = utils.deep_copy(device:get_latest_state("main", capabilities.doorState.ID, capabilities.doorState.supportedDoorStates.NAME) or {}) - for _, state in pairs(supported_door_states) do - if state == door_state.NAME then - return - end - end - table.insert(supported_door_states, door_state.NAME) - device:emit_event(capabilities.doorState.supportedDoorStates(supported_door_states, {visibility = {displayed = false}})) -end - --------------------- -- Operating Modes -- --------------------- @@ -730,12 +675,6 @@ local function door_lock_feature_map_handler(driver, device, ib, response) local feature_map = lock_utils.get_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id) or nil if feature_map ~= ib.data.value then lock_utils.set_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id, ib.data.value, { persist = true }) - -- If the DPS feature is changed, check the DoorState value and call the match_profile. - if ib.data.value & clusters.DoorLock.types.Feature.DOOR_POSITION_SENSOR == 0 then - device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true}) - else - device:set_field(profiling_data.ENABLE_DOOR_STATE, true, {persist = true}) - end match_profile(driver, device, true) end end @@ -2992,7 +2931,6 @@ local new_matter_lock_handler = { attr = { [DoorLock.ID] = { [DoorLock.attributes.LockState.ID] = lock_state_handler, - [DoorLock.attributes.DoorState.ID] = door_state_handler, [DoorLock.attributes.OperatingMode.ID] = operating_modes_handler, [DoorLock.attributes.NumberOfTotalUsersSupported.ID] = total_users_supported_handler, [DoorLock.attributes.NumberOfPINUsersSupported.ID] = pin_users_supported_handler, diff --git a/drivers/SmartThings/matter-lock/src/test/test_door_state.lua b/drivers/SmartThings/matter-lock/src/test/test_door_state.lua deleted file mode 100644 index c105346b15..0000000000 --- a/drivers/SmartThings/matter-lock/src/test/test_door_state.lua +++ /dev/null @@ -1,278 +0,0 @@ --- 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 clusters = require "st.matter.clusters" -local cluster_base = require "st.matter.cluster_base" -local DoorLock = clusters.DoorLock - -local mock_device_door_state_disabled = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("lock-modular.yml"), - manufacturer_info = { - vendor_id = 0x115f, - product_id = 0x2802, - }, - endpoints = { - { - endpoint_id = 0, - clusters = { - { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, - }, - device_types = { - { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode - } - }, - { - endpoint_id = 1, - clusters = { - { - cluster_id = DoorLock.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = 0x20, -- DPS - } - }, - device_types = { - { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock - } - } - } -}) - -local enabled_optional_component_capability_pairs = {{ "main", {capabilities.doorState.ID} }} -local mock_device = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition( - "lock-modular.yml", - {enabled_optional_capabilities = enabled_optional_component_capability_pairs} - ), - manufacturer_info = { - vendor_id = 0x115f, - product_id = 0x2802, - }, - endpoints = { - { - endpoint_id = 0, - clusters = { - { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, - }, - device_types = { - { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode - } - }, - { - endpoint_id = 1, - clusters = { - { - cluster_id = DoorLock.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = 0x20, -- DPS - } - }, - device_types = { - { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock - } - } - } -}) - -local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} -local function test_init() - test.disable_startup_messages() - local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) - subscribe_request:merge(DoorLock.attributes.DoorState:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) - subscribe_request:merge(cluster_base.subscribe(mock_device_door_state_disabled, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) - subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) - subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) - test.mock_device.add_test_device(mock_device) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) - ) - 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({ provisioning_state = "PROVISIONED" }) -end - -test.set_test_init_function(test_init) - -local function test_init_door_state_disabled() - test.disable_startup_messages() - local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_door_state_disabled) - subscribe_request:merge(DoorLock.attributes.DoorState:subscribe(mock_device_door_state_disabled)) - subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_door_state_disabled)) - subscribe_request:merge(cluster_base.subscribe(mock_device_door_state_disabled, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) - subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_door_state_disabled)) - subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_door_state_disabled)) - test.mock_device.add_test_device(mock_device_door_state_disabled) - test.socket.device_lifecycle:__queue_receive({ mock_device_door_state_disabled.id, "added" }) - test.socket.capability:__expect_send( - mock_device_door_state_disabled:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) - ) - test.socket.device_lifecycle:__queue_receive({ mock_device_door_state_disabled.id, "init" }) - test.socket.matter:__expect_send({mock_device_door_state_disabled.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device_door_state_disabled.id, "doConfigure" }) - mock_device_door_state_disabled:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -end - -test.register_coroutine_test( - "Check that the device is updated with correct capabilities based on the profile and attributes.", - function () - test.socket.matter:__queue_receive({ - mock_device_door_state_disabled.id, - DoorLock.attributes.DoorState:build_test_report_data(mock_device_door_state_disabled, 1, DoorLock.attributes.DoorState.DOOR_CLOSED) - }) - test.socket.capability:__expect_send( - mock_device_door_state_disabled:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) - ) - test.socket.capability:__expect_send( - mock_device_door_state_disabled:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) - ) - - mock_device_door_state_disabled:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {"doorState"}}}}) - end, - { test_init = test_init_door_state_disabled } -) - - -test.register_coroutine_test( - "Handle received DoorState.DOOR_CLOSED from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_CLOSED - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.closed()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"closed"}, {visibility={displayed=false}})) - ) - end -) - -test.register_coroutine_test( - "Handle received DoorState.DOOR_JAMMED from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_JAMMED - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.jammed()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"jammed"}, {visibility={displayed=false}})) - ) - end -) - -test.register_coroutine_test( - "Handle received DoorState.DOOR_FORCED_OPEN from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_FORCED_OPEN - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.forcedOpen()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"forcedOpen"}, {visibility={displayed=false}})) - ) - end -) - -test.register_coroutine_test( - "Handle received DoorState.DOOR_UNSPECIFIED_ERROR from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_UNSPECIFIED_ERROR - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.unspecifiedError()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"unspecifiedError"}, {visibility={displayed=false}})) - ) - end -) - -test.register_coroutine_test( - "Handle received DoorState.DOOR_AJAR from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_AJAR - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.ajar()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"ajar"}, {visibility={displayed=false}})) - ) - end -) - -test.register_coroutine_test( - "Handle received DoorState.DOOR_OPEN from Matter device, and then DoorState.DOOR_AJAR, ensuring supportedDoorStates is updated to include both states.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_OPEN - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.open()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"open"}, {visibility={displayed=false}})) - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.DoorState:build_test_report_data( - mock_device, 1, DoorLock.attributes.DoorState.DOOR_AJAR - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.doorState.ajar()) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"open", "ajar"}, {visibility={displayed=false}})) - ) - end -) - -test.run_registered_tests() From 9d979f1e035bff728ff5ed0bf8ff349019c9d88d Mon Sep 17 00:00:00 2001 From: Jeff Page Date: Tue, 31 Mar 2026 11:29:35 -0500 Subject: [PATCH 207/538] WWST add support for Zooz ZEN05 (#2843) * Adding support for ZEN05 --- .../SmartThings/zwave-switch/fingerprints.yml | 7 ++ .../zwave-switch/profiles/zooz-zen05-plug.yml | 90 +++++++++++++++++++ drivers/SmartThings/zwave-switch/src/init.lua | 1 + .../zwave-switch/src/preferences.lua | 15 ++++ 4 files changed, 113 insertions(+) create mode 100644 drivers/SmartThings/zwave-switch/profiles/zooz-zen05-plug.yml diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index d90a0c6b76..46faa7d106 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -916,6 +916,13 @@ zwaveManufacturer: manufacturerId: 0x010F productType: 0x0102 deviceProfileName: fibaro-dimmer-2 +#Zooz + - id: "Zooz/ZEN05" + deviceLabel: Zooz Outdoor Plug ZEN05 + manufacturerId: 0x027A + productType: 0x7000 + productId: 0xB001 + deviceProfileName: zooz-zen05-plug #Shelly/Qubino - id: 1120/2/137 deviceLabel: Wave Plug UK diff --git a/drivers/SmartThings/zwave-switch/profiles/zooz-zen05-plug.yml b/drivers/SmartThings/zwave-switch/profiles/zooz-zen05-plug.yml new file mode 100644 index 0000000000..43685ff2c7 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/profiles/zooz-zen05-plug.yml @@ -0,0 +1,90 @@ +name: zooz-zen05-plug +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: voltageMeasurement + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartPlug +preferences: + - name: "driverInfo" + title: "Driver Info" + description: "This driver may have limited features, check the online Zooz documentation for information about custom drivers." + preferenceType: string + definition: + stringType: paragraph + #param 1 + - name: "ledMode" + title: "LED Indicator Behavior" + description: "Decide how the LED indicator behaves depending on the on/off status of the plug." + required: false + preferenceType: enumeration + definition: + options: + 0: "On when on, off when off *" + 1: "On when off, off when on" + 2: "Always off" + default: 0 + #param 7 + - name: "ledBrightness" + title: "LED Brightness" + description: "Choose the LED indicator brightness level." + required: false + preferenceType: enumeration + definition: + options: + 0: "High" + 1: "Medium" + 2: "Low *" + default: 2 + #param 2 + - name: "autoTurnOff" + title: "Auto-off Timer" + description: "Default: 0=disabled; Auto-off timer will automatically turn the Smart Plug off after x minutes once it has been turned on." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 65535 + default: 0 + #param 4 + - name: "autoTurnOn" + title: "Auto-on Timer" + description: "Default: 0=disabled; Auto-on timer will automatically turn the Smart Plug on after x minutes once it has been turned off." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 65535 + default: 0 + #param 6 + - name: "powerRecovery" + title: "On/Off Status After Power Failure" + description: "Choose the recovery state for your Smart Plug if power outage occurs." + required: false + preferenceType: enumeration + definition: + options: + 2: "Restores Prior Status *" + 0: "Always Off once restored" + 1: "Always On once restored" + default: 2 + #param 8 + - name: "manualControl" + title: "Manual Control" + description: "Disable or enable manual control (turning the Smart Plug on and off using the physical button)." + required: false + preferenceType: enumeration + definition: + options: + 0: "Disabled" + 1: "Enabled *" + default: 1 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/init.lua b/drivers/SmartThings/zwave-switch/src/init.lua index 26cca570a7..9f40fd84e4 100644 --- a/drivers/SmartThings/zwave-switch/src/init.lua +++ b/drivers/SmartThings/zwave-switch/src/init.lua @@ -102,6 +102,7 @@ local driver_template = { capabilities.battery, capabilities.energyMeter, capabilities.powerMeter, + capabilities.voltageMeasurement, capabilities.colorControl, capabilities.button, capabilities.temperatureMeasurement, diff --git a/drivers/SmartThings/zwave-switch/src/preferences.lua b/drivers/SmartThings/zwave-switch/src/preferences.lua index 824e570070..798efaddf0 100644 --- a/drivers/SmartThings/zwave-switch/src/preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/preferences.lua @@ -362,6 +362,21 @@ local devices = { dimmerPaddleControl = {parameter_number = 27, size = 1} } }, + ZOOZ_ZEN05 = { + MATCHING_MATRIX = { + mfrs = 0x027A, + product_types = 0x7000, + product_ids = 0xB001 + }, + PARAMETERS = { + ledMode = { parameter_number = 1, size = 1 }, + autoTurnOff = { parameter_number = 2, size = 4 }, + autoTurnOn = { parameter_number = 4, size = 4 }, + powerRecovery = { parameter_number = 6, size = 1 }, + ledBrightness = { parameter_number = 7, size = 1 }, + manualControl = { parameter_number = 8, size = 1 }, + } + }, AEOTEC_HEAVY_DUTY = { MATCHING_MATRIX = { mfrs = 0x0086, From 7735fa81d1813d08bedd0cb0b843cd06d0862e78 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Wed, 1 Apr 2026 10:33:51 -0500 Subject: [PATCH 208/538] update deep_equals in other drivers --- .../air_quality_sensor_utils/utils.lua | 9 +++++---- .../SmartThings/matter-switch/src/switch_utils/utils.lua | 8 ++++---- .../matter-thermostat/src/thermostat_utils/utils.lua | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) 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 index e23b9f7eeb..0ce7ffb266 100644 --- 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 @@ -102,15 +102,15 @@ function AirQualitySensorUtils.deep_equals(a, b, opts, seen) end -- Compare keys/values from a - for k, v in next, a do - if not AirQualitySensorUtils.deep_equals(v, rawget(b, k), opts, seen) then + for k, v in pairs(a) do + if not AirQualitySensorUtils.deep_equals(v, b[k], opts, seen) then return false end end -- Ensure b doesn't have extra keys - for k in next, b do - if rawget(a, k) == nil then + for k in pairs(b) do + if a[k] == nil then return false end end @@ -121,4 +121,5 @@ function AirQualitySensorUtils.deep_equals(a, b, opts, seen) return AirQualitySensorUtils.deep_equals(mt_a, mt_b, opts, seen) end + return AirQualitySensorUtils diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 1afecb318f..f949a15a56 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -351,15 +351,15 @@ function utils.deep_equals(a, b, opts, seen) end -- Compare keys/values from a - for k, v in next, a do - if not utils.deep_equals(v, rawget(b, k), opts, seen) then + for k, v in pairs(a) do + if not utils.deep_equals(v, b[k], opts, seen) then return false end end -- Ensure b doesn't have extra keys - for k in next, b do - if rawget(a, k) == nil then + for k in pairs(b) do + if a[k] == nil then return false end end diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua index dd298bc987..a233a0c0e7 100644 --- a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua @@ -97,15 +97,15 @@ function ThermostatUtils.deep_equals(a, b, opts, seen) end -- Compare keys/values from a - for k, v in next, a do - if not ThermostatUtils.deep_equals(v, rawget(b, k), opts, seen) then + for k, v in pairs(a) do + if not ThermostatUtils.deep_equals(v, b[k], opts, seen) then return false end end -- Ensure b doesn't have extra keys - for k in next, b do - if rawget(a, k) == nil then + for k in pairs(b) do + if a[k] == nil then return false end end From 656033e98f6f6778bbd21c7406f830151e84ea74 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Fri, 27 Mar 2026 13:17:42 -0500 Subject: [PATCH 209/538] WWSTCERT-10808 Sombra Shades WM25/L-Z (#2848) --- drivers/SmartThings/zigbee-window-treatment/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml b/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml index e15dd6f3a2..c59d96f93f 100644 --- a/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml +++ b/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml @@ -138,6 +138,11 @@ zigbeeManufacturer: manufacturer: HOPOsmart model: A2230011 deviceProfileName: window-shade-only + - id: "Sombra Shades/WM25/L-Z" + deviceLabel: Sombra Shades Window Treatment + manufacturer: Sombra Shades + model: WM25/L-Z + deviceProfileName: window-treatment-battery zigbeeGeneric: - id: "genericShade" From e9d93946882f62feb56341584569bf8c77b0224e Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:32:49 -0500 Subject: [PATCH 210/538] update next to __pairs, rawget to __index (#2865) --- .../SmartThings/matter-lock/src/new-matter-lock/init.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 cca7a56b27..1e7d461eda 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -324,15 +324,15 @@ local function deep_equals(a, b, opts, seen) end -- Compare keys/values from a - for k, v in next, a do - if not deep_equals(v, rawget(b, k), opts, seen) then + for k, v in pairs(a) do + if not deep_equals(v, b[k], opts, seen) then return false end end -- Ensure b doesn't have extra keys - for k in next, b do - if rawget(a, k) == nil then + for k in pairs(b) do + if a[k] == nil then return false end end From 36486994d7034bbde1e3ffa7268f79853d11f4d1 Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Wed, 1 Apr 2026 14:07:52 -0500 Subject: [PATCH 211/538] Remove duplicate code from bad merge (#2872) --- .../air_quality_sensor/air_quality_sensor_utils/utils.lua | 6 ------ 1 file changed, 6 deletions(-) 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 index 2b179b8800..c11ac46605 100644 --- 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 @@ -121,10 +121,4 @@ function AirQualitySensorUtils.deep_equals(a, b, opts, seen) return AirQualitySensorUtils.deep_equals(mt_a, mt_b, opts, seen) end - -- Compare metatables - local mt_a = getmetatable(a) - local mt_b = getmetatable(b) - return AirQualitySensorUtils.deep_equals(mt_a, mt_b, opts, seen) -end - return AirQualitySensorUtils From b9d7b4076755530444e1b0299dcc5b6dfc73d4b9 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 31 Mar 2026 15:53:59 -0500 Subject: [PATCH 212/538] Script for and updating min_api_version of tests. Updated to run PRs against 59 --- .github/workflows/jenkins-driver-tests.yml | 2 +- .../src/test/test_cook_top.lua | 8 +- .../src/test/test_dishwasher.lua | 16 +- .../src/test/test_extractor_hood.lua | 22 +- .../src/test/test_laundry_dryer.lua | 18 +- .../src/test/test_laundry_washer.lua | 4 +- .../src/test/test_matter_appliance_rpc_5.lua | 24 +- .../src/test/test_microwave_oven.lua | 10 +- .../matter-appliance/src/test/test_oven.lua | 20 +- .../src/test/test_refrigerator.lua | 10 +- .../src/test/test_matter_button.lua | 20 +- .../test/test_matter_button_parent_child.lua | 16 +- .../src/test/test_matter_multi_button.lua | 38 +- .../src/test/test_battery_storage.lua | 10 +- .../matter-energy/src/test/test_evse.lua | 24 +- .../src/test/test_evse_energy_meas.lua | 4 +- .../src/test/test_solar_power.lua | 6 +- .../test_thread_border_router_network.lua | 14 +- .../src/test/test_aqara_matter_lock.lua | 16 +- .../src/test/test_bridged_matter_lock.lua | 4 +- .../matter-lock/src/test/test_matter_lock.lua | 22 +- .../src/test/test_matter_lock_battery.lua | 6 +- .../test/test_matter_lock_batteryLevel.lua | 2 +- .../src/test/test_matter_lock_codes.lua | 36 +- .../src/test/test_matter_lock_cota.lua | 26 +- .../src/test/test_matter_lock_modular.lua | 14 +- .../src/test/test_matter_lock_unlatch.lua | 18 +- .../src/test/test_new_matter_lock.lua | 92 +- .../src/test/test_new_matter_lock_aliro.lua | 1364 ++++++++--------- .../src/test/test_new_matter_lock_battery.lua | 24 +- .../src/test/test_matter_media_speaker.lua | 10 +- .../test/test_matter_media_video_player.lua | 18 +- .../matter-rvc/src/test/test_matter_rvc.lua | 38 +- .../test/test_matter_air_quality_sensor.lua | 22 +- ...test_matter_air_quality_sensor_modular.lua | 4 +- .../test/test_matter_bosch_button_contact.lua | 14 +- .../src/test/test_matter_flow_sensor.lua | 6 +- .../test/test_matter_freeze_leak_sensor.lua | 12 +- .../src/test/test_matter_pressure_sensor.lua | 6 +- .../src/test/test_matter_rain_sensor.lua | 4 +- .../src/test/test_matter_sensor.lua | 18 +- .../src/test/test_matter_sensor_battery.lua | 6 +- .../test/test_matter_sensor_featuremap.lua | 6 +- .../src/test/test_matter_sensor_rpc.lua | 2 +- .../src/test/test_matter_smoke_co_alarm.lua | 22 +- .../test_matter_smoke_co_alarm_battery.lua | 6 +- .../test/test_aqara_climate_sensor_w100.lua | 28 +- .../src/test/test_aqara_cube.lua | 4 +- .../src/test/test_aqara_light_switch_h2.lua | 4 +- .../src/test/test_electrical_sensor_set.lua | 22 +- .../src/test/test_electrical_sensor_tree.lua | 12 +- .../src/test/test_eve_energy.lua | 26 +- .../src/test/test_ikea_scroll.lua | 18 +- .../test/test_light_illuminance_motion.lua | 28 +- .../src/test/test_matter_bridge.lua | 4 +- .../src/test/test_matter_button.lua | 28 +- .../src/test/test_matter_camera.lua | 82 +- .../src/test/test_matter_light_fan.lua | 12 +- .../src/test/test_matter_multi_button.lua | 48 +- .../test/test_matter_multi_button_motion.lua | 40 +- .../test_matter_multi_button_switch_mcd.lua | 20 +- .../test_matter_sensor_offset_preferences.lua | 4 +- .../src/test/test_matter_switch.lua | 54 +- .../test/test_matter_switch_device_types.lua | 26 +- .../src/test/test_matter_water_valve.lua | 16 +- .../src/test/test_multi_switch_mcd.lua | 6 +- .../test_multi_switch_parent_child_lights.lua | 22 +- .../test_multi_switch_parent_child_plugs.lua | 20 +- .../src/test/test_stateless_step.lua | 4 +- .../src/test/test_third_reality_mk1.lua | 2 +- .../src/test/test_matter_air_purifier.lua | 26 +- .../test/test_matter_air_purifier_api9.lua | 26 +- .../test/test_matter_air_purifier_modular.lua | 4 +- .../src/test/test_matter_fan.lua | 4 +- .../src/test/test_matter_heat_pump.lua | 26 +- .../src/test/test_matter_room_ac.lua | 16 +- .../src/test/test_matter_room_ac_modular.lua | 4 +- .../src/test/test_matter_thermo_battery.lua | 6 +- .../test/test_matter_thermo_featuremap.lua | 8 +- ...st_matter_thermo_multiple_device_types.lua | 6 +- .../test_matter_thermo_setpoint_limits.lua | 20 +- ...test_matter_thermo_setpoint_limits_rpc.lua | 6 +- .../src/test/test_matter_thermostat.lua | 48 +- ...est_matter_thermostat_composed_bridged.lua | 42 +- .../test/test_matter_thermostat_modular.lua | 2 +- .../src/test/test_matter_thermostat_rpc5.lua | 2 +- .../src/test/test_matter_water_heater.lua | 14 +- .../src/test/test_matter_window_covering.lua | 66 +- .../src/test/test_virtual_switch.lua | 14 +- .../test_MultiIR_air_quality_detector.lua | 44 +- .../src/test/test_shus_mattress.lua | 112 +- .../src/test/test_SLED_button.lua | 6 +- .../src/test/test_aduro_button.lua | 6 +- .../src/test/test_aqara_button.lua | 22 +- .../src/test/test_centralite_button.lua | 10 +- .../src/test/test_dimming_remote.lua | 12 +- .../src/test/test_ewelink_button.lua | 10 +- .../src/test/test_ezviz_button.lua | 12 +- .../src/test/test_frient_button.lua | 18 +- .../src/test/test_heiman_button.lua | 16 +- .../src/test/test_ikea_on_off.lua | 10 +- .../src/test/test_ikea_open_close.lua | 10 +- .../src/test/test_ikea_remote_control.lua | 10 +- .../src/test/test_iris_button.lua | 18 +- .../test/test_linxura_aura_smart_button.lua | 8 +- ...est_linxura_smart_controller_4x_button.lua | 8 +- .../src/test/test_push_only_button.lua | 12 +- .../src/test/test_robb_4x_button.lua | 14 +- .../src/test/test_robb_8x_button.lua | 14 +- .../src/test/test_samjin_button.lua | 4 +- .../src/test/test_shinasystem_button.lua | 10 +- .../src/test/test_somfy_situo_1_button.lua | 10 +- .../src/test/test_somfy_situo_4_button.lua | 8 +- .../src/test/test_thirdreality_button.lua | 8 +- .../src/test/test_vimar_button.lua | 12 +- .../src/test/test_wallhero_button.lua | 4 +- .../src/test/test_zigbee_button.lua | 24 +- .../src/test/test_zigbee_ecosmart_button.lua | 14 +- .../src/test/test_zunzunbee_8_button.lua | 8 +- ...test_climax_technology_carbon_monoxide.lua | 2 +- .../src/test/test_zigbee_carbon_monoxide.lua | 12 +- .../src/test/test_aqara_contact_sensor.lua | 18 +- .../src/test/test_aurora_contact_sensor.lua | 4 +- .../src/test/test_centralite_multi_sensor.lua | 16 +- .../test/test_contact_temperature_sensor.lua | 8 +- .../src/test/test_ecolink_contact.lua | 8 +- .../src/test/test_ewelink_heiman_sensor.lua | 4 +- .../src/test/test_frient_contact_sensor.lua | 18 +- .../test/test_frient_contact_sensor_2_pro.lua | 22 +- .../test/test_frient_contact_sensor_pro.lua | 22 +- .../src/test/test_frient_vibration_sensor.lua | 20 +- .../src/test/test_orvibo_contact_sensor.lua | 4 +- .../src/test/test_samjin_multi_sensor.lua | 8 +- .../src/test/test_sengled_contact_sensor.lua | 4 +- .../src/test/test_smartsense_multi.lua | 52 +- .../test/test_smartthings_multi_sensor.lua | 26 +- .../src/test/test_third_reality_contact.lua | 4 +- .../test/test_thirdreality_multi_sensor.lua | 6 +- .../src/test/test_zigbee_contact.lua | 18 +- .../src/test/test_zigbee_contact_battery.lua | 6 +- .../src/test/test_zigbee_contact_tyco.lua | 6 +- .../src/test/test_zigbee_accessory_dimmer.lua | 34 +- .../test_zigbee_battery_accessory_dimmer.lua | Bin 29143 -> 29143 bytes .../zigbee-fan/src/test/test_fan_light.lua | 42 +- .../src/test/test_aqara_sensor.lua | 20 +- .../src/test/test_centralite_sensor.lua | 6 +- .../src/test/test_ewelink_sensor.lua | 4 +- .../test/test_frient_air_quality_sensor.lua | 18 +- .../src/test/test_frient_sensor.lua | 14 +- .../src/test/test_heiman_sensor.lua | 4 +- .../src/test/test_humidity_battery_sensor.lua | 8 +- .../src/test/test_humidity_plaid_systems.lua | 18 +- .../src/test/test_humidity_temperature.lua | 10 +- .../test_humidity_temperature_battery.lua | 10 +- .../test/test_humidity_temperature_sensor.lua | 8 +- .../src/test/test_illuminance_sensor.lua | 6 +- .../test/test_illuminance_sensor_aqara.lua | 12 +- .../zigbee-lock/src/test/test_c2o_lock.lua | 16 +- .../src/test/test_generic_lock_migration.lua | 2 +- ..._yale_fingerprint_bad_battery_reporter.lua | 2 +- .../zigbee-lock/src/test/test_zigbee_lock.lua | 56 +- .../test/test_zigbee_lock_code_migration.lua | 10 +- .../src/test/test_zigbee_lock_v10.lua | 48 +- .../src/test/test_zigbee_samsungsds.lua | 112 +- .../test_zigbee_yale-bad-battery-reporter.lua | 2 +- .../test_zigbee_yale-fingerprint-lock.lua | 2 +- .../zigbee-lock/src/test/test_zigbee_yale.lua | 22 +- .../test_all_capabilities_zigbee_motion.lua | 24 +- .../src/test/test_aqara_high_precision.lua | 18 +- .../test/test_aqara_motion_illuminance.lua | 12 +- .../src/test/test_aurora_motion.lua | 12 +- .../src/test/test_battery_voltage_motion.lua | 2 +- .../src/test/test_centralite_motion.lua | 4 +- .../src/test/test_compacta_motion.lua | 4 +- .../src/test/test_frient_motion_sensor.lua | 12 +- .../test/test_frient_motion_sensor2_pet.lua | 14 +- .../test/test_frient_motion_sensor_pro.lua | 22 +- .../src/test/test_gator_motion.lua | 18 +- .../src/test/test_ikea_motion.lua | 14 +- .../src/test/test_samjin_sensor.lua | 4 +- .../src/test/test_sengled_motion.lua | 4 +- .../test/test_smartsense_motion_sensor.lua | 16 +- .../src/test/test_smartthings_motion.lua | 2 +- .../src/test/test_thirdreality_sensor.lua | 10 +- .../src/test/test_zigbee_motion_iris.lua | 4 +- .../src/test/test_zigbee_motion_nyce.lua | 6 +- .../src/test/test_zigbee_motion_orvibo.lua | 12 +- .../test/test_zigbee_plugin_motion_sensor.lua | 8 +- .../src/test/test_zigbee_power_meter.lua | 14 +- .../src/test/test_zigbee_power_meter_1p.lua | 20 +- .../src/test/test_zigbee_power_meter_2p.lua | 16 +- .../src/test/test_zigbee_power_meter_3p.lua | 22 +- ...e_power_meter_consumption_report_sihas.lua | 12 +- .../src/test/test_zigbee_power_meter_ezex.lua | 8 +- .../test/test_zigbee_power_meter_frient.lua | 2 +- .../test/test_aqara_presence_sensor_fp1.lua | 20 +- .../src/test/test_st_arrival_sensor_v1.lua | 16 +- .../src/test/test_zigbee_presence_sensor.lua | 26 +- .../test_frient_zigbee_range_extender.lua | 18 +- .../src/test/test_zigbee_extend.lua | 2 +- .../src/test/test_zigbee_sensor.lua | 66 +- .../src/test/test_frient_siren.lua | 46 +- .../src/test/test_frient_siren_tamper.lua | 42 +- .../zigbee-siren/src/test/test_ozom_siren.lua | 6 +- .../src/test/test_zigbee_siren.lua | 28 +- .../src/test/test_aqara_gas_detector.lua | 34 +- .../src/test/test_aqara_smoke_detector.lua | 22 +- .../src/test/test_frient_heat_detector.lua | 32 +- .../src/test/test_frient_smoke_detector.lua | 36 +- .../src/test/test_zigbee_smoke_detector.lua | 14 +- .../src/test/test_zigbee_sound_sensor.lua | 16 +- .../test/test_all_capability_zigbee_bulb.lua | 28 +- .../src/test/test_aqara_led_bulb.lua | 8 +- .../src/test/test_aqara_light.lua | 16 +- .../src/test/test_aqara_smart_plug.lua | 28 +- .../src/test/test_aqara_smart_plug_t1.lua | 30 +- .../src/test/test_aqara_switch_module.lua | 18 +- .../test_aqara_switch_module_no_power.lua | 16 +- .../src/test/test_aqara_switch_no_power.lua | 32 +- .../src/test/test_aqara_switch_power.lua | 34 +- .../src/test/test_aqara_wall_switch.lua | 22 +- .../src/test/test_aurora_relay.lua | 8 +- .../src/test/test_bad_data_type.lua | 2 +- .../src/test/test_bad_device_kind.lua | 4 +- .../zigbee-switch/src/test/test_cree_bulb.lua | 8 +- .../test/test_duragreen_color_temp_bulb.lua | 8 +- .../test/test_enbrighten_metering_dimmer.lua | 14 +- .../src/test/test_frient_IO_module.lua | 10 +- .../src/test/test_frient_switch.lua | 20 +- .../src/test/test_ge_link_bulb.lua | 14 +- .../src/test/test_hanssem_switch.lua | 50 +- .../src/test/test_inovelli_vzm30_sn.lua | 28 +- .../src/test/test_inovelli_vzm30_sn_child.lua | 12 +- .../test_inovelli_vzm30_sn_preferences.lua | 16 +- .../src/test/test_inovelli_vzm31_sn.lua | 22 +- .../src/test/test_inovelli_vzm31_sn_child.lua | 12 +- .../test_inovelli_vzm31_sn_preferences.lua | 16 +- .../src/test/test_inovelli_vzm32_sn.lua | 30 +- .../src/test/test_inovelli_vzm32_sn_child.lua | 12 +- .../test_inovelli_vzm32_sn_preferences.lua | 12 +- .../src/test/test_jasco_switch.lua | 10 +- .../src/test/test_laisiao_bath_heather.lua | 64 +- .../src/test/test_multi_switch.lua | 10 +- .../src/test/test_multi_switch_no_master.lua | 28 +- .../src/test/test_multi_switch_power.lua | 32 +- .../src/test/test_on_off_zigbee_bulb.lua | 10 +- .../src/test/test_osram_iqbr30_light.lua | 8 +- .../src/test/test_osram_light.lua | 6 +- .../zigbee-switch/src/test/test_rgb_bulb.lua | 10 +- .../zigbee-switch/src/test/test_rgbw_bulb.lua | 14 +- .../test/test_robb_smarrt_2-wire_dimmer.lua | 10 +- .../src/test/test_robb_smarrt_knob_dimmer.lua | 8 +- .../src/test/test_sengled_color_temp_bulb.lua | 6 +- ...sengled_dimmer_bulb_with_motion_sensor.lua | 16 +- .../src/test/test_sinope_dimmer.lua | 18 +- .../src/test/test_sinope_switch.lua | 6 +- .../src/test/test_switch_power.lua | 8 +- .../src/test/test_tuya_multi.lua | 2 +- .../src/test/test_tuya_multi_switch.lua | 10 +- .../src/test/test_wallhero_switch.lua | 48 +- .../src/test/test_white_color_temp_bulb.lua | 6 +- .../src/test/test_yanmi_switch.lua | 24 +- .../src/test/test_zigbee_ezex_switch.lua | 10 +- ...metering_plug_power_consumption_report.lua | 6 +- .../test_zigbee_metering_plug_rexense.lua | 4 +- .../src/test/test_zll_color_temp_bulb.lua | 12 +- .../src/test/test_zll_dimmer.lua | 10 +- .../src/test/test_zll_dimmer_bulb.lua | 14 +- .../src/test/test_zll_rgb_bulb.lua | 258 ++-- .../src/test/test_zll_rgbw_bulb.lua | 20 +- .../src/test/test_aqara_thermostat.lua | 24 +- .../src/test/test_centralite_thermostat.lua | 10 +- .../src/test/test_danfoss_thermostat.lua | 12 +- .../src/test/test_fidure_thermostat.lua | 4 +- .../src/test/test_leviton_rc.lua | 28 +- .../src/test/test_popp_thermostat.lua | 30 +- .../src/test/test_resideo_dt300st_m000.lua | 100 +- .../test/test_sinope_th1300_thermostat.lua | 8 +- .../test/test_sinope_th1400_thermostat.lua | 8 +- .../src/test/test_sinope_thermostat.lua | 10 +- .../test_stelpro_ki_zigbee_thermostat.lua | 36 +- .../src/test/test_stelpro_thermostat.lua | 38 +- .../src/test/test_vimar_thermostat.lua | 38 +- .../src/test/test_zenwithin_thermostat.lua | 22 +- .../src/test/test_zigbee_thermostat.lua | 52 +- .../zigbee-valve/src/test/test_ezex_valve.lua | 26 +- .../src/test/test_sinope_valve.lua | 12 +- .../src/test/test_zigbee_valve.lua | 24 +- .../zigbee-vent/src/test/test_zigbee_vent.lua | 22 +- .../src/test/test_aqara_water_leak_sensor.lua | 8 +- .../test_centralite_water_leak_sensor.lua | 18 +- .../test/test_frient_water_leak_sensor.lua | 18 +- .../src/test/test_leaksmart_water.lua | 14 +- .../test/test_samjin_water_leak_sensor.lua | 14 +- .../test/test_sengled_water_leak_sensor.lua | 4 +- .../src/test/test_sinope_zigbee_water.lua | 18 +- .../test_smartthings_water_leak_sensor.lua | 12 +- .../test_thirdreality_water_leak_sensor.lua | 12 +- .../src/test/test_zigbee_water.lua | 18 +- .../src/test/test_zigbee_water_freeze.lua | 10 +- .../test/test_thirdreality_watering_kit.lua | 32 +- .../test_zigbee_window_shade_battery_ikea.lua | 20 +- ...est_zigbee_window_shade_battery_yoolax.lua | 22 +- ...est_zigbee_window_shade_only_HOPOsmart.lua | 18 +- .../src/test/test_zigbee_window_treatment.lua | 18 +- ..._zigbee_window_treatment_VWSDSTUST120H.lua | 34 +- .../test_zigbee_window_treatment_aqara.lua | 40 +- ...ndow_treatment_aqara_curtain_driver_e1.lua | 30 +- ...ow_treatment_aqara_roller_shade_rotate.lua | 32 +- .../test_zigbee_window_treatment_axis.lua | 34 +- .../test_zigbee_window_treatment_feibit.lua | 28 +- .../test_zigbee_window_treatment_hanssem.lua | 14 +- .../test_zigbee_window_treatment_rooms.lua | 26 +- ...ee_window_treatment_screen_innovations.lua | 28 +- .../test_zigbee_window_treatment_somfy.lua | 38 +- .../test_zigbee_window_treatment_vimar.lua | 26 +- .../src/test/test_aeon_multiwhite_bulb.lua | 36 +- .../src/test/test_aeotec_led_bulb_6.lua | 6 +- .../src/test/test_fibaro_rgbw_controller.lua | 24 +- .../zwave-bulb/src/test/test_zwave_bulb.lua | 20 +- .../src/test/test_zwave_aeotec_minimote.lua | 20 +- .../test/test_zwave_aeotec_nanomote_one.lua | 2 +- .../src/test/test_zwave_button.lua | 12 +- .../src/test/test_zwave_fibaro_button.lua | 2 +- .../src/test/test_zwave_multi_button.lua | 44 +- .../src/test/test_aeon_meter.lua | 8 +- .../src/test/test_aeotec_gen5_meter.lua | 8 +- .../src/test/test_qubino_3_phase_meter.lua | 8 +- .../src/test/test_qubino_smart_meter.lua | 8 +- .../src/test/test_zwave_electric_meter.lua | 6 +- .../src/test/test_zwave_fan_3_speed.lua | 10 +- .../src/test/test_zwave_fan_4_speed.lua | 10 +- .../test_ecolink_garage_door_operator.lua | 22 +- .../src/test/test_mimolite_garage_door.lua | 24 +- .../test/test_zwave_garage_door_opener.lua | 14 +- .../zwave-lock/src/test/test_keywe_lock.lua | 6 +- .../zwave-lock/src/test/test_lock_battery.lua | 10 +- .../zwave-lock/src/test/test_samsung_lock.lua | 10 +- .../zwave-lock/src/test/test_schlage_lock.lua | 16 +- .../zwave-lock/src/test/test_zwave_lock.lua | 48 +- .../test/test_zwave_lock_code_migration.lua | 10 +- .../src/test/test_zwave_mouse_trap.lua | 24 +- .../src/test/test_aeon_multisensor.lua | 4 +- .../src/test/test_aeotec_multisensor_6.lua | 22 +- .../src/test/test_aeotec_multisensor_7.lua | 12 +- .../src/test/test_aeotec_multisensor_gen5.lua | 2 +- .../src/test/test_aeotec_water_sensor.lua | 24 +- .../src/test/test_aeotec_water_sensor_7.lua | 12 +- .../src/test/test_enerwave_motion_sensor.lua | 6 +- .../src/test/test_everpsring_sp817.lua | 4 +- .../src/test/test_everspring_PIR_sensor.lua | 12 +- .../src/test/test_everspring_ST814.lua | 2 +- .../test_everspring_illuminance_sensor.lua | 2 +- .../test_everspring_motion_light_sensor.lua | 2 +- .../test_ezmultipli_multipurpose_sensor.lua | 10 +- .../test/test_fibaro_door_window_sensor.lua | 14 +- .../test/test_fibaro_door_window_sensor_1.lua | 22 +- .../test/test_fibaro_door_window_sensor_2.lua | 20 +- ...ro_door_window_sensor_with_temperature.lua | 20 +- .../src/test/test_fibaro_flood_sensor.lua | 24 +- .../src/test/test_fibaro_flood_sensor_zw5.lua | 2 +- .../src/test/test_fibaro_motion_sensor.lua | 26 +- .../test/test_fibaro_motion_sensor_zw5.lua | 4 +- .../src/test/test_firmware_version.lua | 6 +- .../src/test/test_generic_sensor.lua | 118 +- .../test_glentronics_water_leak_sensor.lua | 14 +- .../src/test/test_homeseer_multi_sensor.lua | 10 +- .../src/test/test_no_wakeup_poll.lua | 2 +- .../src/test/test_sensative_strip.lua | 6 +- .../test_smartthings_water_leak_sensor.lua | 24 +- .../src/test/test_v1_contact_event.lua | 6 +- .../src/test/test_vision_motion_detector.lua | 12 +- .../src/test/test_zooz_4_in_1_sensor.lua | 20 +- .../test/test_zwave_motion_light_sensor.lua | 22 +- .../test_zwave_motion_temp_light_sensor.lua | 18 +- .../src/test/test_zwave_sensor.lua | 54 +- .../src/test/test_zwave_water_sensor.lua | 24 +- .../zwave-siren/src/test/test_aeon_siren.lua | 20 +- .../src/test/test_aeotec_doorbell_siren.lua | 160 +- .../src/test/test_ecolink_wireless_siren.lua | 30 +- .../src/test/test_fortrezz_siren.lua | 8 +- .../src/test/test_philio_sound_siren.lua | 36 +- .../src/test/test_utilitech_siren.lua | 4 +- .../zwave-siren/src/test/test_yale_siren.lua | 18 +- .../src/test/test_zipato_siren.lua | 10 +- .../test/test_zwave_multifunctional-siren.lua | 10 +- .../test/test_zwave_notification_siren.lua | 8 +- .../zwave-siren/src/test/test_zwave_siren.lua | 28 +- .../src/test/test_zwave_sound_sensor.lua | 6 +- .../src/test/test_fibaro_co_sensor_zw5.lua | 28 +- .../src/test/test_fibaro_smoke_sensor.lua | 6 +- .../src/test/test_zwave_alarm_v1.lua | 14 +- .../src/test/test_zwave_co_detector.lua | 14 +- .../src/test/test_zwave_smoke_detector.lua | 30 +- .../src/test/test_aeon_smart_strip.lua | 32 +- .../src/test/test_aeotec_dimmer_switch.lua | 22 +- ..._aeotec_dual_nano_switch_configuration.lua | 2 +- .../test/test_aeotec_heavy_duty_switch.lua | 36 +- ...t_aeotec_metering_switch_configuration.lua | 2 +- .../src/test/test_aeotec_nano_dimmer.lua | 22 +- .../test_aeotec_nano_dimmer_preferences.lua | 2 +- .../src/test/test_aeotec_smart_switch.lua | 4 +- .../test/test_aeotec_smart_switch_7_eu.lua | 12 +- .../test/test_aeotec_smart_switch_7_us.lua | 12 +- .../test/test_aeotec_smart_switch_gen5.lua | 2 +- .../src/test/test_dawon_smart_plug.lua | 4 +- .../src/test/test_dawon_wall_smart_switch.lua | 20 +- .../src/test/test_eaton_5_scene_keypad.lua | 26 +- .../src/test/test_eaton_accessory_dimmer.lua | 16 +- .../src/test/test_eaton_anyplace_switch.lua | 12 +- .../src/test/test_eaton_rf_dimmer.lua | 2 +- .../src/test/test_ecolink_switch.lua | 18 +- .../src/test/test_fibaro_double_switch.lua | 20 +- .../src/test/test_fibaro_single_switch.lua | 42 +- .../src/test/test_fibaro_wall_plug_eu.lua | 2 +- ...test_fibaro_wall_plug_uk_configuration.lua | 2 +- .../src/test/test_fibaro_wall_plug_us.lua | 16 +- .../test_fibaro_walli_dimmer_preferences.lua | 14 +- .../test/test_fibaro_walli_double_switch.lua | 30 +- ...fibaro_walli_double_switch_preferences.lua | 12 +- .../src/test/test_generic_zwave_device1.lua | 16 +- ...go_control_plug_in_switch_configuraton.lua | 2 +- .../src/test/test_honeywell_dimmer.lua | 2 +- .../test_inovelli_2_channel_smart_plug.lua | 36 +- .../src/test/test_inovelli_button.lua | 8 +- .../src/test/test_inovelli_dimmer.lua | 14 +- .../src/test/test_inovelli_dimmer_led.lua | 4 +- .../test_inovelli_dimmer_power_energy.lua | 18 +- .../test/test_inovelli_dimmer_preferences.lua | 10 +- .../src/test/test_inovelli_dimmer_scenes.lua | 22 +- .../src/test/test_inovelli_vzw32_sn.lua | 14 +- .../src/test/test_inovelli_vzw32_sn_child.lua | 12 +- .../test_inovelli_vzw32_sn_preferences.lua | 10 +- .../src/test/test_multi_metering_switch.lua | 36 +- .../src/test/test_multichannel_device.lua | 118 +- .../test_popp_outdoor_plug_configuration.lua | 2 +- .../src/test/test_qubino_din_dimmer.lua | 24 +- .../test_qubino_din_dimmer_preferences.lua | 14 +- .../test_qubino_flush_1_relay_preferences.lua | 10 +- ...test_qubino_flush_1d_relay_preferences.lua | 6 +- .../src/test/test_qubino_flush_2_relay.lua | 34 +- .../test_qubino_flush_2_relay_preferences.lua | 10 +- .../src/test/test_qubino_flush_dimmer.lua | 24 +- ..._qubino_flush_dimmer_0_10V_preferences.lua | 14 +- .../test_qubino_flush_dimmer_preferences.lua | 18 +- .../test_qubino_mini_dimmer_preferences.lua | 16 +- ...t_qubino_temperature_sensor_with_power.lua | 18 +- ...ubino_temperature_sensor_without_power.lua | 12 +- .../test_shelly_multi_metering_switch.lua | 34 +- .../zwave-switch/src/test/test_wyfy_touch.lua | 22 +- .../test/test_wyfy_touch_configuration.lua | 2 +- .../src/test/test_zooz_double_plug.lua | 30 +- .../src/test/test_zooz_power_strip.lua | 66 +- .../test/test_zooz_zen_30_dimmer_relay.lua | 80 +- ...t_zooz_zen_30_dimmer_relay_preferences.lua | 2 +- .../test/test_zwave_dimmer_power_energy.lua | 18 +- .../src/test/test_zwave_dual_switch.lua | 36 +- .../test/test_zwave_dual_switch_migration.lua | 4 +- .../src/test/test_zwave_switch.lua | 20 +- .../src/test/test_zwave_switch_battery.lua | 6 +- .../test/test_zwave_switch_electric_meter.lua | 8 +- .../test/test_zwave_switch_energy_meter.lua | 10 +- .../src/test/test_zwave_switch_level.lua | 4 +- .../test/test_zwave_switch_power_meter.lua | 8 +- .../test/test_aeotec_radiator_thermostat.lua | 16 +- .../src/test/test_ct100_thermostat.lua | 20 +- .../src/test/test_fibaro_heat_controller.lua | 22 +- .../test/test_popp_radiator_thermostat.lua | 12 +- .../src/test/test_qubino_flush_thermostat.lua | 30 +- .../src/test/test_stelpro_ki_thermostat.lua | 20 +- .../test/test_thermostat_heating_battery.lua | 28 +- .../src/test/test_zwave_thermostat.lua | 44 +- .../src/test/test_inverse.valve.lua | 12 +- .../zwave-valve/src/test/test_zwave_valve.lua | 16 +- .../test_zwave_virtual_momentary_switch.lua | 14 +- .../src/test/test_fibaro_roller_shutter.lua | 60 +- .../src/test/test_qubino_flush_shutter.lua | 50 +- .../test/test_zwave_aeotec_nano_shutter.lua | 24 +- .../test_zwave_iblinds_window_treatment.lua | 30 +- .../test_zwave_springs_window_treatment.lua | 2 +- .../src/test/test_zwave_window_treatment.lua | 36 +- tools/update_min_tags.py | 100 ++ 482 files changed, 5311 insertions(+), 5153 deletions(-) mode change 100755 => 100644 drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-switch/src/test/test_sinope_switch.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_only_HOPOsmart.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_axis.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua mode change 100755 => 100644 drivers/SmartThings/zwave-thermostat/src/test/test_aeotec_radiator_thermostat.lua mode change 100755 => 100644 drivers/SmartThings/zwave-thermostat/src/test/test_popp_radiator_thermostat.lua create mode 100644 tools/update_min_tags.py diff --git a/.github/workflows/jenkins-driver-tests.yml b/.github/workflows/jenkins-driver-tests.yml index ec96330d39..04129fd688 100644 --- a/.github/workflows/jenkins-driver-tests.yml +++ b/.github/workflows/jenkins-driver-tests.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: version: - [ 60 ] + [ 59, 60 ] runs-on: ubuntu-latest steps: 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 99d56a5869..0321d956ce 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua @@ -91,7 +91,7 @@ test.register_coroutine_test( assert(component_to_endpoint_map["cookSurfaceTwo"] == COOK_SURFACE_TWO_ENDPOINT, "Cook Surface Two Endpoint must be 3") end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -157,7 +157,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -192,7 +192,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua b/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua index 0cfeb19f07..6819815506 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua @@ -103,7 +103,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -171,7 +171,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -239,7 +239,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -307,7 +307,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -386,7 +386,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -450,7 +450,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -490,7 +490,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -549,7 +549,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) 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 ca85e49214..d176352f80 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_extractor_hood.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_extractor_hood.lua @@ -176,7 +176,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -250,7 +250,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -339,7 +339,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) test.register_message_test( @@ -452,7 +452,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -490,7 +490,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -531,7 +531,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -592,7 +592,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -653,7 +653,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -666,7 +666,7 @@ test.register_coroutine_test( end, { test_init = test_init_onoff, - min_api_version = 19 + min_api_version = 17 } ) @@ -692,7 +692,7 @@ test.register_coroutine_test( end, { test_init = test_init_onoff, - min_api_version = 19 + min_api_version = 17 } ) @@ -716,7 +716,7 @@ test.register_coroutine_test( end, { test_init = test_init_onoff, - min_api_version = 19 + min_api_version = 17 } ) 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 5c01970d7b..ba1440af85 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua @@ -102,7 +102,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -170,7 +170,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -238,7 +238,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -306,7 +306,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -385,7 +385,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -449,7 +449,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -489,7 +489,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -548,7 +548,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -591,7 +591,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 a0df2e229b..6aba845a27 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_laundry_washer.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_laundry_washer.lua @@ -135,7 +135,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -178,7 +178,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 4df43c94fc..7e59d7925f 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 @@ -458,7 +458,7 @@ test.register_coroutine_test( end, { test_init = test_init_dishwasher, - min_api_version = 19 + min_api_version = 17 } ) @@ -500,7 +500,7 @@ test.register_coroutine_test( end, { test_init = test_init_dishwasher, - min_api_version = 19 + min_api_version = 17 } ) @@ -542,7 +542,7 @@ test.register_coroutine_test( end, { test_init = test_init_washer, - min_api_version = 19 + min_api_version = 17 } ) @@ -584,7 +584,7 @@ test.register_coroutine_test( end, { test_init = test_init_washer, - min_api_version = 19 + min_api_version = 17 } ) @@ -626,7 +626,7 @@ test.register_coroutine_test( end, { test_init = test_init_dryer, - min_api_version = 19 + min_api_version = 17 } ) @@ -668,7 +668,7 @@ test.register_coroutine_test( end, { test_init = test_init_dryer, - min_api_version = 19 + min_api_version = 17 } ) @@ -710,7 +710,7 @@ test.register_coroutine_test( end, { test_init = test_init_oven, - min_api_version = 19 + min_api_version = 17 } ) @@ -752,7 +752,7 @@ test.register_coroutine_test( end, { test_init = test_init_oven, - min_api_version = 19 + min_api_version = 17 } ) @@ -794,7 +794,7 @@ test.register_coroutine_test( end, { test_init = test_init_refrigerator, - min_api_version = 19 + min_api_version = 17 } ) @@ -836,7 +836,7 @@ test.register_coroutine_test( end, { test_init = test_init_refrigerator, - min_api_version = 19 + min_api_version = 17 } ) @@ -878,7 +878,7 @@ test.register_coroutine_test( end, { test_init = test_init_refrigerator, - min_api_version = 19 + min_api_version = 17 } ) @@ -920,7 +920,7 @@ test.register_coroutine_test( end, { test_init = test_init_refrigerator, - min_api_version = 19 + min_api_version = 17 } ) 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 14402c5157..cf69721714 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua @@ -174,7 +174,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -258,7 +258,7 @@ test.register_message_test( test_init() init_supported_microwave_oven_modes() end, - min_api_version = 19 + min_api_version = 17 } ) @@ -338,7 +338,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -453,7 +453,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -479,7 +479,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_oven.lua b/drivers/SmartThings/matter-appliance/src/test/test_oven.lua index 8c146f2bd5..905f34d666 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_oven.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_oven.lua @@ -143,7 +143,7 @@ test.register_coroutine_test( "Cook Surface Two Endpoint must be 6") end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -228,7 +228,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -287,7 +287,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -371,7 +371,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -411,7 +411,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -437,7 +437,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -475,7 +475,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -513,7 +513,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -535,7 +535,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -557,7 +557,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua b/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua index 3158f69298..1210819d51 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua @@ -147,7 +147,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -206,7 +206,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -249,7 +249,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -308,7 +308,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -351,7 +351,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-button/src/test/test_matter_button.lua b/drivers/SmartThings/matter-button/src/test/test_matter_button.lua index 0c703c642d..dfc32bc7cb 100644 --- a/drivers/SmartThings/matter-button/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-button/src/test/test_matter_button.lua @@ -71,7 +71,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -109,7 +109,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -148,7 +148,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -196,7 +196,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -220,7 +220,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -244,7 +244,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -268,7 +268,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -308,7 +308,7 @@ test.register_message_test( }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -348,7 +348,7 @@ test.register_message_test( }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -373,7 +373,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) -- run the tests diff --git a/drivers/SmartThings/matter-button/src/test/test_matter_button_parent_child.lua b/drivers/SmartThings/matter-button/src/test/test_matter_button_parent_child.lua index 64396e46f4..01d3b103c7 100644 --- a/drivers/SmartThings/matter-button/src/test/test_matter_button_parent_child.lua +++ b/drivers/SmartThings/matter-button/src/test/test_matter_button_parent_child.lua @@ -136,7 +136,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -170,7 +170,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -213,7 +213,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -237,7 +237,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -261,7 +261,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -295,7 +295,7 @@ test.register_message_test( }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -329,7 +329,7 @@ test.register_message_test( }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -354,7 +354,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) -- run the tests diff --git a/drivers/SmartThings/matter-button/src/test/test_matter_multi_button.lua b/drivers/SmartThings/matter-button/src/test/test_matter_multi_button.lua index 2549698c61..91f3a06494 100644 --- a/drivers/SmartThings/matter-button/src/test/test_matter_multi_button.lua +++ b/drivers/SmartThings/matter-button/src/test/test_matter_multi_button.lua @@ -136,7 +136,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -169,7 +169,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -194,7 +194,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.held({state_change = true}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -243,7 +243,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -283,7 +283,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button4", button_attr.double({state_change = true}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -311,7 +311,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -339,7 +339,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -377,7 +377,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -416,7 +416,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -464,7 +464,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -488,7 +488,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -512,7 +512,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -536,7 +536,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -576,7 +576,7 @@ test.register_message_test( }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -616,7 +616,7 @@ test.register_message_test( }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -641,7 +641,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -686,7 +686,7 @@ test.register_message_test( -- no double event }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -744,7 +744,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) -- run the tests 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 a9c652f661..329daa6bd7 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua @@ -104,7 +104,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) test.register_coroutine_test( @@ -162,7 +162,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -179,7 +179,7 @@ test.register_coroutine_test( capabilities.powerMeter.power({ value = 30.0, unit = "W" }))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -196,7 +196,7 @@ test.register_coroutine_test( capabilities.powerMeter.power({ value = 30.0, unit = "W" }))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -301,7 +301,7 @@ test.register_coroutine_test( test_init = function() test_init() end, - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-energy/src/test/test_evse.lua b/drivers/SmartThings/matter-energy/src/test/test_evse.lua index 0aeed44209..5614b0be18 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_evse.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_evse.lua @@ -120,7 +120,7 @@ test.register_coroutine_test( "Device Energy Management Endpoint must be 3") end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -150,7 +150,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -201,7 +201,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -225,7 +225,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -248,7 +248,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -271,7 +271,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -294,7 +294,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -317,7 +317,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -340,7 +340,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -364,7 +364,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -438,7 +438,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -541,7 +541,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua b/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua index ba5d6d6101..f3b3b6c700 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua @@ -118,7 +118,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -152,7 +152,7 @@ test.register_coroutine_test( test_init = function() test_init() end, - min_api_version = 19 + min_api_version = 17 } ) 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 0e248624d9..8e4e3c4e9a 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua @@ -124,7 +124,7 @@ test.register_coroutine_test( capabilities.powerMeter.power({ value = 35.0, unit = "W" }))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -172,7 +172,7 @@ test.register_coroutine_test( test_init = function() test_init() end, - min_api_version = 19 + min_api_version = 17 } ) @@ -197,7 +197,7 @@ test.register_coroutine_test( 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, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-hrap/src/test/test_thread_border_router_network.lua b/drivers/SmartThings/matter-hrap/src/test/test_thread_border_router_network.lua index f4d2450c58..af8315375f 100644 --- a/drivers/SmartThings/matter-hrap/src/test/test_thread_border_router_network.lua +++ b/drivers/SmartThings/matter-hrap/src/test/test_thread_border_router_network.lua @@ -108,7 +108,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -143,7 +143,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -191,7 +191,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -213,7 +213,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -230,7 +230,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -247,7 +247,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -356,7 +356,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua index fa55d37774..57d7e2b71e 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua @@ -95,7 +95,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -119,7 +119,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -141,7 +141,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -163,7 +163,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -185,7 +185,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -211,7 +211,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -310,7 +310,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -327,7 +327,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua index 12872290e0..bb7454d27d 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua @@ -93,7 +93,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -104,7 +104,7 @@ test.register_coroutine_test( mock_device_level:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua index da6a13ecad..2477e2745b 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua @@ -71,7 +71,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -95,7 +95,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -118,7 +118,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -141,7 +141,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -164,7 +164,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -187,7 +187,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -212,7 +212,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -239,7 +239,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -308,7 +308,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -348,7 +348,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -363,7 +363,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua index 135cb84c37..18bf34d3b9 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua @@ -121,7 +121,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "base-lock" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -149,7 +149,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "base-lock-batteryLevel" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -159,7 +159,7 @@ test.register_coroutine_test( end, { test_init = test_init_no_battery, - min_api_version = 19 + min_api_version = 17 } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua index 16787f10e5..8bb78f3fce 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua @@ -94,7 +94,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua index 6bfc5e5bfb..a0a603c6b3 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua @@ -225,7 +225,7 @@ test.register_coroutine_test( expect_reload_all_codes_messages(mock_device) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -241,7 +241,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -265,7 +265,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -289,7 +289,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -313,7 +313,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -335,7 +335,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -360,7 +360,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -375,7 +375,7 @@ test.register_coroutine_test( expect_reload_all_codes_messages(mock_device) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -420,7 +420,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -458,7 +458,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -564,7 +564,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -613,7 +613,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -673,7 +673,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -708,7 +708,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) test.register_coroutine_test( @@ -742,7 +742,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -784,7 +784,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -827,7 +827,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -906,7 +906,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua index 143c04763e..72d905d1db 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua @@ -145,7 +145,7 @@ test.register_coroutine_test( expect_kick_off_cota_process(mock_device) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -162,7 +162,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -179,7 +179,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -214,7 +214,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -238,7 +238,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -340,7 +340,7 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -438,7 +438,7 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -529,7 +529,7 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -589,7 +589,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -629,7 +629,7 @@ test.register_coroutine_test( test.mock_time.advance_time(2) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -710,7 +710,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -745,7 +745,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -753,7 +753,7 @@ test.register_coroutine_test( "Delay setting COTA cred if another cred is already being set.", function() end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index 3ac725ea9b..cb22abdf7d 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -361,7 +361,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {"batteryLevel"}}} }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -396,7 +396,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {"battery"}}} }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -431,7 +431,7 @@ test.register_coroutine_test( end, { test_init = test_init_unlatch, - min_api_version = 19 + min_api_version = 17 } ) @@ -467,7 +467,7 @@ test.register_coroutine_test( end, { test_init = test_init_unlatch, - min_api_version = 19 + min_api_version = 17 } ) @@ -503,7 +503,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin, - min_api_version = 19 + min_api_version = 17 } ) @@ -540,7 +540,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin_schedule_unlatch, - min_api_version = 19 + min_api_version = 17 } ) @@ -619,7 +619,7 @@ test.register_coroutine_test( end, { test_init = test_init_modular, - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua index 56f6da5e41..f040c2b26e 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua @@ -106,7 +106,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -157,7 +157,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -178,7 +178,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -202,7 +202,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -226,7 +226,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -248,7 +248,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -270,7 +270,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -292,7 +292,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -381,7 +381,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 19cc296e34..3154e18cbd 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -115,7 +115,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -166,7 +166,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -186,7 +186,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -206,7 +206,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -226,7 +226,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -246,7 +246,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -266,7 +266,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -286,7 +286,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -357,7 +357,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -438,7 +438,7 @@ function() test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -455,7 +455,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -475,7 +475,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -494,7 +494,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -515,7 +515,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -539,7 +539,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -561,7 +561,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -583,7 +583,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -605,7 +605,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -631,7 +631,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -730,7 +730,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -957,7 +957,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -974,7 +974,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1043,7 +1043,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1070,7 +1070,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1114,7 +1114,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1163,7 +1163,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1190,7 +1190,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1256,7 +1256,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1283,7 +1283,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1349,7 +1349,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1376,7 +1376,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1441,7 +1441,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1470,7 +1470,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1526,7 +1526,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1589,7 +1589,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1645,7 +1645,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1701,7 +1701,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1730,7 +1730,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1784,7 +1784,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1813,7 +1813,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1885,7 +1885,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1914,7 +1914,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1985,7 +1985,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -2014,7 +2014,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -2064,7 +2064,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -2096,7 +2096,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua index 04d051b93e..166fa7110a 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua @@ -1,682 +1,682 @@ --- Copyright 2023 SmartThings --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. - -local test = require "integration_test" -local capabilities = require "st.capabilities" -local t_utils = require "integration_test.utils" -local clusters = require "st.matter.clusters" -local cluster_base = require "st.matter.cluster_base" -local DoorLock = clusters.DoorLock -local OctetString1 = require "st.matter.data_types.OctetString1" - -local enabled_optional_component_capability_pairs = {{ - "main", - { - capabilities.lockUsers.ID, - capabilities.lockSchedules.ID, - capabilities.lockAliro.ID - } -}} -local mock_device = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition( - "lock-modular.yml", - {enabled_optional_capabilities = enabled_optional_component_capability_pairs} - ), - manufacturer_info = { - vendor_id = 0x135D, - product_id = 0x00C1, - }, - endpoints = { - { - endpoint_id = 0, - clusters = { - { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, - }, - device_types = { - { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode - } - }, - { - endpoint_id = 1, - clusters = { - { - cluster_id = DoorLock.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = 0x2510, -- WDSCH & YDSCH & USR & ALIRO - } - }, - device_types = { - { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock - } - } - } -}) - -local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} -local function test_init() - test.disable_startup_messages() - test.mock_device.add_test_device(mock_device) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) - ) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) - local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) - subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroReaderVerificationKey:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroReaderGroupIdentifier:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroReaderGroupSubIdentifier:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroGroupResolvingKey:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroBLEAdvertisingVersion:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.NumberOfAliroEndpointKeysSupported:subscribe(mock_device)) - subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) - subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) - subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) - subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) - test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) - - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) - ) - mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -end - -test.set_test_init_function(test_init) - -test.register_coroutine_test( - "Handle received AliroReaderVerificationKey from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.AliroReaderVerificationKey:build_test_report_data( - mock_device, 1, - "\x04\xA9\xCB\xE4\x18\xEB\x09\x66\x16\x43\xE2\xA4\xA8\x46\xB8\xED\xFE\x27\x86\x98\x30\x2E\x9F\xB4\x3E\x9B\xFF\xD3\xE3\x10\xCC\x2C\x2C\x7F\xF4\x02\xE0\x6E\x40\xEA\x3C\xE1\x29\x43\x52\x73\x36\x68\x3F\xC5\xB1\xCB\x0C\x6A\x7C\x3F\x0B\x5A\xFF\x78\x35\xDF\x21\xC6\x24" - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.readerVerificationKey( - "04a9cbe418eb09661643e2a4a846b8edfe278698302e9fb43e9bffd3e310cc2c2c7ff402e06e40ea3ce12943527336683fc5b1cb0c6a7c3f0b5aff7835df21c624", - {visibility = {displayed = false}}) - ) - ) - end -) - -test.register_coroutine_test( - "Handle received AliroReaderGroupIdentifier from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.AliroReaderGroupIdentifier:build_test_report_data( - mock_device, 1, - "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8" - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.readerGroupIdentifier( - "e24f1b205ba923b32cd13dc009e993a8", - {visibility = {displayed = false}}) - ) - ) - end -) - -test.register_coroutine_test( - "Handle received AliroExpeditedTransactionSupportedProtocolVersions from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions:build_test_report_data( - mock_device, 1, - {OctetString1("\x00\x09"), OctetString1("\x01\x00")} - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.expeditedTransactionProtocolVersions( - {"0.9", "1.0"}, - {visibility = {displayed = false}}) - ) - ) - end -) - -test.register_coroutine_test( - "Handle received AliroSupportedBLEUWBProtocolVersions from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions:build_test_report_data( - mock_device, 1, - {OctetString1("\x00\x09"), OctetString1("\x01\x00")} - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.bleUWBProtocolVersions( - {"0.9", "1.0"}, - {visibility = {displayed = false}}) - ) - ) - end -) - -test.register_coroutine_test( - "Handle received AliroReaderVerificationKey from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported:build_test_report_data( - mock_device, 1, - 35 - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.maxCredentialIssuerKeys( - 35, - {visibility = {displayed = false}}) - ) - ) - end -) - -test.register_coroutine_test( - "Handle received AliroGroupResolvingKey from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.AliroGroupResolvingKey:build_test_report_data( - mock_device, 1, - "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8" - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.groupResolvingKey( - "e24f1b205ba923b32cd13dc009e993a8", - {visibility = {displayed = false}}) - ) - ) - end -) - -test.register_coroutine_test( - "Handle received AliroBLEAdvertisingVersion from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.AliroBLEAdvertisingVersion:build_test_report_data( - mock_device, 1, - 1 - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.bleAdvertisingVersion( - "1", - {visibility = {displayed = false}}) - ) - ) - end -) - -test.register_coroutine_test( - "Handle received NumberOfAliroEndpointKeysSupported from Matter device.", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.attributes.NumberOfAliroEndpointKeysSupported:build_test_report_data( - mock_device, 1, - 10 - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.maxEndpointKeys( - 10, - {visibility = {displayed = false}}) - ) - ) - end -) - -test.register_coroutine_test( - "Handle Set Card Id command received from SmartThings.", - function() - test.socket.capability:__queue_receive( - { - mock_device.id, - { - capability = capabilities.lockAliro.ID, - command = "setCardId", - args = {"3icub18c8pr00"} - }, - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.cardId("3icub18c8pr00", {visibility = {displayed = false}}) - ) - ) - end -) - -test.register_coroutine_test( - "Handle Set Reader Config command received from SmartThings.", - function() - test.socket.capability:__queue_receive( - { - mock_device.id, - { - capability = capabilities.lockAliro.ID, - command = "setReaderConfig", - args = { - "1a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac", - "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", - "e24f1b205ba923b32cd13dc009e993a8", - nil - } - }, - } - ) - test.socket.matter:__expect_send( - { - mock_device.id, - DoorLock.server.commands.SetAliroReaderConfig( - mock_device, 1, -- endpoint - "\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC", - "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", - "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8", - nil - ), - } - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.server.commands.SetAliroReaderConfig:build_test_command_response( - mock_device, 1, - DoorLock.types.DlStatus.SUCCESS -- status - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.commandResult( - {commandName="setReaderConfig", statusCode="success"}, - {state_change=true, visibility={displayed=false}} - ) - ) - ) - end -) - -test.register_coroutine_test( - "Handle Set Endpoint Key command and Clear Endpoint Key command received from SmartThings.", - function() - test.socket.capability:__queue_receive( - { - mock_device.id, - { - capability = capabilities.lockAliro.ID, - command = "setEndpointKey", - args = { - 0, - "vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", - "nonEvictableEndpointKey", - "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", - "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2" - } - }, - } - ) - test.socket.matter:__expect_send( - { - mock_device.id, - DoorLock.server.commands.SetCredential( - mock_device, 1, -- endpoint - DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type - DoorLock.types.CredentialStruct( - { - credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY, - credential_index = 1 - } - ), -- credential - "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", -- credential_data - nil, -- user_index - nil, -- user_status - DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type - ), - } - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.client.commands.SetCredentialResponse:build_test_command_response( - mock_device, 1, - DoorLock.types.DlStatus.SUCCESS, -- status - 1, -- user_index - 2 -- next_credential_index - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockUsers.users( - {{userIndex=1, userType="adminMember"}}, - {visibility={displayed=false}} - ) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.credentials( - {{ - keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", - keyIndex=1, - keyType="nonEvictableEndpointKey", - userIndex=1 - }}, - {visibility={displayed=false}} - ) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.commandResult( - { - commandName="setEndpointKey", - keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", - requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", - statusCode="success", - userIndex=1 - }, - {state_change=true, visibility={displayed=false}} - ) - ) - ) - test.wait_for_events() - test.socket.capability:__queue_receive( - { - mock_device.id, - { - capability = capabilities.lockAliro.ID, - command = "clearEndpointKey", - args = {1, "vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", "nonEvictableEndpointKey"} - }, - } - ) - test.socket.matter:__expect_send( - { - mock_device.id, - DoorLock.server.commands.ClearCredential( - mock_device, 1, -- endpoint - DoorLock.types.CredentialStruct( - {credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY, credential_index = 1} - ) - ), - } - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.server.commands.ClearCredential:build_test_command_response( - mock_device, 1 - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.credentials({}, {visibility={displayed=false}}) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockUsers.users({}, {visibility={displayed=false}}) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockSchedules.weekDaySchedules({}, {visibility={displayed=false}}) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockSchedules.yearDaySchedules({}, {visibility={displayed=false}}) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.commandResult( - { - commandName="clearEndpointKey", - keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", - statusCode="success", - userIndex=1 - }, - {state_change=true, visibility={displayed=false}} - ) - ) - ) - end -) - -test.register_coroutine_test( - "Handle Set Issuer Key command and Clear Issuer Key command received from SmartThings.", - function() - test.socket.capability:__queue_receive( - { - mock_device.id, - { - capability = capabilities.lockAliro.ID, - command = "setIssuerKey", - args = { - 0, - "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", - "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2" - } - }, - } - ) - test.socket.matter:__expect_send( - { - mock_device.id, - DoorLock.server.commands.SetCredential( - mock_device, 1, -- endpoint - DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type - DoorLock.types.CredentialStruct( - { - credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, - credential_index = 1 - } - ), -- credential - "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", -- credential_data - nil, -- user_index - nil, -- user_status - DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type - ), - } - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.client.commands.SetCredentialResponse:build_test_command_response( - mock_device, 1, - DoorLock.types.DlStatus.SUCCESS, -- status - 1, -- user_index - 2 -- next_credential_index - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockUsers.users( - {{userIndex=1, userType="adminMember"}}, - {visibility={displayed=false}} - ) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.credentials( - {{ - keyIndex=1, - keyType="issuerKey", - userIndex=1 - }}, - {visibility={displayed=false}} - ) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.commandResult( - { - commandName="setIssuerKey", - requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", - statusCode="success", - userIndex=1 - }, - {state_change=true, visibility={displayed=false}} - ) - ) - ) - test.wait_for_events() - test.socket.capability:__queue_receive( - { - mock_device.id, - { - capability = capabilities.lockAliro.ID, - command = "clearIssuerKey", - args = {1, "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2"} - }, - } - ) - test.socket.matter:__expect_send( - { - mock_device.id, - DoorLock.server.commands.ClearCredential( - mock_device, 1, -- endpoint - DoorLock.types.CredentialStruct( - {credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, credential_index = 1} - ) - ), - } - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - DoorLock.server.commands.ClearCredential:build_test_command_response( - mock_device, 1 - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.credentials({}, {visibility={displayed=false}}) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockUsers.users({}, {visibility={displayed=false}}) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockSchedules.weekDaySchedules({}, {visibility={displayed=false}}) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockSchedules.yearDaySchedules({}, {visibility={displayed=false}}) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAliro.commandResult( - { - commandName="clearIssuerKey", - requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", - statusCode="success", - userIndex=1 - }, - {state_change=true, visibility={displayed=false}} - ) - ) - ) - end -) - -test.run_registered_tests() +-- Copyright 2023 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" +local DoorLock = clusters.DoorLock +local OctetString1 = require "st.matter.data_types.OctetString1" + +local enabled_optional_component_capability_pairs = {{ + "main", + { + capabilities.lockUsers.ID, + capabilities.lockSchedules.ID, + capabilities.lockAliro.ID + } +}} +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition( + "lock-modular.yml", + {enabled_optional_capabilities = enabled_optional_component_capability_pairs} + ), + manufacturer_info = { + vendor_id = 0x135D, + product_id = 0x00C1, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x2510, -- WDSCH & YDSCH & USR & ALIRO + } + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} +local function test_init() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) + subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroReaderVerificationKey:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroReaderGroupIdentifier:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroReaderGroupSubIdentifier:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroGroupResolvingKey:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.AliroBLEAdvertisingVersion:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported:subscribe(mock_device)) + subscribe_request:merge(DoorLock.attributes.NumberOfAliroEndpointKeysSupported:subscribe(mock_device)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) + subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) + subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) + subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) + test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) + ) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle received AliroReaderVerificationKey from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroReaderVerificationKey:build_test_report_data( + mock_device, 1, + "\x04\xA9\xCB\xE4\x18\xEB\x09\x66\x16\x43\xE2\xA4\xA8\x46\xB8\xED\xFE\x27\x86\x98\x30\x2E\x9F\xB4\x3E\x9B\xFF\xD3\xE3\x10\xCC\x2C\x2C\x7F\xF4\x02\xE0\x6E\x40\xEA\x3C\xE1\x29\x43\x52\x73\x36\x68\x3F\xC5\xB1\xCB\x0C\x6A\x7C\x3F\x0B\x5A\xFF\x78\x35\xDF\x21\xC6\x24" + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.readerVerificationKey( + "04a9cbe418eb09661643e2a4a846b8edfe278698302e9fb43e9bffd3e310cc2c2c7ff402e06e40ea3ce12943527336683fc5b1cb0c6a7c3f0b5aff7835df21c624", + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroReaderGroupIdentifier from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroReaderGroupIdentifier:build_test_report_data( + mock_device, 1, + "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8" + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.readerGroupIdentifier( + "e24f1b205ba923b32cd13dc009e993a8", + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroExpeditedTransactionSupportedProtocolVersions from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions:build_test_report_data( + mock_device, 1, + {OctetString1("\x00\x09"), OctetString1("\x01\x00")} + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.expeditedTransactionProtocolVersions( + {"0.9", "1.0"}, + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroSupportedBLEUWBProtocolVersions from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions:build_test_report_data( + mock_device, 1, + {OctetString1("\x00\x09"), OctetString1("\x01\x00")} + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.bleUWBProtocolVersions( + {"0.9", "1.0"}, + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroReaderVerificationKey from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported:build_test_report_data( + mock_device, 1, + 35 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.maxCredentialIssuerKeys( + 35, + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroGroupResolvingKey from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroGroupResolvingKey:build_test_report_data( + mock_device, 1, + "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8" + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.groupResolvingKey( + "e24f1b205ba923b32cd13dc009e993a8", + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received AliroBLEAdvertisingVersion from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.AliroBLEAdvertisingVersion:build_test_report_data( + mock_device, 1, + 1 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.bleAdvertisingVersion( + "1", + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle received NumberOfAliroEndpointKeysSupported from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.NumberOfAliroEndpointKeysSupported:build_test_report_data( + mock_device, 1, + 10 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.maxEndpointKeys( + 10, + {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle Set Card Id command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setCardId", + args = {"3icub18c8pr00"} + }, + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.cardId("3icub18c8pr00", {visibility = {displayed = false}}) + ) + ) + end +) + +test.register_coroutine_test( + "Handle Set Reader Config command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setReaderConfig", + args = { + "1a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac", + "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", + "e24f1b205ba923b32cd13dc009e993a8", + nil + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetAliroReaderConfig( + mock_device, 1, -- endpoint + "\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC", + "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", + "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8", + nil + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.SetAliroReaderConfig:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS -- status + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + {commandName="setReaderConfig", statusCode="success"}, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end +) + +test.register_coroutine_test( + "Handle Set Endpoint Key command and Clear Endpoint Key command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setEndpointKey", + args = { + 0, + "vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + "nonEvictableEndpointKey", + "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", + "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2" + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY, + credential_index = 1 + } + ), -- credential + "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users( + {{userIndex=1, userType="adminMember"}}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.credentials( + {{ + keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + keyIndex=1, + keyType="nonEvictableEndpointKey", + userIndex=1 + }}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setEndpointKey", + keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", + statusCode="success", + userIndex=1 + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "clearEndpointKey", + args = {1, "vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", "nonEvictableEndpointKey"} + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.ClearCredential( + mock_device, 1, -- endpoint + DoorLock.types.CredentialStruct( + {credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY, credential_index = 1} + ) + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.ClearCredential:build_test_command_response( + mock_device, 1 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.credentials({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.weekDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.yearDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="clearEndpointKey", + keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + statusCode="success", + userIndex=1 + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end +) + +test.register_coroutine_test( + "Handle Set Issuer Key command and Clear Issuer Key command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setIssuerKey", + args = { + 0, + "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", + "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2" + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, + credential_index = 1 + } + ), -- credential + "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users( + {{userIndex=1, userType="adminMember"}}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.credentials( + {{ + keyIndex=1, + keyType="issuerKey", + userIndex=1 + }}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setIssuerKey", + requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", + statusCode="success", + userIndex=1 + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "clearIssuerKey", + args = {1, "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2"} + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.ClearCredential( + mock_device, 1, -- endpoint + DoorLock.types.CredentialStruct( + {credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, credential_index = 1} + ) + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.ClearCredential:build_test_command_response( + mock_device, 1 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.credentials({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.weekDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.yearDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="clearIssuerKey", + requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", + statusCode="success", + userIndex=1 + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua index 8b31a80b0c..64f8944a43 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua @@ -301,7 +301,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "lock" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -332,7 +332,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "lock-batteryLevel" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -364,7 +364,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "lock-battery" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -395,7 +395,7 @@ test.register_coroutine_test( end, { test_init = test_init_unlatch, - min_api_version = 19 + min_api_version = 17 } ) @@ -427,7 +427,7 @@ test.register_coroutine_test( end, { test_init = test_init_unlatch, - min_api_version = 19 + min_api_version = 17 } ) @@ -460,7 +460,7 @@ test.register_coroutine_test( end, { test_init = test_init_unlatch, - min_api_version = 19 + min_api_version = 17 } ) @@ -491,7 +491,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin, - min_api_version = 19 + min_api_version = 17 } ) @@ -523,7 +523,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin, - min_api_version = 19 + min_api_version = 17 } ) @@ -556,7 +556,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin, - min_api_version = 19 + min_api_version = 17 } ) @@ -587,7 +587,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin_schedule_unlatch, - min_api_version = 19 + min_api_version = 17 } ) @@ -619,7 +619,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin_schedule_unlatch, - min_api_version = 19 + min_api_version = 17 } ) @@ -652,7 +652,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin_schedule_unlatch, - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-media/src/test/test_matter_media_speaker.lua b/drivers/SmartThings/matter-media/src/test/test_matter_media_speaker.lua index 80b0c33ff8..b795f6438a 100644 --- a/drivers/SmartThings/matter-media/src/test/test_matter_media_speaker.lua +++ b/drivers/SmartThings/matter-media/src/test/test_matter_media_speaker.lua @@ -126,7 +126,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -167,7 +167,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -213,7 +213,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -335,7 +335,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -366,7 +366,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua b/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua index 296d203b41..dc3a4e4c08 100644 --- a/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua +++ b/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua @@ -240,7 +240,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -278,7 +278,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -316,7 +316,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -354,7 +354,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -395,7 +395,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -436,7 +436,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -605,7 +605,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -638,7 +638,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -671,7 +671,7 @@ test.register_coroutine_test( mock_device_variable_speed:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua b/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua index 431a6e1c8e..3b03b1a2e1 100644 --- a/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua +++ b/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua @@ -230,7 +230,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -261,7 +261,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -289,7 +289,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -317,7 +317,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -344,7 +344,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -365,7 +365,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -406,7 +406,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -444,7 +444,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -508,7 +508,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -562,7 +562,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -619,7 +619,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -676,7 +676,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -730,7 +730,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -784,7 +784,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1019,7 +1019,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1055,7 +1055,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1081,7 +1081,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1107,7 +1107,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1192,7 +1192,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) 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 734aaad66b..8d3bd462a5 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 @@ -499,7 +499,7 @@ test.register_coroutine_test( test_aqs_device_type_do_configure(mock_device, "aqs-temp-humidity-all-level-all-meas") end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -510,7 +510,7 @@ test.register_coroutine_test( end, { test_init = test_init_common, - min_api_version = 19 + min_api_version = 17 } ) @@ -521,7 +521,7 @@ test.register_coroutine_test( end, { test_init = test_init_level, - min_api_version = 19 + min_api_version = 17 } ) @@ -533,7 +533,7 @@ test.register_coroutine_test( end, { test_init = test_init_co_co2, - min_api_version = 19 + min_api_version = 17 } ) @@ -544,7 +544,7 @@ test.register_coroutine_test( end, { test_init = test_init_tvoc, - min_api_version = 19 + min_api_version = 17 } ) @@ -568,7 +568,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -590,7 +590,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -625,7 +625,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -695,7 +695,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -720,7 +720,7 @@ test.register_coroutine_test( end, { test_init = test_init_common, - min_api_version = 19 + min_api_version = 17 } ) @@ -828,7 +828,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 57e54fb2ec..9525992eb3 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 @@ -387,7 +387,7 @@ test.register_coroutine_test( end, { test_init = test_init_all, - min_api_version = 19 + min_api_version = 17 } ) @@ -417,7 +417,7 @@ test.register_coroutine_test( end, { test_init = test_init_common, - min_api_version = 19 + min_api_version = 17 } ) 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 b999ac44f9..644c4088e1 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 @@ -82,7 +82,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -120,7 +120,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -168,7 +168,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -192,7 +192,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -232,7 +232,7 @@ test.register_message_test( }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -257,7 +257,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -292,7 +292,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 220ac3c51e..dd4eb87252 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 @@ -71,7 +71,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -101,7 +101,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -133,7 +133,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) 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 0c05271917..02e5815c06 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 @@ -99,7 +99,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -135,7 +135,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -170,7 +170,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -191,7 +191,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -211,7 +211,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -244,7 +244,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 24659a2426..13bd93ed70 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 @@ -94,7 +94,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -116,7 +116,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -148,7 +148,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) test.run_registered_tests() 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 b25ca9ff96..2b942c6f0c 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 @@ -96,7 +96,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -131,7 +131,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 06f7f40fb8..d6024f61be 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor.lua @@ -163,7 +163,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -185,7 +185,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -207,7 +207,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -242,7 +242,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -264,7 +264,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -299,7 +299,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -336,7 +336,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -366,7 +366,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -381,7 +381,7 @@ test.register_coroutine_test( end, { test_init = test_init_presence_sensor, - min_api_version = 19 + min_api_version = 17 } ) 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 46b3819baf..0709be6a31 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 @@ -88,7 +88,7 @@ test.register_coroutine_test( mock_device_humidity_battery:expect_metadata_update({ profile = "humidity-battery" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -116,7 +116,7 @@ test.register_coroutine_test( mock_device_humidity_battery:expect_metadata_update({ profile = "humidity-batteryLevel" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -142,7 +142,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 b617e317a0..0a02bbb27f 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 @@ -171,7 +171,7 @@ test.register_coroutine_test( end, { test_init = test_init_humidity_battery, - min_api_version = 19 + min_api_version = 17 } ) @@ -181,7 +181,7 @@ test.register_coroutine_test( end, { test_init = test_init_humidity_no_battery, - min_api_version = 19 + min_api_version = 17 } ) @@ -191,7 +191,7 @@ test.register_coroutine_test( end, { test_init = test_init_temp_humidity, - min_api_version = 19 + min_api_version = 17 } ) 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 cfc6455813..6ca73351c9 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 @@ -71,7 +71,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 634c9750d7..aeef7b15f9 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 @@ -124,7 +124,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -172,7 +172,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -220,7 +220,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -281,7 +281,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -342,7 +342,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -390,7 +390,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -435,7 +435,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -470,7 +470,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -492,7 +492,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -527,7 +527,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -578,7 +578,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 b5a3fd9f39..3c8102064e 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 @@ -92,7 +92,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "smoke-co-temp-humidity-comeas-battery" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -107,7 +107,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -127,7 +127,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 357c1171dd..305c7e682e 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 @@ -164,7 +164,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -189,7 +189,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -217,7 +217,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -236,7 +236,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -261,7 +261,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -286,7 +286,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -327,7 +327,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button2", capabilities.button.button.double({state_change = true}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -356,7 +356,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -385,7 +385,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -404,7 +404,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -423,7 +423,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -448,7 +448,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -471,7 +471,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -494,7 +494,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 5a5eb23b0f..dbda0c35fc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_aqara_cube.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_aqara_cube.lua @@ -235,7 +235,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -270,7 +270,7 @@ test.register_coroutine_test( end, { test_init = test_init_exhausted, - min_api_version = 19 + min_api_version = 17 } ) 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 9f21039519..667e68ec16 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 @@ -269,7 +269,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -349,7 +349,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 index ec972534b3..180604431b 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -231,7 +231,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -264,7 +264,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -294,7 +294,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -362,7 +362,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -387,7 +387,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -449,7 +449,7 @@ test.register_coroutine_test( end, { test_init = test_init_periodic, - min_api_version = 19 + min_api_version = 17 } ) @@ -474,7 +474,7 @@ test.register_coroutine_test( end, { test_init = test_init, - min_api_version = 19 + min_api_version = 17 } ) @@ -489,7 +489,7 @@ test.register_coroutine_test( end, { test_init = test_init_periodic, - min_api_version = 19 + min_api_version = 17 } ) @@ -607,7 +607,7 @@ test.register_coroutine_test( end, { test_init = test_init, - min_api_version = 19 + min_api_version = 17 } ) @@ -678,7 +678,7 @@ test.register_coroutine_test( end, { test_init = test_init_periodic, - min_api_version = 19 + min_api_version = 17 } ) @@ -761,7 +761,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua index 37524e4a03..f618389ba6 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua @@ -163,7 +163,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -196,7 +196,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -226,7 +226,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -294,7 +294,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -320,7 +320,7 @@ test.register_coroutine_test( end, { test_init = test_init, - min_api_version = 19 + min_api_version = 17 } ) @@ -403,7 +403,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) 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 2f40904cd8..5fa58ce8d3 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -182,7 +182,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -215,7 +215,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -235,7 +235,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -250,7 +250,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -283,7 +283,7 @@ test.register_coroutine_test( test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") end, - min_api_version = 19 + min_api_version = 17 } ) @@ -302,7 +302,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -329,7 +329,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -356,7 +356,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -395,7 +395,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -423,7 +423,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -450,7 +450,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -486,7 +486,7 @@ test.register_coroutine_test( test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") end, - min_api_version = 19 + min_api_version = 17 } ) @@ -580,7 +580,7 @@ test.register_coroutine_test( end, { test_init = test_init_electrical_sensor, - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua index 6e1e0206bc..e07edf6fc9 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua @@ -237,7 +237,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -361,7 +361,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -485,7 +485,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -541,7 +541,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -597,7 +597,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -653,7 +653,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -709,7 +709,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -743,7 +743,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -777,7 +777,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) 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 36624197b1..ccf952a824 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 @@ -154,7 +154,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -187,7 +187,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -270,7 +270,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -300,7 +300,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -386,7 +386,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -411,7 +411,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -436,7 +436,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -482,7 +482,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -514,7 +514,7 @@ test.register_coroutine_test( end, { test_init = test_init_x_y_color_mode, - min_api_version = 19 + min_api_version = 17 } ) @@ -546,7 +546,7 @@ test.register_coroutine_test( end, { test_init = test_init_x_y_color_mode, - min_api_version = 19 + min_api_version = 17 } ) @@ -578,7 +578,7 @@ test.register_coroutine_test( end, { test_init = test_init_x_y_color_mode, - min_api_version = 19 + min_api_version = 17 } ) @@ -595,7 +595,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -617,7 +617,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -652,7 +652,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 42ec81e888..6cb679796d 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua @@ -101,7 +101,7 @@ test.register_coroutine_test( end, { test_init = test_init_mock_bridge, - min_api_version = 19 + min_api_version = 17 } ) @@ -157,7 +157,7 @@ test.register_coroutine_test( end, { test_init = test_init_mock_basic_bridge, - min_api_version = 19 + min_api_version = 17 } ) 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 7de587d93f..86ce8185bf 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua @@ -172,7 +172,7 @@ test.register_coroutine_test( end, { test_init = test_init_battery, - min_api_version = 19 + min_api_version = 17 } ) @@ -196,7 +196,7 @@ test.register_coroutine_test( end, { test_init = test_init_battery, - min_api_version = 19 + min_api_version = 17 } ) @@ -219,7 +219,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -257,7 +257,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -296,7 +296,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -344,7 +344,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -368,7 +368,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -392,7 +392,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -416,7 +416,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -455,7 +455,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -494,7 +494,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -516,7 +516,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -537,7 +537,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "button-batteryLevel" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -558,7 +558,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "button-battery" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index a11f7f944b..631060f79b 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -401,7 +401,7 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)}) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -451,7 +451,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -494,7 +494,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -523,7 +523,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -567,7 +567,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -598,7 +598,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -648,7 +648,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -673,7 +673,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -726,7 +726,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -850,7 +850,7 @@ test.register_coroutine_test( emit_supported_resolutions() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -868,7 +868,7 @@ test.register_coroutine_test( emit_supported_resolutions() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -886,7 +886,7 @@ test.register_coroutine_test( emit_supported_resolutions() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -941,7 +941,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -965,7 +965,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -983,7 +983,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1001,7 +1001,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1049,7 +1049,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1087,7 +1087,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1113,7 +1113,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1142,7 +1142,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.sounds.selectedSound(2))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1185,7 +1185,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1211,7 +1211,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1251,7 +1251,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1287,7 +1287,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1312,7 +1312,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1379,7 +1379,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1487,7 +1487,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1513,7 +1513,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1538,7 +1538,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1570,7 +1570,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1651,7 +1651,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1683,7 +1683,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1708,7 +1708,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1782,7 +1782,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1855,7 +1855,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1931,7 +1931,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -2023,7 +2023,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -2129,7 +2129,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -2205,7 +2205,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -2808,7 +2808,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -2850,7 +2850,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("doorbell", capabilities.button.button.pushed({state_change = false}))) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 eb38e516ee..6a8ab5657a 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 @@ -218,7 +218,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -264,7 +264,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -313,7 +313,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -349,7 +349,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -374,7 +374,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -399,7 +399,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 f929b93745..2225ed129b 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 @@ -294,7 +294,7 @@ test.register_coroutine_test( end, { test_init = test_init_battery, - min_api_version = 19 + min_api_version = 17 } ) @@ -318,7 +318,7 @@ test.register_coroutine_test( end, { test_init = test_init_battery, - min_api_version = 19 + min_api_version = 17 } ) @@ -341,7 +341,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -374,7 +374,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -399,7 +399,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.held({state_change = true}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -424,7 +424,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -448,7 +448,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -488,7 +488,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button5", button_attr.double({state_change = true}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -516,7 +516,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -544,7 +544,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -582,7 +582,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -621,7 +621,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -669,7 +669,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -693,7 +693,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -717,7 +717,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -741,7 +741,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -780,7 +780,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -819,7 +819,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -843,7 +843,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -887,7 +887,7 @@ test.register_message_test( -- no double event }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -945,7 +945,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -967,7 +967,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -988,7 +988,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "5-button-batteryLevel" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1009,7 +1009,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "5-button-battery" }) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 index 071c897a35..68fc7e8339 100644 --- 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 @@ -197,7 +197,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -230,7 +230,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -255,7 +255,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.held({state_change = true}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -280,7 +280,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -304,7 +304,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -344,7 +344,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button6", button_attr.double({state_change = true}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -372,7 +372,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -391,7 +391,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -419,7 +419,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -457,7 +457,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -496,7 +496,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -544,7 +544,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -568,7 +568,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -592,7 +592,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -616,7 +616,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -656,7 +656,7 @@ test.register_message_test( }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -696,7 +696,7 @@ test.register_message_test( }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -720,7 +720,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -764,7 +764,7 @@ test.register_message_test( -- no double event }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -822,7 +822,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) -- run the 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 2cf394a356..0c4b314791 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 @@ -282,7 +282,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -305,7 +305,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -338,7 +338,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -363,7 +363,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -392,7 +392,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -457,7 +457,7 @@ test.register_coroutine_test( end, { test_init = test_init_mcd_unsupported_switch_device_type, - min_api_version = 19 + min_api_version = 17 } ) @@ -477,7 +477,7 @@ test.register_coroutine_test( expect_configure_buttons() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -495,7 +495,7 @@ test.register_coroutine_test( expect_configure_buttons() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -509,7 +509,7 @@ test.register_coroutine_test( expect_configure_buttons() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -529,7 +529,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_child.id, "doConfigure" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua index 642610e88a..3b3cefd8e3 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua @@ -102,7 +102,7 @@ test.register_coroutine_test("Read appropriate attribute values after tempOffset }))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -125,7 +125,7 @@ test.register_coroutine_test("Read appropriate attribute values after humidityOf }))) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 189d95d2e9..a1b60be357 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -265,7 +265,7 @@ test.register_message_test( {}, { test_init = test_init_color_temp, - min_api_version = 19 + min_api_version = 17 } ) @@ -274,7 +274,7 @@ test.register_message_test( {}, { test_init = test_init_extended_color, - min_api_version = 19 + min_api_version = 17 } ) @@ -307,7 +307,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -340,7 +340,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -424,7 +424,7 @@ test.register_message_test( }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -454,7 +454,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -503,7 +503,7 @@ test.register_coroutine_test( end, { test_init = test_init_no_hue_sat, - min_api_version = 19 + min_api_version = 17 } ) @@ -589,7 +589,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -674,7 +674,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -701,7 +701,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -726,7 +726,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -772,7 +772,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -804,7 +804,7 @@ test.register_coroutine_test( end, { test_init = test_init_x_y_color_mode, - min_api_version = 19 + min_api_version = 17 } ) @@ -836,7 +836,7 @@ test.register_coroutine_test( end, { test_init = test_init_x_y_color_mode, - min_api_version = 19 + min_api_version = 17 } ) @@ -868,7 +868,7 @@ test.register_coroutine_test( end, { test_init = test_init_x_y_color_mode, - min_api_version = 19 + min_api_version = 17 } ) @@ -885,7 +885,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -915,7 +915,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -945,7 +945,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1001,7 +1001,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1063,7 +1063,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1088,7 +1088,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1113,7 +1113,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1143,7 +1143,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1168,7 +1168,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1226,7 +1226,7 @@ test.register_coroutine_test( end, { test_init = test_init_x_y_color_mode, - min_api_version = 19 + min_api_version = 17 } ) @@ -1271,7 +1271,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1289,7 +1289,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) 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 b45219adca..37b5ec8ca5 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 @@ -676,7 +676,7 @@ test.register_coroutine_test( end, { test_init = test_init_onoff, - min_api_version = 19 + min_api_version = 17 } ) @@ -686,7 +686,7 @@ test.register_coroutine_test( end, { test_init = test_init_dimmer, - min_api_version = 19 + min_api_version = 17 } ) @@ -696,7 +696,7 @@ test.register_coroutine_test( end, { test_init = test_init_color_dimmer, - min_api_version = 19 + min_api_version = 17 } ) @@ -706,7 +706,7 @@ test.register_coroutine_test( end, { test_init = test_init_onoff_client, - min_api_version = 19 + min_api_version = 17 } ) @@ -716,7 +716,7 @@ test.register_coroutine_test( end, { test_init = test_init_switch_vendor_override, - min_api_version = 19 + min_api_version = 17 } ) @@ -726,7 +726,7 @@ test.register_coroutine_test( end, { test_init = test_init_mounted_on_off_control, - min_api_version = 19 + min_api_version = 17 } ) @@ -736,7 +736,7 @@ test.register_coroutine_test( end, { test_init = test_init_mounted_dimmable_load_control, - min_api_version = 19 + min_api_version = 17 } ) @@ -746,7 +746,7 @@ test.register_coroutine_test( end, { test_init = test_init_water_valve, - min_api_version = 19 + min_api_version = 17 } ) @@ -756,7 +756,7 @@ test.register_coroutine_test( end, { test_init = test_init_parent_client_child_server, - min_api_version = 19 + min_api_version = 17 } ) @@ -766,7 +766,7 @@ test.register_coroutine_test( end, { test_init = test_init_parent_child_switch_types, - min_api_version = 19 + min_api_version = 17 } ) @@ -776,7 +776,7 @@ test.register_coroutine_test( end, { test_init = test_init_parent_child_different_types, - min_api_version = 19 + min_api_version = 17 } ) @@ -786,7 +786,7 @@ test.register_coroutine_test( end, { test_init = test_init_parent_child_unsupported_device_type, - min_api_version = 19 + min_api_version = 17 } ) @@ -796,7 +796,7 @@ test.register_coroutine_test( end, { test_init = test_init_light_level_motion, - min_api_version = 19 + min_api_version = 17 } ) 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 03cf8ee407..338acd58c7 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 @@ -87,7 +87,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -112,7 +112,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -137,7 +137,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -162,7 +162,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -184,7 +184,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -206,7 +206,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -228,7 +228,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -250,7 +250,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) 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 2fabc37408..7976b27a4a 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 @@ -206,7 +206,7 @@ test.register_message_test( }, { test_init = test_init_mock_3switch, - min_api_version = 19 + min_api_version = 17 } ) @@ -233,7 +233,7 @@ test.register_message_test( }, { test_init = test_init_mock_2switch, - min_api_version = 19 + min_api_version = 17 } ) @@ -260,7 +260,7 @@ test.register_message_test( }, { test_init = test_init_mock_3switch_non_sequential, - min_api_version = 19 + min_api_version = 17 } ) 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 efbb7c4545..8102c61865 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 @@ -350,7 +350,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -404,7 +404,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -458,7 +458,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -488,7 +488,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -534,7 +534,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -569,7 +569,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -628,7 +628,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -679,7 +679,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -709,7 +709,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -719,7 +719,7 @@ test.register_coroutine_test( end, { test_init = test_init_parent_child_endpoints_non_sequential, - min_api_version = 19 + min_api_version = 17 } ) @@ -732,7 +732,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "light-binary" }) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 edbc775df8..9beed1805e 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 @@ -343,7 +343,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -397,7 +397,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -451,7 +451,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -481,7 +481,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -527,7 +527,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -562,7 +562,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -621,7 +621,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -672,7 +672,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -702,7 +702,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -712,7 +712,7 @@ test.register_coroutine_test( end, { test_init = test_init_parent_child_endpoints_non_sequential, - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua index 345d8ad232..8d05070efc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua @@ -104,7 +104,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -162,7 +162,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 77c7d7843c..82a8d4d641 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 @@ -234,7 +234,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) 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 8c0ddad122..0a97b12181 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 @@ -432,7 +432,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_aqs, - min_api_version = 19 + min_api_version = 17 } ) @@ -448,7 +448,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs, - min_api_version = 19 + min_api_version = 17 } ) @@ -473,7 +473,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 19 + min_api_version = 17 } ) @@ -530,7 +530,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -574,7 +574,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -612,7 +612,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -660,7 +660,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -721,7 +721,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -777,7 +777,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -799,7 +799,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -870,7 +870,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -888,7 +888,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 19 + min_api_version = 17 } ) @@ -914,7 +914,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 19 + min_api_version = 17 } ) 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 d16833eda1..8bb8a6c920 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 @@ -432,7 +432,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_aqs, - min_api_version = 19 + min_api_version = 17 } ) @@ -448,7 +448,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs, - min_api_version = 19 + min_api_version = 17 } ) @@ -473,7 +473,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 19 + min_api_version = 17 } ) @@ -530,7 +530,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -574,7 +574,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -612,7 +612,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -660,7 +660,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -721,7 +721,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -778,7 +778,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -800,7 +800,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -872,7 +872,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -890,7 +890,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 19 + min_api_version = 17 } ) @@ -916,7 +916,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 19 + min_api_version = 17 } ) 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 7ee1a966d4..6aefcb5045 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 @@ -317,7 +317,7 @@ test.register_coroutine_test( end, { test_init = test_init_basic, - min_api_version = 19 + min_api_version = 17 } ) @@ -394,7 +394,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 19 + min_api_version = 17 } ) 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 975a192d9d..91d3303f78 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua @@ -113,7 +113,7 @@ test.register_coroutine_test( end, { test_init = test_init, - min_api_version = 19 + min_api_version = 17 } ) @@ -128,7 +128,7 @@ test.register_coroutine_test( end, { test_init = test_init_generic, - min_api_version = 19 + min_api_version = 17 } ) 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 8bfd026c24..66c6e593ed 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 @@ -148,7 +148,7 @@ test.register_coroutine_test( assert(component_to_endpoint_map["thermostatTwo"] == THERMOSTAT_TWO_EP, string.format("Thermostat Two Endpoint must be %d", THERMOSTAT_TWO_EP)) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -193,7 +193,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -238,7 +238,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -279,7 +279,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -320,7 +320,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -387,7 +387,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -475,7 +475,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -546,7 +546,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -613,7 +613,7 @@ test.register_message_test( }, { test_init = test_init_auto, - min_api_version = 19 + min_api_version = 17 } ) @@ -637,7 +637,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -662,7 +662,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -710,7 +710,7 @@ test.register_coroutine_test( test_init = function() test_init() end, - min_api_version = 19 + min_api_version = 17 } ) @@ -763,7 +763,7 @@ test.register_coroutine_test( test_init = function() test_init() end, - min_api_version = 19 + min_api_version = 17 } ) 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 6dfe993c27..2f7085bff2 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 @@ -296,7 +296,7 @@ test.register_coroutine_test( end, { test_init = test_init_configure, - min_api_version = 19 + min_api_version = 17 } ) @@ -317,7 +317,7 @@ test.register_coroutine_test( end, { test_init = test_init_nostate, - min_api_version = 19 + min_api_version = 17 } ) @@ -355,7 +355,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -411,7 +411,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -459,7 +459,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -495,7 +495,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -544,7 +544,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -584,7 +584,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 6ecbe049aa..aac1ecd4bc 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 @@ -314,7 +314,7 @@ test.register_coroutine_test( end, { test_init = test_init_basic, - min_api_version = 19 + min_api_version = 17 } ) @@ -342,7 +342,7 @@ test.register_coroutine_test( end, { test_init = test_init_no_state, - min_api_version = 19 + min_api_version = 17 } ) test.run_registered_tests() 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 bb9c637714..5352a65a9d 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 @@ -107,7 +107,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "thermostat-cooling-only-nostate" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -129,7 +129,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "thermostat-cooling-only-nostate-batteryLevel" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -144,7 +144,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 328ea848cf..377725fba0 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 @@ -228,7 +228,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "thermostat-humidity-fan-heating-only" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -252,7 +252,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -277,7 +277,7 @@ test.register_coroutine_test( mock_device_simple:expect_metadata_update({ profile = "thermostat-cooling-only-nostate" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -294,7 +294,7 @@ test.register_coroutine_test( mock_device_no_battery:expect_metadata_update({ profile = "thermostat-cooling-only-nostate-nobattery" }) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 e9f92b6d2c..4e7704feb1 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 @@ -240,7 +240,7 @@ test.register_coroutine_test( get_subscribe_request(mock_device, new_cluster_subscribe_list)) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -252,7 +252,7 @@ test.register_coroutine_test( end, { test_init = test_init_disorder_endpoints, - min_api_version = 19 + min_api_version = 17 } ) @@ -286,7 +286,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) 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 6f8f7d6412..1d772fb2f1 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 @@ -150,7 +150,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -179,7 +179,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -208,7 +208,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -237,7 +237,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -259,7 +259,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -281,7 +281,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -297,7 +297,7 @@ test.register_coroutine_test( assert(min_setpoint_deadband_checked == true, "min_setpoint_deadband_checked is True") end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -327,7 +327,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -357,7 +357,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -387,7 +387,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 3163303942..3dd153d270 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 @@ -101,7 +101,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -126,7 +126,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -151,7 +151,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 4125e20649..d1373c036d 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat.lua @@ -169,7 +169,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -191,7 +191,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -213,7 +213,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -240,7 +240,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -267,7 +267,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -289,7 +289,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -311,7 +311,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -333,7 +333,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -355,7 +355,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -392,7 +392,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -441,7 +441,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -490,7 +490,7 @@ test.register_message_test( }, { test_init = test_init_auto, - min_api_version = 19 + min_api_version = 17 } ) @@ -530,7 +530,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -571,7 +571,7 @@ test.register_message_test( }, { test_init = test_init_auto, - min_api_version = 19 + min_api_version = 17 } ) @@ -623,7 +623,7 @@ test.register_message_test( }, { test_init = test_init_auto, - min_api_version = 19 + min_api_version = 17 } ) @@ -680,7 +680,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -716,7 +716,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -741,7 +741,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -766,7 +766,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -791,7 +791,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -832,7 +832,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -857,7 +857,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -878,7 +878,7 @@ test.register_coroutine_test("Battery percent reports should generate correct me test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -933,7 +933,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 9e38f59b09..af44bf73f6 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 @@ -98,7 +98,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -121,7 +121,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -144,7 +144,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -173,7 +173,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -202,7 +202,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -225,7 +225,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -248,7 +248,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -271,7 +271,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -294,7 +294,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -329,7 +329,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -382,7 +382,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -424,7 +424,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -474,7 +474,7 @@ test.register_message_test( }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -512,7 +512,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -537,7 +537,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -562,7 +562,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -587,7 +587,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -628,7 +628,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -653,7 +653,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -674,7 +674,7 @@ test.register_coroutine_test("Battery percent reports should generate correct me test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -729,7 +729,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 1cdf85215a..a678e85499 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 @@ -179,7 +179,7 @@ test.register_coroutine_test( end, { test_init = test_init, - min_api_version = 19 + min_api_version = 17 } ) 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 0ae634cbc4..f7078703ac 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 @@ -137,7 +137,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 552715c3bf..b4336beeb4 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 @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -142,7 +142,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -204,7 +204,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -236,7 +236,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -261,7 +261,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -331,7 +331,7 @@ test.register_coroutine_test( test_init = function() test_init() end, - min_api_version = 19 + min_api_version = 17 } ) @@ -433,7 +433,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) 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 20f01b6f99..062b199ea1 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 @@ -189,7 +189,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -222,7 +222,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -255,7 +255,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -288,7 +288,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -321,7 +321,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -354,7 +354,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -387,7 +387,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -420,7 +420,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -453,7 +453,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -486,7 +486,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -519,7 +519,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -552,7 +552,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -589,7 +589,7 @@ test.register_coroutine_test("WindowCovering OperationalStatus opening", functio ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -626,7 +626,7 @@ test.register_coroutine_test("WindowCovering OperationalStatus closing", functio ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -663,7 +663,7 @@ test.register_coroutine_test("WindowCovering OperationalStatus unknown", functio ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -681,7 +681,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -699,7 +699,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -717,7 +717,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -749,7 +749,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -765,7 +765,7 @@ test.register_coroutine_test("WindowShade setShadeLevel cmd handler", function() ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -781,7 +781,7 @@ test.register_coroutine_test("WindowShade setShadeTiltLevel cmd handler", functi ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -799,7 +799,7 @@ test.register_coroutine_test("LevelControl CurrentLevel handler", function() ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -821,7 +821,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -844,7 +844,7 @@ test.register_coroutine_test("OperationalStatus report contains current position ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -870,7 +870,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -886,7 +886,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "window-covering-tilt-battery" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -901,7 +901,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -911,7 +911,7 @@ test.register_coroutine_test( end, { test_init = test_init_mains_powered, - min_api_version = 19 + min_api_version = 17 } ) @@ -926,7 +926,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({profile = "window-covering-tilt-battery"}) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1038,7 +1038,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1112,7 +1112,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1170,7 +1170,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1228,7 +1228,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua b/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua index 00264417b1..5c284b1e6b 100644 --- a/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua +++ b/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua @@ -46,7 +46,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -65,7 +65,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -84,7 +84,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -104,7 +104,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -123,7 +123,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -148,7 +148,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -169,7 +169,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.switch.switch.on())) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua b/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua old mode 100755 new mode 100644 index 73920bcd34..606b716dff --- a/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua +++ b/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua @@ -73,7 +73,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, read_AQI_messge}) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -95,7 +95,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -137,7 +137,7 @@ test.register_coroutine_test( capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "good"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -157,7 +157,7 @@ test.register_coroutine_test( capabilities.fineDustHealthConcern.fineDustHealthConcern.good())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -177,7 +177,7 @@ test.register_coroutine_test( capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern.good())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -197,7 +197,7 @@ test.register_coroutine_test( capabilities.dustHealthConcern.dustHealthConcern.good())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -215,7 +215,7 @@ test.register_coroutine_test( capabilities.formaldehydeMeasurement.formaldehydeLevel({value = 1000.0, unit = "mg/m^3"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -235,7 +235,7 @@ test.register_coroutine_test( capabilities.tvocHealthConcern.tvocHealthConcern({value = "unhealthy"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -253,7 +253,7 @@ test.register_coroutine_test( capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "good"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -271,7 +271,7 @@ test.register_coroutine_test( capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "moderate"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -289,7 +289,7 @@ test.register_coroutine_test( capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "slightlyUnhealthy"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -307,7 +307,7 @@ test.register_coroutine_test( capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "unhealthy"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -325,7 +325,7 @@ test.register_coroutine_test( capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "veryUnhealthy"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -343,7 +343,7 @@ test.register_coroutine_test( capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "hazardous"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -363,7 +363,7 @@ test.register_coroutine_test( capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "moderate"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -383,7 +383,7 @@ test.register_coroutine_test( capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "unhealthy"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -403,7 +403,7 @@ test.register_coroutine_test( capabilities.fineDustHealthConcern.fineDustHealthConcern({value = "moderate"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -423,7 +423,7 @@ test.register_coroutine_test( capabilities.fineDustHealthConcern.fineDustHealthConcern({value = "unhealthy"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -443,7 +443,7 @@ test.register_coroutine_test( capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern({value = "unhealthy"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -463,7 +463,7 @@ test.register_coroutine_test( capabilities.dustHealthConcern.dustHealthConcern({value = "unhealthy"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -483,7 +483,7 @@ test.register_coroutine_test( capabilities.tvocHealthConcern.tvocHealthConcern({value = "good"}))) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua old mode 100755 new mode 100644 index bc19893cc6..50c7512838 --- a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua +++ b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua @@ -85,7 +85,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, read_0x0011_messge}) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -130,7 +130,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, read_0x0011_messge}) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -148,7 +148,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftback.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -166,7 +166,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftback.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -184,7 +184,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftwaist.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -202,7 +202,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftwaist.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -220,7 +220,7 @@ test.register_coroutine_test( custom_capabilities.left_control.lefthip.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -238,7 +238,7 @@ test.register_coroutine_test( custom_capabilities.left_control.lefthip.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -256,7 +256,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightback.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -274,7 +274,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightback.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -292,7 +292,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightwaist.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -310,7 +310,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightwaist.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -328,7 +328,7 @@ test.register_coroutine_test( custom_capabilities.right_control.righthip.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -346,7 +346,7 @@ test.register_coroutine_test( custom_capabilities.right_control.righthip.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -364,7 +364,7 @@ test.register_coroutine_test( custom_capabilities.mattressHardness.leftBackHardness(1))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -382,7 +382,7 @@ test.register_coroutine_test( custom_capabilities.mattressHardness.leftWaistHardness(1))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -400,7 +400,7 @@ test.register_coroutine_test( custom_capabilities.mattressHardness.leftHipHardness(1))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -418,7 +418,7 @@ test.register_coroutine_test( custom_capabilities.mattressHardness.rightBackHardness(1))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -436,7 +436,7 @@ test.register_coroutine_test( custom_capabilities.mattressHardness.rightWaistHardness(1))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -454,7 +454,7 @@ test.register_coroutine_test( custom_capabilities.mattressHardness.rightHipHardness(1))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -472,7 +472,7 @@ test.register_coroutine_test( custom_capabilities.yoga.state.both())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -490,7 +490,7 @@ test.register_coroutine_test( custom_capabilities.yoga.state.right())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -508,7 +508,7 @@ test.register_coroutine_test( custom_capabilities.yoga.state.left())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -526,7 +526,7 @@ test.register_coroutine_test( custom_capabilities.yoga.state.stop())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -544,7 +544,7 @@ test.register_coroutine_test( custom_capabilities.ai_mode.left.off())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -562,7 +562,7 @@ test.register_coroutine_test( custom_capabilities.ai_mode.left.on())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -580,7 +580,7 @@ test.register_coroutine_test( custom_capabilities.ai_mode.right.on())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -598,7 +598,7 @@ test.register_coroutine_test( custom_capabilities.ai_mode.right.off())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -616,7 +616,7 @@ test.register_coroutine_test( custom_capabilities.auto_inflation.inflationState.off())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -634,7 +634,7 @@ test.register_coroutine_test( custom_capabilities.auto_inflation.inflationState.on())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -652,7 +652,7 @@ test.register_coroutine_test( custom_capabilities.strong_exp_mode.expState.off())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -670,7 +670,7 @@ test.register_coroutine_test( custom_capabilities.strong_exp_mode.expState.on())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -688,7 +688,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -705,7 +705,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -722,7 +722,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -739,7 +739,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -756,7 +756,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -773,7 +773,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -790,7 +790,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -807,7 +807,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -826,7 +826,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftback.soft())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -845,7 +845,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftwaist.soft())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -864,7 +864,7 @@ test.register_coroutine_test( custom_capabilities.left_control.lefthip.soft())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -883,7 +883,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftback.hard())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -902,7 +902,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftwaist.hard())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -921,7 +921,7 @@ test.register_coroutine_test( custom_capabilities.left_control.lefthip.hard())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -940,7 +940,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightback.soft())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -959,7 +959,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightwaist.soft())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -978,7 +978,7 @@ test.register_coroutine_test( custom_capabilities.right_control.righthip.soft())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -997,7 +997,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightback.hard())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1016,7 +1016,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightwaist.hard())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1035,7 +1035,7 @@ test.register_coroutine_test( custom_capabilities.right_control.righthip.hard())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1052,7 +1052,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1069,7 +1069,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1086,7 +1086,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -1111,7 +1111,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftback("idle", { visibility = { displayed = false }}))) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua index 6e4ad5793e..342a62a5c7 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua @@ -50,7 +50,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -74,7 +74,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -89,7 +89,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua index 2d89507a07..808eb0a764 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua @@ -93,7 +93,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -131,7 +131,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -179,7 +179,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) 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 d15ea394b4..49883cd275 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua @@ -99,7 +99,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -131,7 +131,7 @@ test.register_coroutine_test( mock_device_e1:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -166,7 +166,7 @@ test.register_coroutine_test( mock_device_h1_double_rocker:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -187,7 +187,7 @@ test.register_coroutine_test( capabilities.button.button.pushed({ state_change = true }))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -208,7 +208,7 @@ test.register_coroutine_test( capabilities.button.button.double({ state_change = true }))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -229,7 +229,7 @@ test.register_coroutine_test( capabilities.button.button.held({ state_change = true }))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -248,7 +248,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) test.register_message_test( @@ -266,7 +266,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) test.register_message_test( @@ -284,7 +284,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -328,7 +328,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -350,7 +350,7 @@ test.register_coroutine_test( capabilities.batteryLevel.quantity(1))) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua index f1e0b28ebd..be95f76cce 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua @@ -95,7 +95,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -152,7 +152,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -191,7 +191,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -205,7 +205,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -253,7 +253,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua b/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua index d04587e2f0..db9bad6ea9 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua @@ -55,7 +55,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -86,7 +86,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -117,7 +117,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -159,7 +159,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -173,7 +173,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -275,7 +275,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua index 43d6f95d84..773021ac20 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua @@ -64,7 +64,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -79,7 +79,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -111,7 +111,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -151,7 +151,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua index 53242a2e2c..07652b0e0e 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua @@ -55,7 +55,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -99,7 +99,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -118,7 +118,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -137,7 +137,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -173,7 +173,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device_ezviz_button.id, ZoneStatusAttribute:read(mock_device_ezviz_button) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -199,7 +199,7 @@ test.register_coroutine_test( end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua index 0204586c8a..40d74e503c 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua @@ -88,7 +88,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -113,7 +113,7 @@ test.register_message_test("Refresh should read all necessary attributes", { }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -149,7 +149,7 @@ test.register_coroutine_test("panicAlarm should be triggered and cleared", funct end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -184,7 +184,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -226,7 +226,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -253,7 +253,7 @@ function() test.socket.zigbee:__expect_send({mock_device.id, buttonDelay_msg}) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -286,7 +286,7 @@ test.register_coroutine_test(" Configuration and Switching to button-profile-pan --test.socket.capability:__expect_send({mock_device.id, capabilities.panicAlarm.panicAlarm.clear({state_change = true})}) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -302,7 +302,7 @@ test.register_coroutine_test("Switching from button-profile-panic-frient to butt test.socket.zigbee:__expect_send({mock_device_panic.id, cluster_base.write_manufacturer_specific_attribute(mock_device_panic,BasicInput.ID,0x8000,DEVELCO_MANUFACTURER_CODE,data_types.Uint16,0xFFFF)}) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -324,7 +324,7 @@ test.register_coroutine_test("New preferences after switching the profile should test.socket.zigbee:__expect_send({mock_device_panic.id, cluster_base.write_manufacturer_specific_attribute(mock_device_panic, IASZone.ID,0x8005,DEVELCO_MANUFACTURER_CODE,data_types.Enum8, 1)}) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua index 6761cf263c..7e9448ed7e 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua @@ -92,7 +92,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -127,7 +127,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -174,7 +174,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -198,7 +198,7 @@ test.register_coroutine_test( mock_device_hs6ssb:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -279,7 +279,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -313,7 +313,7 @@ test.register_coroutine_test( mock_device_hs6ssb:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -361,7 +361,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -409,7 +409,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua b/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua index 87e225ae16..4a772c5ab0 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua @@ -75,7 +75,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -119,7 +119,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -143,7 +143,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -205,7 +205,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -304,7 +304,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua b/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua index a3eb2ef195..d350567294 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua @@ -55,7 +55,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -100,7 +100,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -124,7 +124,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -149,7 +149,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, Groups.commands.AddGroup(mock_device, 0x0000) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -247,7 +247,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua b/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua index 6c77739952..1fc0e9d8e6 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua @@ -109,7 +109,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -154,7 +154,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -179,7 +179,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -286,7 +286,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -305,7 +305,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua index 88fae6eeb2..81e68a52da 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua @@ -47,7 +47,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -63,7 +63,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -105,7 +105,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -123,7 +123,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -177,7 +177,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -198,7 +198,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -240,7 +240,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -261,7 +261,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua index 244969abbb..a70d3b52a2 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua @@ -57,7 +57,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -76,7 +76,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -115,7 +115,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua index 2f22a129c4..35ad7aae08 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua @@ -57,7 +57,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -76,7 +76,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -115,7 +115,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua index 218bb10615..2f6deceb70 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua @@ -48,7 +48,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) test.register_message_test( @@ -61,7 +61,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -80,7 +80,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -99,7 +99,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -164,7 +164,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua index 3f0d63d75b..bbe5cbaf86 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua @@ -81,7 +81,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -124,7 +124,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -169,7 +169,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -212,7 +212,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -326,7 +326,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -368,7 +368,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -386,7 +386,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua index ac1d7f4362..8c291f8483 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua @@ -126,7 +126,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -203,7 +203,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -284,7 +284,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -329,7 +329,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -444,7 +444,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -486,7 +486,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -504,7 +504,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua index 4560daa1a0..72bcd2009c 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua @@ -90,7 +90,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -140,7 +140,7 @@ test.register_coroutine_test( -- }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua index a0810072f8..d5eed5d55f 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua @@ -85,7 +85,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -142,7 +142,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -199,7 +199,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -239,7 +239,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -287,7 +287,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua index f1339d6155..2793a6de1b 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua @@ -65,7 +65,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -116,7 +116,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -140,7 +140,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -202,7 +202,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -250,7 +250,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua index 2422763c25..9e209541ff 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua @@ -94,7 +94,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -163,7 +163,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -187,7 +187,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -235,7 +235,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua index 560f59940e..f4ee2e0aed 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua @@ -48,7 +48,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -65,7 +65,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -82,7 +82,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.double({ state_change = true }))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -99,7 +99,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.held({ state_change = true }))) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua index 3e8fc77797..c76d080a01 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua @@ -77,7 +77,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -113,7 +113,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -139,7 +139,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -165,7 +165,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -213,7 +213,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua index 2c95ffe855..93c6d26139 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua @@ -306,7 +306,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -347,7 +347,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua index ad7b45ef95..db4f6b9a43 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua @@ -39,7 +39,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -58,7 +58,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -77,7 +77,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -91,7 +91,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -110,7 +110,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -129,7 +129,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -148,7 +148,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -167,7 +167,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -187,7 +187,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -213,7 +213,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -296,7 +296,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -373,7 +373,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua index d14c324ff5..1b3078f637 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua @@ -50,7 +50,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -98,7 +98,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -123,7 +123,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -141,7 +141,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -167,7 +167,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -212,7 +212,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -261,7 +261,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua index eb7e222bcb..13479d4e8b 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua @@ -81,7 +81,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -142,7 +142,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -170,7 +170,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -199,7 +199,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) 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 9e74007faa..b0df2ffaa4 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 @@ -42,7 +42,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 19 + min_api_version = 17 } ) 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 abbe814c8e..1b67dc8ee6 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 @@ -44,7 +44,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -71,7 +71,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -99,7 +99,7 @@ test.register_message_test( }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -126,7 +126,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -145,7 +145,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -216,7 +216,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua index 4243db2bcf..8f6c5ecfdd 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua @@ -66,7 +66,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -90,7 +90,7 @@ test.register_coroutine_test( capabilities.batteryLevel.battery("normal"))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -108,7 +108,7 @@ test.register_coroutine_test( capabilities.batteryLevel.battery("normal"))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -126,7 +126,7 @@ test.register_coroutine_test( capabilities.batteryLevel.battery("critical"))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -144,7 +144,7 @@ test.register_coroutine_test( capabilities.batteryLevel.battery("normal"))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -162,7 +162,7 @@ test.register_coroutine_test( capabilities.batteryLevel.battery("warning"))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -180,7 +180,7 @@ test.register_coroutine_test( capabilities.batteryLevel.battery("critical"))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -198,7 +198,7 @@ test.register_coroutine_test( capabilities.contactSensor.contact.closed())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -216,7 +216,7 @@ test.register_coroutine_test( capabilities.contactSensor.contact.open())) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua index dbb8b48dff..a2bcfdf612 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua @@ -44,7 +44,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua index 560356b3d4..b13d48fc2e 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua @@ -108,7 +108,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({300, 200, 100})) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -134,7 +134,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive()) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -170,7 +170,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -197,7 +197,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -221,7 +221,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -282,7 +282,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -307,7 +307,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -333,7 +333,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua index 288856d99d..62e0eee71b 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua @@ -46,7 +46,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -108,7 +108,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -127,7 +127,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -146,7 +146,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua index 946d7ddad3..e092666737 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua @@ -47,7 +47,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -117,7 +117,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -136,7 +136,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -155,7 +155,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua index 121ac601f2..f3b94929b6 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua @@ -45,7 +45,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -96,7 +96,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua index a7ae12f907..8be3dd430a 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua @@ -48,7 +48,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -135,7 +135,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -154,7 +154,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -173,7 +173,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -207,7 +207,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 19 + min_api_version = 17 } ) @@ -226,7 +226,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -245,7 +245,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -264,7 +264,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -283,7 +283,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua index fe875d95f6..f6a2e0ebd9 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua @@ -56,7 +56,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -178,7 +178,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -205,7 +205,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -224,7 +224,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -243,7 +243,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -322,7 +322,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 19 + min_api_version = 17 } ) @@ -341,7 +341,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -360,7 +360,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -379,7 +379,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -398,7 +398,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -426,7 +426,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua index 89f1e00d4e..66881f9f0b 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua @@ -56,7 +56,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -178,7 +178,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -205,7 +205,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -224,7 +224,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -243,7 +243,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -322,7 +322,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 19 + min_api_version = 17 } ) @@ -346,7 +346,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -370,7 +370,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -394,7 +394,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -418,7 +418,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -446,7 +446,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua index 27dca9b220..7829e32736 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua @@ -300,7 +300,7 @@ test.register_coroutine_test( mock_device_contact:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -328,7 +328,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -347,7 +347,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -366,7 +366,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -407,7 +407,7 @@ function() }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -431,7 +431,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -455,7 +455,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -477,7 +477,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -505,7 +505,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -533,7 +533,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua index 8d75babbb6..6ebb00d45e 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua @@ -44,7 +44,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua index f564e4dd7e..d4082723c7 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua @@ -95,7 +95,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -119,7 +119,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -180,7 +180,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -222,7 +222,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua index 78398bd78e..fc48ab8eaa 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua @@ -44,7 +44,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua index b9d19e6f13..4713e6eb41 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua @@ -116,7 +116,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -134,7 +134,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -152,7 +152,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -170,7 +170,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -188,7 +188,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -206,7 +206,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -224,7 +224,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -242,7 +242,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -260,7 +260,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -278,7 +278,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(97))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -296,7 +296,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -314,7 +314,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -332,7 +332,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(0))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -350,7 +350,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(0))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -368,7 +368,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(0))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -383,7 +383,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({1050, 3, 9})) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -398,7 +398,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({-1050, -3, -9})) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -413,7 +413,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({10, 1020, 7})) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -428,7 +428,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({-10, -1020, -7})) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -443,7 +443,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({116, 4, 1003})) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -458,7 +458,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({-116, -4, -1003})) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -491,7 +491,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -510,7 +510,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -529,7 +529,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -548,7 +548,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -567,7 +567,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua index 1337e696c2..da87c0aee2 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua @@ -89,7 +89,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -108,7 +108,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -134,7 +134,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive()) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -151,7 +151,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active()) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -170,7 +170,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({300, 100, -200})) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -206,7 +206,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -236,7 +236,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -255,7 +255,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -274,7 +274,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -301,7 +301,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -325,7 +325,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -390,7 +390,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -434,7 +434,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua index 5e6bb70e7d..c03ab49dde 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua @@ -44,7 +44,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_thirdreality_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_thirdreality_multi_sensor.lua index 74a6375b01..92f7274365 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_thirdreality_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_thirdreality_multi_sensor.lua @@ -54,7 +54,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive()) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -71,7 +71,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active()) ) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -90,7 +90,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({200, 100, 300})) ) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua index de9f7d97ee..f27743d4ee 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua @@ -46,7 +46,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -73,7 +73,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -102,7 +102,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -131,7 +131,7 @@ test.register_message_test( }, }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -159,7 +159,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -185,7 +185,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -205,7 +205,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -250,7 +250,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -344,7 +344,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua index 4f12bdb22c..d908aedba6 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua @@ -88,7 +88,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -108,7 +108,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -128,7 +128,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua index 62151b9baa..6b482996de 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua @@ -56,7 +56,7 @@ test.register_coroutine_test( end end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -84,7 +84,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -105,7 +105,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) 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 6c628b7047..75773fdfea 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 @@ -61,7 +61,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -83,7 +83,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -102,7 +102,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -121,7 +121,7 @@ test.register_message_test( } }, { - min_api_version = 19 + min_api_version = 17 } ) @@ -137,7 +137,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -153,7 +153,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -168,7 +168,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(90))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -183,7 +183,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(100))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -200,7 +200,7 @@ test.register_coroutine_test( ))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -216,7 +216,7 @@ test.register_coroutine_test( ))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -246,7 +246,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -259,7 +259,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -276,7 +276,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(0))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -293,7 +293,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(100))) end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -308,7 +308,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -324,7 +324,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) @@ -341,7 +341,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 19 + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua index a5729b649223637c7620d064eba82a049bfd19a0..b3a1073fe22735c0eacfca41eb835f2c15383a67 100644 GIT binary patch delta 308 zcmccqnDP2!#tnyg7|kbdR8^mRmXB}qPo6c55box)e0x!un+t>zWuTHj_0B;g#WvqF zIK~7P1u^v)H{X^oW1JjsrVJ6Ae9u65a=bn3}+fL5|#9r~&}zQgPn^ delta 282 zcmccqnDP2!#tnygCT~#Hn0%IxZ}U%{HH;w6=CgczQ5c+?D}*9tz*0Z;&Vi)FH{UZj z#sp>PF>bysU&c5&-b@)JGWni?@Z@-V*2yO=WjCKQI|Eh*6Wn~#GKUqWYID3jGc$|> z)S|n2r_&Bp str: + command = f"python3 tools/run_driver_tests.py -v".split(" ") + if test_filter != None: + command = f"python3 tools/run_driver_tests.py --filter {test_filter} -v".split(" ") + + print(f"Running command: {command}") + proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + print(f"Done") + return proc.stdout.decode('utf-8') + +def get_results_from_output(output:str) -> dict: + passing = {} + failing = {} + while (m := re.search(TEST_FILE_REGEX,output)): + # print(m.group(1)) + output = output[m.end():] + + passed = [] + failed = [] + while True: + eot_index = len(output) + if eot := (re.search(r"#{10,}", output) or re.search(TEST_FILE_REGEX, output)): + eot_index = eot.start() + + if t := re.search(TEST_RESULT_REGEX,output[:eot_index]): + if t.group(2) == 'PASSED': + passed.append(t.group(1)) + else: + failed.append(t.group(1)) + output = output[t.end():] + else: + output = output[eot_index:] + break + if len(passed) > 0: + passing[m.group(1)] = passed + if len(failed) > 0: + failing[m.group(1)] = failed + return (passing,failing) + +def show_failing_tests(failing): + for filename in failing: + tests = failing[filename] + print(f"Failing tests for: {filename}") + for tn in tests: + print(f" {tn}") + +def update_passing_tests(passing): + for filename in passing: + # print(f"Parsing file: {filename}") + fn = filename + passing_tests = list(passing[filename]) + + def replace_function(match): + if match.group(2) in passing_tests and int(match.group(3)) > LIBS_VERSION: + return f"{match.group(1)}{LIBS_VERSION}{match.group(4)}" + else: + return match.group(0) + + + with open(filename,'r') as fd: + file_contents = fd.read() + + updated_test_file = re.sub(TEST_CODE_REGEX,replace_function,file_contents) + + with open(filename + ".new", 'w') as fd: + fd.write(updated_test_file) + + os.replace(filename + ".new", filename) + print(f"Updated: {filename}") + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description="""Runs driver tests against lua libs, parses the output of the tests results, + and updates the min_api_version of the tests that successfully passed if the test version is + less than the test's min_api_version.NOTE: this CAN NOT be done if test filtering is active. + If enabled, disable this by temporarily removing the code or commenting it out.""") + parser.add_argument("--filter", "-f", help="Filter of tests to run and update min_api_version for. Argument is passed into the tools/run_driver_tests.py --filter ",default=None) + args = parser.parse_args() + + test_output = capture_test_output(args.filter) + passing, failing = get_results_from_output(test_output) + update_passing_tests(passing) + show_failing_tests(failing) + From 8a4b8f2b80e7da4d2ab632c514083314814ff7b5 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 1 Apr 2026 11:10:55 -0500 Subject: [PATCH 213/538] Updating permissions for statuses --- .github/workflows/jenkins-driver-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/jenkins-driver-tests.yml b/.github/workflows/jenkins-driver-tests.yml index 04129fd688..1e29ea8afc 100644 --- a/.github/workflows/jenkins-driver-tests.yml +++ b/.github/workflows/jenkins-driver-tests.yml @@ -4,6 +4,9 @@ on: paths: - 'drivers/**' +permissions: + statuses: write + jobs: trigger-driver-test: strategy: From d593cdafef4f3b843b6763d4bf4e352564651977 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri, 3 Apr 2026 11:52:13 -0500 Subject: [PATCH 214/538] Sonos: Remove Hardcoded Ports (#2855) --- drivers/SmartThings/sonos/src/api/sonos_connection.lua | 6 ++++-- .../SmartThings/sonos/src/api/sonos_ssdp_discovery.lua | 9 ++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/SmartThings/sonos/src/api/sonos_connection.lua b/drivers/SmartThings/sonos/src/api/sonos_connection.lua index 471c3a04fb..6a131c3b0c 100644 --- a/drivers/SmartThings/sonos/src/api/sonos_connection.lua +++ b/drivers/SmartThings/sonos/src/api/sonos_connection.lua @@ -487,9 +487,11 @@ function SonosConnection.new(driver, device) return end - local url_ip = lb_utils.force_url_table(coordinator_player.player.websocket_url).host + local url_table = lb_utils.force_url_table(coordinator_player.player.websocket_url) + local url_ip = url_table.host + local url_port = url_table.port or SonosApi.DEFAULT_SONOS_PORT local base_url = lb_utils.force_url_table( - string.format("https://%s:%s", url_ip, SonosApi.DEFAULT_SONOS_PORT) + string.format("https://%s:%s", url_ip, url_port) ) local _, api_key = driver:check_auth(device) local maybe_token = driver:get_oauth_token() diff --git a/drivers/SmartThings/sonos/src/api/sonos_ssdp_discovery.lua b/drivers/SmartThings/sonos/src/api/sonos_ssdp_discovery.lua index ddc1d0842d..21cbdcd62c 100644 --- a/drivers/SmartThings/sonos/src/api/sonos_ssdp_discovery.lua +++ b/drivers/SmartThings/sonos/src/api/sonos_ssdp_discovery.lua @@ -421,12 +421,11 @@ function sonos_ssdp.spawn_persistent_ssdp_task() if is_new_information then local headers = SonosApi.make_headers() - local discovery_info, err = SonosApi.RestApi.get_player_info( - net_url.parse( - string.format("https://%s:%s", sonos_ssdp_info.ip, SonosApi.DEFAULT_SONOS_PORT) - ), - headers + local parsed_wss_url = net_url.parse(sonos_ssdp_info.wss_url) or {} + local base_url = net_url.parse( + string.format("https://%s:%s", parsed_wss_url.host, parsed_wss_url.port or SonosApi.DEFAULT_SONOS_PORT) ) + local discovery_info, err = SonosApi.RestApi.get_player_info(base_url, headers) if not discovery_info then log.error(string.format("Error getting discovery info from SSDP response: %s", err)) elseif discovery_info._objectType == "globalError" then From 82cdfe213802a9b360bb18e2491525dcc105d5b5 Mon Sep 17 00:00:00 2001 From: wkhenon Date: Thu, 26 Feb 2026 15:04:19 -0500 Subject: [PATCH 215/538] CHAD-17426: Add stateless step capabilities to zigbee-switch Co-authored-by: Harrison Carter --- .../profiles/abl-light-z-001-bulb.yml | 4 + .../zigbee-switch/profiles/aqara-led-bulb.yml | 4 + .../zigbee-switch/profiles/aqara-light.yml | 4 + .../zigbee-switch/profiles/color-bulb.yml | 2 + .../profiles/color-temp-bulb-2000K-6500K.yml | 4 + .../profiles/color-temp-bulb-2200K-4000K.yml | 4 + .../profiles/color-temp-bulb-2200K-5000K.yml | 4 + .../profiles/color-temp-bulb-2200K-6500K.yml | 4 + .../profiles/color-temp-bulb-2500K-6000K.yml | 4 + .../profiles/color-temp-bulb-2700K-5000K.yml | 4 + .../profiles/color-temp-bulb-2700K-6500K.yml | 4 + .../profiles/color-temp-bulb.yml | 4 + .../zigbee-switch/profiles/ge-link-bulb.yml | 2 + .../profiles/inovelli-vzm30-sn.yml | 2 + .../profiles/inovelli-vzm31-sn.yml | 2 + .../profiles/inovelli-vzm32-sn.yml | 2 + .../profiles/light-level-power-energy.yml | 2 + .../profiles/light-level-power.yml | 2 + .../profiles/on-off-level-intensity.yml | 2 + .../profiles/on-off-level-motion-sensor.yml | 2 + .../on-off-level-no-firmware-update.yml | 2 + .../zigbee-switch/profiles/on-off-level.yml | 2 + .../profiles/plug-level-power.yml | 2 + .../profiles/rgbw-bulb-1800K-6500K.yml | 4 + .../profiles/rgbw-bulb-2000K-6500K.yml | 4 + .../profiles/rgbw-bulb-2200K-4000K.yml | 4 + .../profiles/rgbw-bulb-2200K-5000K.yml | 4 + .../profiles/rgbw-bulb-2200K-6500K.yml | 4 + .../profiles/rgbw-bulb-2500K-6000K.yml | 4 + .../profiles/rgbw-bulb-2700K-5000K.yml | 4 + .../profiles/rgbw-bulb-2700K-6500K.yml | 4 + .../zigbee-switch/profiles/rgbw-bulb.yml | 4 + .../profiles/switch-dimmer-power-energy.yml | 2 + .../profiles/switch-level-power.yml | 2 + .../zigbee-switch/profiles/switch-level.yml | 2 + .../src/color_temp_range_handlers/init.lua | 79 ++++++------ .../src/stateless_handlers/can_handle.lua | 14 ++ .../src/stateless_handlers/init.lua | 56 ++++++++ .../zigbee-switch/src/sub_drivers.lua | 3 +- .../zigbee-switch/src/switch_utils.lua | 11 ++ .../test/test_all_capability_zigbee_bulb.lua | 122 ++++++++++++++++++ .../src/test/test_sengled_color_temp_bulb.lua | 47 +++++++ 42 files changed, 403 insertions(+), 39 deletions(-) create mode 100644 drivers/SmartThings/zigbee-switch/src/stateless_handlers/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua diff --git a/drivers/SmartThings/zigbee-switch/profiles/abl-light-z-001-bulb.yml b/drivers/SmartThings/zigbee-switch/profiles/abl-light-z-001-bulb.yml index 8a0d62b1f6..28d233316c 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/abl-light-z-001-bulb.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/abl-light-z-001-bulb.yml @@ -6,8 +6,12 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/aqara-led-bulb.yml b/drivers/SmartThings/zigbee-switch/profiles/aqara-led-bulb.yml index 65ee11beb0..c3d8f2f16d 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/aqara-led-bulb.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/aqara-led-bulb.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [2700, 6500] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/aqara-light.yml b/drivers/SmartThings/zigbee-switch/profiles/aqara-light.yml index 6c2da08393..07cc4b4904 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/aqara-light.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/aqara-light.yml @@ -10,8 +10,12 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/color-bulb.yml b/drivers/SmartThings/zigbee-switch/profiles/color-bulb.yml index fbe4243f6a..8395432142 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/color-bulb.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/color-bulb.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2000K-6500K.yml b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2000K-6500K.yml index fae09d20cb..43b1953caf 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2000K-6500K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2000K-6500K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2000, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2200K-4000K.yml b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2200K-4000K.yml index 0e542eff43..4a81aac14c 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2200K-4000K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2200K-4000K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 4000 ] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2200K-5000K.yml b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2200K-5000K.yml index e8495a5b6c..420f43c959 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2200K-5000K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2200K-5000K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 5000 ] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2200K-6500K.yml b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2200K-6500K.yml index 985ec05a4f..221be95e5d 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2200K-6500K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2200K-6500K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2500K-6000K.yml b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2500K-6000K.yml index e6ffe1a46f..7715942435 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2500K-6000K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2500K-6000K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2500, 6000 ] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2700K-5000K.yml b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2700K-5000K.yml index 15677d1307..a1b8fa2b7c 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2700K-5000K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2700K-5000K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2700, 5000 ] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2700K-6500K.yml b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2700K-6500K.yml index b56cb5f84e..2cbe79b906 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2700K-6500K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb-2700K-6500K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2700, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb.yml b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb.yml index c30ba1d25f..e882f23098 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/color-temp-bulb.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2700, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/ge-link-bulb.yml b/drivers/SmartThings/zigbee-switch/profiles/ge-link-bulb.yml index e0c09885c4..0381032825 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/ge-link-bulb.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/ge-link-bulb.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm30-sn.yml b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm30-sn.yml index ff670c0097..6a41cae14b 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm30-sn.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm30-sn.yml @@ -6,6 +6,8 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: temperatureMeasurement version: 1 - id: relativeHumidityMeasurement diff --git a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm31-sn.yml b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm31-sn.yml index f39f8324eb..47368ca7e3 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm31-sn.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm31-sn.yml @@ -6,6 +6,8 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: powerMeter version: 1 - id: energyMeter diff --git a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml index 746890a15a..fe3b3de11b 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml @@ -6,6 +6,8 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: motionSensor version: 1 - id: illuminanceMeasurement diff --git a/drivers/SmartThings/zigbee-switch/profiles/light-level-power-energy.yml b/drivers/SmartThings/zigbee-switch/profiles/light-level-power-energy.yml index b45fc5e0e8..ac516c23c0 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/light-level-power-energy.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/light-level-power-energy.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: powerMeter version: 1 - id: energyMeter diff --git a/drivers/SmartThings/zigbee-switch/profiles/light-level-power.yml b/drivers/SmartThings/zigbee-switch/profiles/light-level-power.yml index 6eca96ab18..a07047269b 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/light-level-power.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/light-level-power.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: powerMeter version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/on-off-level-intensity.yml b/drivers/SmartThings/zigbee-switch/profiles/on-off-level-intensity.yml index 0d8688cb6b..dc5e6e0ca2 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/on-off-level-intensity.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/on-off-level-intensity.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/on-off-level-motion-sensor.yml b/drivers/SmartThings/zigbee-switch/profiles/on-off-level-motion-sensor.yml index 248bd66e7f..f344f32a34 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/on-off-level-motion-sensor.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/on-off-level-motion-sensor.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: motionSensor version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/on-off-level-no-firmware-update.yml b/drivers/SmartThings/zigbee-switch/profiles/on-off-level-no-firmware-update.yml index a25ef4aa2c..f5ef30908f 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/on-off-level-no-firmware-update.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/on-off-level-no-firmware-update.yml @@ -6,6 +6,8 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: refresh version: 1 categories: diff --git a/drivers/SmartThings/zigbee-switch/profiles/on-off-level.yml b/drivers/SmartThings/zigbee-switch/profiles/on-off-level.yml index 350c51c722..67f27f7289 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/on-off-level.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/on-off-level.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/plug-level-power.yml b/drivers/SmartThings/zigbee-switch/profiles/plug-level-power.yml index d6ac987d50..a234f15409 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/plug-level-power.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/plug-level-power.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: powerMeter version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-1800K-6500K.yml b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-1800K-6500K.yml index c95d6c4b16..6f6a78a4b5 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-1800K-6500K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-1800K-6500K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 1800, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2000K-6500K.yml b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2000K-6500K.yml index 89af9a7f94..bf7f81832d 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2000K-6500K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2000K-6500K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2000, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2200K-4000K.yml b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2200K-4000K.yml index 2e17ba527d..b1c7d3e379 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2200K-4000K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2200K-4000K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 4000 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2200K-5000K.yml b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2200K-5000K.yml index d83b671f12..9032ba0fe0 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2200K-5000K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2200K-5000K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 5000 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2200K-6500K.yml b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2200K-6500K.yml index 0d334ca62e..1a13390cf5 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2200K-6500K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2200K-6500K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2200, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2500K-6000K.yml b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2500K-6000K.yml index 3bd54e3a59..c74ba232c0 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2500K-6000K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2500K-6000K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2500, 6000 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2700K-5000K.yml b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2700K-5000K.yml index 740a002b83..466a34c06a 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2700K-5000K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2700K-5000K.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2700, 5000 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2700K-6500K.yml b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2700K-6500K.yml index 8878a04a99..dcab0e8224 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2700K-6500K.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb-2700K-6500K.yml @@ -6,12 +6,16 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2700, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb.yml b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb.yml index b863f9e587..57f566bdfb 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/rgbw-bulb.yml @@ -10,12 +10,16 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 config: values: - key: "colorTemperature.value" range: [ 2700, 6500 ] + - id: statelessColorTemperatureStep + version: 1 - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-dimmer-power-energy.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-dimmer-power-energy.yml index 4507ab5282..623156bf6c 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/switch-dimmer-power-energy.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-dimmer-power-energy.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: powerMeter version: 1 - id: energyMeter diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-level-power.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-level-power.yml index 2042471bf3..43b2b26581 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/switch-level-power.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-level-power.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: powerMeter version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-level.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-level.yml index 96166ef0d9..abcaba3e21 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/switch-level.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-level.yml @@ -10,6 +10,8 @@ components: values: - key: "level.value" range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/init.lua b/drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/init.lua index 761ac49736..84185557af 100644 --- a/drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/init.lua @@ -3,56 +3,59 @@ local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" -local utils = require "st.utils" -local KELVIN_MAX = "_max_kelvin" -local KELVIN_MIN = "_min_kelvin" -local MIREDS_CONVERSION_CONSTANT = 1000000 -local COLOR_TEMPERATURE_KELVIN_MAX = 15000 -local COLOR_TEMPERATURE_KELVIN_MIN = 1000 -local COLOR_TEMPERATURE_MIRED_MAX = utils.round(MIREDS_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MIN) -- 1000 -local COLOR_TEMPERATURE_MIRED_MIN = utils.round(MIREDS_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MAX) -- 67 +local switch_utils = require "switch_utils" + +-- These values are a "sanity check" to ensure that max/min values we are getting are reasonable +local COLOR_TEMPERATURE_MIRED_MAX = 1000 -- 1000 Kelvin +local COLOR_TEMPERATURE_MIRED_MIN = 67 -- 15000 Kelvin local function color_temp_min_mireds_handler(driver, device, value, zb_rx) - local temp_in_mired = value.value - local endpoint = zb_rx.address_header.src_endpoint.value - if temp_in_mired == nil then + -- if mired value is nil or outside of sane bounds, log and ignore. Else, save value + local min_mired_bound = value.value + if min_mired_bound == nil then return - end - if (temp_in_mired < COLOR_TEMPERATURE_MIRED_MIN or temp_in_mired > COLOR_TEMPERATURE_MIRED_MAX) then - device.log.warn_with({hub_logs = true}, string.format("Device reported a color temperature %d mired outside of sane range of %.2f-%.2f", temp_in_mired, COLOR_TEMPERATURE_MIRED_MIN, COLOR_TEMPERATURE_MIRED_MAX)) + elseif (min_mired_bound < COLOR_TEMPERATURE_MIRED_MIN or min_mired_bound > COLOR_TEMPERATURE_MIRED_MAX) then + device.log.warn_with({hub_logs = true}, string.format("Device reported a color temperature %d mired outside of sane range of %.2f-%.2f", min_mired_bound, COLOR_TEMPERATURE_MIRED_MIN, COLOR_TEMPERATURE_MIRED_MAX)) return end - local temp_in_kelvin = utils.round(MIREDS_CONVERSION_CONSTANT / temp_in_mired) - device:set_field(KELVIN_MAX..endpoint, temp_in_kelvin) - local min = device:get_field(KELVIN_MIN..endpoint) - if min ~= nil then - if temp_in_kelvin > min then - device:emit_event_for_endpoint(endpoint, capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = min, maximum = temp_in_kelvin}})) - else - device.log.warn_with({hub_logs = true}, string.format("Device reported a max color temperature %d K that is not higher than the reported min color temperature %d K", min, temp_in_kelvin)) - end + device:set_field(switch_utils.MIRED_MIN_BOUND, min_mired_bound, {persist = true}) + + -- if we have already received a valid max mired bound, emit a colorTemperatureRange event + local max_mired_bound = device:get_field(switch_utils.MIRED_MAX_BOUND) + if max_mired_bound == nil then + return + elseif min_mired_bound < max_mired_bound then + local endpoint = zb_rx.address_header.src_endpoint.value + local max_kelvin_bound = switch_utils.convert_mired_to_kelvin(min_mired_bound) + local min_kelvin_bound = switch_utils.convert_mired_to_kelvin(max_mired_bound) + device:emit_event_for_endpoint(endpoint, capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = min_kelvin_bound, maximum = max_kelvin_bound}})) + else + device.log.warn_with({hub_logs = true}, string.format("Device reported a max color temperature %d Mireds that is not higher than the reported min color temperature %d Mireds", max_mired_bound, min_mired_bound)) end end local function color_temp_max_mireds_handler(driver, device, value, zb_rx) - local temp_in_mired = value.value - local endpoint = zb_rx.address_header.src_endpoint.value - if temp_in_mired == nil then + -- if mired value is nil or outside of sane bounds, log and ignore. Else, save value + local max_mired_bound = value.value + if max_mired_bound == nil then return - end - if (temp_in_mired < COLOR_TEMPERATURE_MIRED_MIN or temp_in_mired > COLOR_TEMPERATURE_MIRED_MAX) then - device.log.warn_with({hub_logs = true}, string.format("Device reported a color temperature %d mired outside of sane range of %.2f-%.2f", temp_in_mired, COLOR_TEMPERATURE_MIRED_MIN, COLOR_TEMPERATURE_MIRED_MAX)) + elseif (max_mired_bound < COLOR_TEMPERATURE_MIRED_MIN or max_mired_bound > COLOR_TEMPERATURE_MIRED_MAX) then + device.log.warn_with({hub_logs = true}, string.format("Device reported a color temperature %d mired outside of sane range of %.2f-%.2f", max_mired_bound, COLOR_TEMPERATURE_MIRED_MIN, COLOR_TEMPERATURE_MIRED_MAX)) return end - local temp_in_kelvin = utils.round(MIREDS_CONVERSION_CONSTANT / temp_in_mired) - device:set_field(KELVIN_MIN..endpoint, temp_in_kelvin) - local max = device:get_field(KELVIN_MAX..endpoint) - if max ~= nil then - if temp_in_kelvin < max then - device:emit_event_for_endpoint(endpoint, capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = temp_in_kelvin, maximum = max}})) - else - device.log.warn_with({hub_logs = true}, string.format("Device reported a min color temperature %d K that is not lower than the reported max color temperature %d K", temp_in_kelvin, max)) - end + device:set_field(switch_utils.MIRED_MAX_BOUND, max_mired_bound, {persist = true}) + + -- if we have already received a valid min mired bound, emit a colorTemperatureRange event + local min_mired_bound = device:get_field(switch_utils.MIRED_MIN_BOUND) + if min_mired_bound == nil then + return + elseif max_mired_bound > min_mired_bound then + local endpoint = zb_rx.address_header.src_endpoint.value + local max_kelvin_bound = switch_utils.convert_mired_to_kelvin(min_mired_bound) + local min_kelvin_bound = switch_utils.convert_mired_to_kelvin(max_mired_bound) + device:emit_event_for_endpoint(endpoint, capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = min_kelvin_bound, maximum = max_kelvin_bound}})) + else + device.log.warn_with({hub_logs = true}, string.format("Device reported a min color temperature %d Mireds that is not lower than the reported max color temperature %d Mireds", min_mired_bound, max_mired_bound)) end end diff --git a/drivers/SmartThings/zigbee-switch/src/stateless_handlers/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/stateless_handlers/can_handle.lua new file mode 100644 index 0000000000..845bd33156 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/stateless_handlers/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" + +return function(opts, driver, device) + local can_handle = device:supports_capability(capabilities.statelessColorTemperatureStep) + or device:supports_capability(capabilities.statelessSwitchLevelStep) + if can_handle then + local subdriver = require("stateless_handlers") + return true, subdriver + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua b/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua new file mode 100644 index 0000000000..5dc1f18b3c --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua @@ -0,0 +1,56 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local st_utils = require "st.utils" +local clusters = require "st.zigbee.zcl.clusters" +local switch_utils = require "switch_utils" + +-- These values are the mired versions of the config bounds in the default profile (e.g. color-temp-bulb) +local DEFAULT_MIRED_MAX_BOUND = 370 -- 2700 Kelvin (Mireds are the inverse of Kelvin) +local DEFAULT_MIRED_MIN_BOUND = 154 -- 6500 Kelvin (Mireds are the inverse of Kelvin) + +-- Transition Time: The time that shall be taken to perform the step change, in units of 1/10ths of a second. +local TRANSITION_TIME = 3 -- default: 0.3 seconds + +-- Options Mask & Override: Indicates which options are being overridden by the Level/ColorControl cluster commands +local OPTIONS_MASK = 0x01 -- default: The `ExecuteIfOff` option is overriden +local IGNORE_COMMAND_IF_OFF = 0x00 -- default: the command will not be executed if the device is off + +local function step_color_temperature_by_percent_handler(driver, device, cmd) + local step_percent_change = cmd.args and cmd.args.stepSize or 0 + if step_percent_change == 0 then return end + -- Reminder, stepSize > 0 == Kelvin UP == Mireds DOWN. stepSize < 0 == Kelvin DOWN == Mireds UP + local step_mode = (step_percent_change > 0) and clusters.ColorControl.types.CcStepMode.DOWN or clusters.ColorControl.types.CcStepMode.UP + local min_mireds = device:get_field(switch_utils.MIRED_MIN_BOUND) + local max_mireds = device:get_field(switch_utils.MIRED_MAX_BOUND) + -- since colorTemperatureRange is only set after both custom bounds are, use defaults if any custom bound is missing + if not (min_mireds and max_mireds) then + min_mireds = DEFAULT_MIRED_MIN_BOUND + max_mireds = DEFAULT_MIRED_MAX_BOUND + end + local step_size_in_mireds = st_utils.round((max_mireds - min_mireds) * (math.abs(step_percent_change)/100.0)) + device:send(clusters.ColorControl.server.commands.StepColorTemperature(device, step_mode, step_size_in_mireds, TRANSITION_TIME, min_mireds, max_mireds, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF)) +end + +local function step_level_handler(driver, device, cmd) + local step_size = st_utils.round((cmd.args and cmd.args.stepSize or 0)/100.0 * 254) + if step_size == 0 then return end + local step_mode = (step_size > 0) and clusters.Level.types.MoveStepMode.UP or clusters.Level.types.MoveStepMode.DOWN + device:send(clusters.Level.server.commands.Step(device, step_mode, math.abs(step_size), TRANSITION_TIME, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF)) +end + +local stateless_handlers = { + NAME = "Zigbee Stateless Step Handlers", + capability_handlers = { + [capabilities.statelessColorTemperatureStep.ID] = { + [capabilities.statelessColorTemperatureStep.commands.stepColorTemperatureByPercent.NAME] = step_color_temperature_by_percent_handler, + }, + [capabilities.statelessSwitchLevelStep.ID] = { + [capabilities.statelessSwitchLevelStep.commands.stepLevel.NAME] = step_level_handler, + }, + }, + can_handle = require("stateless_handlers.can_handle") +} + +return stateless_handlers diff --git a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua index 5dcf24ca74..69be094da4 100644 --- a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua @@ -35,5 +35,6 @@ return { lazy_load_if_possible("tuya-multi"), lazy_load_if_possible("frient"), lazy_load_if_possible("frient-IO"), - lazy_load_if_possible("color_temp_range_handlers") + lazy_load_if_possible("color_temp_range_handlers"), + lazy_load_if_possible("stateless_handlers") } diff --git a/drivers/SmartThings/zigbee-switch/src/switch_utils.lua b/drivers/SmartThings/zigbee-switch/src/switch_utils.lua index 66ad4715f9..d30ada0588 100644 --- a/drivers/SmartThings/zigbee-switch/src/switch_utils.lua +++ b/drivers/SmartThings/zigbee-switch/src/switch_utils.lua @@ -1,8 +1,19 @@ -- Copyright 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local st_utils = require "st.utils" + local switch_utils = {} +switch_utils.MIRED_MAX_BOUND = "__max_mired_bound" +switch_utils.MIRED_MIN_BOUND = "__min_mired_bound" + +switch_utils.MIREDS_CONVERSION_CONSTANT = 1000000 + +switch_utils.convert_mired_to_kelvin = function(mired) + return st_utils.round(switch_utils.MIREDS_CONVERSION_CONSTANT / mired) +end + switch_utils.emit_event_if_latest_state_missing = function(device, component, capability, attribute_name, value) if device:get_latest_state(component, capability.ID, attribute_name) == nil then device:emit_event(value) 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 cf4afb92c2..c6391233b5 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 @@ -25,8 +25,10 @@ local zigbee_bulb_all_caps = { capabilities = { [capabilities.switch.ID] = { id = capabilities.switch.ID }, [capabilities.switchLevel.ID] = { id = capabilities.switchLevel.ID }, + [capabilities.statelessSwitchLevelStep.ID] = { id = capabilities.statelessSwitchLevelStep.ID }, [capabilities.colorControl.ID] = { id = capabilities.colorControl.ID }, [capabilities.colorTemperature.ID] = { id = capabilities.colorTemperature.ID }, + [capabilities.statelessColorTemperatureStep.ID] = { id = capabilities.statelessColorTemperatureStep.ID }, [capabilities.powerMeter.ID] = { id = capabilities.powerMeter.ID }, [capabilities.energyMeter.ID] = { id = capabilities.energyMeter.ID }, [capabilities.refresh.ID] = { id = capabilities.refresh.ID }, @@ -294,6 +296,126 @@ test.register_message_test( } ) +local DEFAULT_MIRED_MAX = 370 +local DEFAULT_MIRED_MIN = 154 +local TRANSITION_TIME = 3 +local OPTIONS_MASK = 0x01 +local IGNORE_COMMAND_IF_OFF = 0x00 + +test.register_message_test( + "Step ColorTemperature command test", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { 20 } } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.server.commands.StepColorTemperature(mock_device, ColorControl.types.CcStepMode.DOWN, 43, TRANSITION_TIME, DEFAULT_MIRED_MIN, DEFAULT_MIRED_MAX, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF) + }, + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { 90 } } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.server.commands.StepColorTemperature(mock_device, ColorControl.types.CcStepMode.DOWN, 194, TRANSITION_TIME, DEFAULT_MIRED_MIN, DEFAULT_MIRED_MAX, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF) + }, + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { -50 } } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.server.commands.StepColorTemperature(mock_device, ColorControl.types.CcStepMode.UP, 108, TRANSITION_TIME, DEFAULT_MIRED_MIN, DEFAULT_MIRED_MAX, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF) + }, + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Step Level command test", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 25 } } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + Level.server.commands.Step(mock_device, Level.types.MoveStepMode.UP, 64, TRANSITION_TIME, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF) + }, + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { -50 } } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + Level.server.commands.Step(mock_device, Level.types.MoveStepMode.DOWN, 127, TRANSITION_TIME, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF) + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 100 } } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + Level.server.commands.Step(mock_device, Level.types.MoveStepMode.UP, 254, TRANSITION_TIME, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF) + } + } + }, + { + min_api_version = 19 + } +) + test.register_coroutine_test( "lifecycle configure event should configure device", function () 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 eae21e8ed7..f88399a145 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 @@ -4,6 +4,7 @@ local test = require "integration_test" local t_utils = require "integration_test.utils" local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local OnOff = clusters.OnOff @@ -151,4 +152,50 @@ test.register_coroutine_test( } ) +local TRANSITION_TIME = 3 +local OPTIONS_MASK = 0x01 +local IGNORE_COMMAND_IF_OFF = 0x00 +local REPORTED_MIRED_MIN = 160 +local REPORTED_MIRED_MAX = 370 + +test.register_coroutine_test( + "Step Color Temperature command with device-reported mired range test", + function() + -- Report non-default range values to verify subsequent step commands do not use defaults. + test.socket.zigbee:__queue_receive({mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_attr_report(mock_device, REPORTED_MIRED_MAX)}) + test.socket.zigbee:__queue_receive({mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_attr_report(mock_device, REPORTED_MIRED_MIN)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 2703, maximum = 6250}))) + test.wait_for_events() + + test.socket.capability:__queue_receive({mock_device.id, { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { 20 } } }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.server.commands.StepColorTemperature(mock_device, ColorControl.types.CcStepMode.DOWN, 42, TRANSITION_TIME, REPORTED_MIRED_MIN, REPORTED_MIRED_MAX, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF) + } + ) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Step Level command test", + function() + test.socket.capability:__queue_receive({mock_device.id, { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 25 } } }) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + Level.commands.Step(mock_device, Level.types.MoveStepMode.UP, 64, TRANSITION_TIME, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF) + } + ) + test.wait_for_events() + end, + { + min_api_version = 19 + } +) + test.run_registered_tests() From 09cb31c7ae37a493a1c23f4d6041908a30b5c431 Mon Sep 17 00:00:00 2001 From: haedo-doo Date: Wed, 8 Apr 2026 06:52:56 +0900 Subject: [PATCH 216/538] Aqara Wireless Knob Switch H1 (#2844) * add Aqara Wireless Knob Switch H1 * fix copyright year * refactor and add rotation test cases * fix indentation and address review comments * fix whitespace warnings --- .../zigbee-button/fingerprints.yml | 5 + .../profiles/aqara-knob-switch.yml | 19 ++ .../src/aqara-knob/can_handle.lua | 13 + .../zigbee-button/src/aqara-knob/init.lua | 141 +++++++++ .../zigbee-button/src/sub_drivers.lua | 1 + .../src/test/test_aqara_knob_switch.lua | 287 ++++++++++++++++++ 6 files changed, 466 insertions(+) create mode 100644 drivers/SmartThings/zigbee-button/profiles/aqara-knob-switch.yml create mode 100644 drivers/SmartThings/zigbee-button/src/aqara-knob/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/aqara-knob/init.lua create mode 100644 drivers/SmartThings/zigbee-button/src/test/test_aqara_knob_switch.lua diff --git a/drivers/SmartThings/zigbee-button/fingerprints.yml b/drivers/SmartThings/zigbee-button/fingerprints.yml index c73109ebca..f256e0584c 100644 --- a/drivers/SmartThings/zigbee-button/fingerprints.yml +++ b/drivers/SmartThings/zigbee-button/fingerprints.yml @@ -34,6 +34,11 @@ zigbeeManufacturer: manufacturer: LUMI model: lumi.remote.b28ac1 deviceProfileName: aqara-double-buttons-mode + - id: "lumi/lumi.remote.rkba01" + deviceLabel: "Aqara Wireless Smart Knob H1" + manufacturer: LUMI + model: lumi.remote.rkba01 + deviceProfileName: aqara-knob-switch - id: "HEIMAN/SOS-EM" deviceLabel: HEIMAN Button manufacturer: HEIMAN diff --git a/drivers/SmartThings/zigbee-button/profiles/aqara-knob-switch.yml b/drivers/SmartThings/zigbee-button/profiles/aqara-knob-switch.yml new file mode 100644 index 0000000000..1c197504bb --- /dev/null +++ b/drivers/SmartThings/zigbee-button/profiles/aqara-knob-switch.yml @@ -0,0 +1,19 @@ +name: aqara-knob-switch +components: + - id: main + capabilities: + - id: button + version: 1 + - id: knob + version: 1 + - id: batteryLevel + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Button +preferences: + - preferenceId: stse.knobSensitivity + explicit: true diff --git a/drivers/SmartThings/zigbee-button/src/aqara-knob/can_handle.lua b/drivers/SmartThings/zigbee-button/src/aqara-knob/can_handle.lua new file mode 100644 index 0000000000..a5a48f6cb9 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/aqara-knob/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_aqara_products = function(opts, driver, device, ...) + local FINGERPRINTS = { mfr = "LUMI", model = "lumi.remote.rkba01" } + + if device:get_manufacturer() == FINGERPRINTS.mfr and device:get_model() == FINGERPRINTS.model then + return true, require("aqara-knob") + end + return false +end + +return is_aqara_products diff --git a/drivers/SmartThings/zigbee-button/src/aqara-knob/init.lua b/drivers/SmartThings/zigbee-button/src/aqara-knob/init.lua new file mode 100644 index 0000000000..3b979baa24 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/aqara-knob/init.lua @@ -0,0 +1,141 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local battery_defaults = require "st.zigbee.defaults.battery_defaults" +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +local utils = require "st.utils" +local button_utils = require "button_utils" + +local PowerConfiguration = clusters.PowerConfiguration +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F +local MULTISTATE_INPUT_CLUSTER_ID = 0x0012 +local PRESENT_ATTRIBUTE_ID = 0x0055 +local ROTATION_MONITOR_ID = 0x0232 + +local AQARA_KNOB = { + ["lumi.remote.rkba01"] = { mfr = "LUMI", type = "CR2032", quantity = 2 }, -- Aqara Wireless Knob Switch H1 +} + +local function device_init(driver, device) + local configuration = { + { + cluster = PowerConfiguration.ID, + attribute = PowerConfiguration.attributes.BatteryVoltage.ID, + minimum_interval = 30, + maximum_interval = 3600, + data_type = PowerConfiguration.attributes.BatteryVoltage.base_type, + reportable_change = 1 + } + } + + battery_defaults.build_linear_voltage_init(2.6, 3.0)(driver, device) + for _, attribute in ipairs(configuration) do + device:add_configured_attribute(attribute) + end +end + +local function device_added(self, device) + local model = device:get_model() + local type = AQARA_KNOB[model].type or "CR2032" + local quantity = AQARA_KNOB[model].quantity or 1 + + 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.batteryLevel.battery.normal()) + device:emit_event(capabilities.batteryLevel.type(type)) + device:emit_event(capabilities.batteryLevel.quantity(quantity)) + device:emit_event(capabilities.knob.rotateAmount(0)) + device:emit_event(capabilities.knob.heldRotateAmount(0)) +end + +local function do_configure(driver, device) + device:configure() + -- Set manufacturer-specific attribute to enable "Operation Mode" for rotation reports + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1)) + device:emit_event(capabilities.knob.supportedAttributes({"rotateAmount", "heldRotateAmount"}, {state_change = true})) +end + +local function button_monitor_handler(driver, device, value, zb_rx) + local val = value.value + + if val == 1 then -- push + device:emit_event(capabilities.button.button.pushed({ state_change = true })) + elseif val == 2 then -- double push + device:emit_event(capabilities.button.button.double({ state_change = true })) + elseif val == 0 then -- down_hold + device:emit_event(capabilities.button.button.held({ state_change = true })) + end +end + +local function rotation_monitor_per_handler(driver, device, value, zb_rx) + local SENSITIVITY_KEY = "stse.knobSensitivity" + local SENSITIVITY_FACTORS = {0.5, 1.0, 2.0} + + local end_point = zb_rx.address_header.src_endpoint.value + local raw_val = utils.round(value.value) + + if raw_val > 0x7FFF then + raw_val = raw_val - 0x10000 + end + + local sensitivity = tonumber(device.preferences[SENSITIVITY_KEY]) + local factor = SENSITIVITY_FACTORS[sensitivity] or 1.0 + local intermediate_val = raw_val * factor + local sign = (intermediate_val > 0 and 1) or (intermediate_val < 0 and -1) or 0 + local val = math.floor(math.abs(intermediate_val) + 0.5) * sign + val = math.max(-100, math.min(100, val)) + + if val == 0 then + return + elseif end_point == 0x47 then -- normal + device:emit_event(capabilities.knob.rotateAmount({value = val}, {state_change = true})) + elseif end_point == 0x48 then -- press + device:emit_event(capabilities.knob.heldRotateAmount({value = val}, {state_change = true})) + end +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 + + device:emit_event(capabilities.batteryLevel.battery(batteryLevel)) +end + +local aqara_knob_switch_handler = { + NAME = "Aqara Wireless Knob Switch Handler", + lifecycle_handlers = { + init = device_init, + added = device_added, + doConfigure = do_configure + }, + zigbee_handlers = { + attr = { + [MULTISTATE_INPUT_CLUSTER_ID] = { + [PRESENT_ATTRIBUTE_ID] = button_monitor_handler + }, + [PRIVATE_CLUSTER_ID] = { + [ROTATION_MONITOR_ID] = rotation_monitor_per_handler + }, + [PowerConfiguration.ID] = { + [PowerConfiguration.attributes.BatteryVoltage.ID] = battery_level_handler + }, + } + }, + can_handle = require("aqara-knob.can_handle"), +} + +return aqara_knob_switch_handler \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-button/src/sub_drivers.lua b/drivers/SmartThings/zigbee-button/src/sub_drivers.lua index 47fe5ff9c4..bec1f76ac1 100644 --- a/drivers/SmartThings/zigbee-button/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-button/src/sub_drivers.lua @@ -4,6 +4,7 @@ local lazy_load_if_possible = require "lazy_load_subdriver" local sub_drivers = { lazy_load_if_possible("aqara"), + lazy_load_if_possible("aqara-knob"), lazy_load_if_possible("pushButton"), lazy_load_if_possible("frient"), lazy_load_if_possible("zigbee-multi-button"), diff --git a/drivers/SmartThings/zigbee-button/src/test/test_aqara_knob_switch.lua b/drivers/SmartThings/zigbee-button/src/test/test_aqara_knob_switch.lua new file mode 100644 index 0000000000..69ae937aff --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/test/test_aqara_knob_switch.lua @@ -0,0 +1,287 @@ +-- Copyright 2026 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 capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" + + +local MULTISTATE_INPUT_CLUSTER_ID = 0x0012 +local PRESENT_ATTRIBUTE_ID = 0x0055 +local PowerConfiguration = clusters.PowerConfiguration + +local MFG_CODE = 0x115F +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-knob-switch.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.remote.rkba01", + server_clusters = { 0x0001 } + } + } + } +) +mock_device:set_field("preferences", {["stse.knobSensitivity"] = 2}, {persist = true}) + +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( + "Handle added lifecycle", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed", "held", "double" }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.button.button.pushed({ state_change = false }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.batteryLevel.battery.normal())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.batteryLevel.type("CR2032"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.batteryLevel.quantity(2))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.knob.rotateAmount(0))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.knob.heldRotateAmount(0))) + end +) + +test.register_coroutine_test( + "Handle doConfigure lifecycle", + function() + 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) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:configure_reporting(mock_device, 30, 3600, 1) + }) + 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) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.knob.supportedAttributes({"rotateAmount", "heldRotateAmount"}, {state_change = true}))) + end +) + +test.register_coroutine_test( + "rotation_monitor_per_handler - normal", + function() + local attr_report_data = { + { 0x0232, data_types.Uint16.ID, 0x0001 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, + attr_report_data, MFG_CODE):from_endpoint(0x47) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.knob.rotateAmount({value = 1}, {state_change = true}))) + end +) + +test.register_coroutine_test( + "rotation_monitor_per_handler - max clamp at 100", + function() + local attr_report_data = {{ 0x0232, data_types.Uint16.ID, 200 }} + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, + attr_report_data, MFG_CODE):from_endpoint(0x47) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.knob.rotateAmount({value = 100}, {state_change = true}))) + end +) + +test.register_coroutine_test( + "rotation_monitor_per_handler - min clamp at -100", + function() + local attr_report_data = {{ 0x0232, data_types.Uint16.ID, 65386 }} + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, + attr_report_data, MFG_CODE):from_endpoint(0x47) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.knob.rotateAmount({value = -100}, {state_change = true}))) + end +) + +test.register_coroutine_test( + "rotation_monitor_per_handler - sensitivity 0.5x", + function() + mock_device:set_field("preferences", {["stse.knobSensitivity"] = 1}) + local attr_report_data = {{ 0x0232, data_types.Uint16.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):from_endpoint(0x47) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.knob.rotateAmount({value = 3}, {state_change = true}))) + end +) + +test.register_coroutine_test( + "rotation_monitor_per_handler - sensitivity 2.0x", + function() + mock_device:set_field("preferences", {["stse.knobSensitivity"] = 3}) + local attr_report_data = {{ 0x0232, data_types.Uint16.ID, 5 }} + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, + attr_report_data, MFG_CODE):from_endpoint(0x47) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.knob.rotateAmount({value = 5}, {state_change = true}))) + end +) + +test.register_coroutine_test( + "rotation_monitor_per_handler - held rotate amount", + function() + mock_device:set_field("preferences", {["stse.knobSensitivity"] = 2}) + local attr_report_data = {{ 0x0232, data_types.Uint16.ID, 15 }} + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, + attr_report_data, MFG_CODE):from_endpoint(0x48) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.knob.heldRotateAmount({value = 15}, {state_change = true}))) + end +) + +test.register_coroutine_test( + "rotation_monitor_per_handler - press", + function() + local attr_report_data = { + { 0x0232, data_types.Uint16.ID, 0x0001 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, + attr_report_data, MFG_CODE):from_endpoint(0x48) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.knob.heldRotateAmount({value = 1}, {state_change = true}))) + end +) + +test.register_coroutine_test( + "Reported button should be handled: pushed true", + function() + local attr_report_data = { + { PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0001 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, MULTISTATE_INPUT_CLUSTER_ID, + attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.button.button.pushed({ state_change = true }))) + end +) + +test.register_coroutine_test( + "Reported button should be handled: double true", + function() + local attr_report_data = { + { PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0002 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, MULTISTATE_INPUT_CLUSTER_ID, + attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.button.button.double({ state_change = true }))) + end +) + +test.register_coroutine_test( + "Reported button should be handled: held true", + function() + local attr_report_data = { + { PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0000 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, MULTISTATE_INPUT_CLUSTER_ID, + attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.button.button.held({ state_change = true }))) + end +) + +test.register_message_test( + "Battery Level - Normal", + { + { + 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.batteryLevel.battery("normal")) + } + } +) +test.register_message_test( + "Battery Level - Warning", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device, 27) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.batteryLevel.battery("warning")) + } + } +) +test.register_message_test( + "Battery Level - Critical", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device, 20) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.batteryLevel.battery("critical")) + } + } +) + +test.run_registered_tests() From 9754f298097606c655633d0482ab73bbfba8a3f5 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Thu, 9 Apr 2026 00:08:38 -0500 Subject: [PATCH 217/538] Reinit capabilities upon feature change when profile is unchanged --- .../camera_utils/device_configuration.lua | 4 ++ .../src/sub_drivers/camera/init.lua | 6 +- .../src/test/test_matter_camera.lua | 65 +++++++++++++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) 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 index f709d25420..88b3ccd1b7 100644 --- 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 @@ -42,6 +42,7 @@ function CameraDeviceConfiguration.create_child_devices(driver, device) end function CameraDeviceConfiguration.match_profile(device, status_light_enabled_present, status_light_brightness_present) + local profile_update_requested = false local optional_supported_component_capabilities = {} local main_component_capabilities = {} local status_led_component_capabilities = {} @@ -145,12 +146,15 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr end if camera_utils.optional_capabilities_list_changed(optional_supported_component_capabilities, device.profile.components) then + profile_update_requested = true 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 + + return profile_update_requested end local function init_webrtc(device) diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index 91ebe03ebc..1dbad05698 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -49,12 +49,14 @@ end function CameraLifecycleHandlers.info_changed(driver, device, event, args) local software_version_changed = device.matter_version ~= nil and args.old_st_store.matter_version ~= nil and device.matter_version.software ~= args.old_st_store.matter_version.software + local profile_update_requested = false + local profile_changed = not switch_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) if software_version_changed then - camera_cfg.match_profile(device, false, false) + profile_update_requested = camera_cfg.match_profile(device, false, false) end - if not switch_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then + if profile_changed or (software_version_changed and not profile_update_requested) 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 diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 631060f79b..d248f585b5 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -405,6 +405,71 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Software version change should initialize camera capabilities when profile is unchanged", + function() + local camera_handler = require "sub_drivers.camera" + local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" + local button_cfg = require("switch_utils.device_configuration").ButtonCfg + + local match_profile_called = false + local init_called = false + local subscribe_called = false + local configure_buttons_called = false + + local fake_device = { + matter_version = { hardware = 1, software = 3 }, + profile = { id = "camera" }, + endpoints = { + { + endpoint_id = CAMERA_EP, + device_types = { + {device_type_id = 0x0142, device_type_revision = 1} -- Camera + } + }, + { + endpoint_id = DOORBELL_EP, + device_types = { + {device_type_id = 0x0143, device_type_revision = 1} -- Doorbell + } + } + }, + subscribe = function() subscribe_called = true end, + get_endpoints = function() return { DOORBELL_EP } end, + } + + local original_match_profile = camera_cfg.match_profile + local original_init = camera_cfg.initialize_camera_capabilities + local original_configure_buttons = button_cfg.configure_buttons + + camera_cfg.match_profile = function() + match_profile_called = true + return false + end + camera_cfg.initialize_camera_capabilities = function() init_called = true end + button_cfg.configure_buttons = function() configure_buttons_called = true end + + camera_handler.lifecycle_handlers.infoChanged(nil, fake_device, nil, { + old_st_store = { + matter_version = { hardware = 1, software = 1 }, + profile = fake_device.profile, + } + }) + + camera_cfg.match_profile = original_match_profile + camera_cfg.initialize_camera_capabilities = original_init + button_cfg.configure_buttons = original_configure_buttons + + assert(match_profile_called, "match_profile should be called on software version change") + assert(init_called, "initialize_camera_capabilities should be called") + assert(subscribe_called, "subscribe should be called") + assert(configure_buttons_called, "configure_buttons should be called") + end, + { + min_api_version = 17 + } +) + test.register_coroutine_test( "Reports mapping to EnabledState capability data type should generate appropriate events", function() From 1094ae337691ab5bdd158b21c8e54508cacfa7ae Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Thu, 9 Apr 2026 13:02:41 -0500 Subject: [PATCH 218/538] Handle FeatureMap change, fix profile matching & reinit --- .../camera_handlers/attribute_handlers.lua | 7 +- .../camera_utils/device_configuration.lua | 302 ++++++++++++++---- .../camera/camera_utils/fields.lua | 6 + .../sub_drivers/camera/camera_utils/utils.lua | 25 +- .../src/sub_drivers/camera/init.lua | 27 +- .../src/test/test_matter_camera.lua | 47 ++- 6 files changed, 328 insertions(+), 86 deletions(-) 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 index 140ba6d313..b32b1dd55c 100644 --- 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 @@ -466,7 +466,12 @@ function CameraAttributeHandlers.camera_av_stream_management_attribute_list_hand 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) + camera_cfg.update_status_light_attribute_presence(device, status_light_enabled_present, status_light_brightness_present) + camera_cfg.reconcile_profile_and_capabilities(device) +end + +function CameraAttributeHandlers.camera_feature_map_handler(driver, device, ib, response) + camera_cfg.reconcile_profile_and_capabilities(device) end return CameraAttributeHandlers 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 index 88b3ccd1b7..c2ea259aa1 100644 --- 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 @@ -12,6 +12,152 @@ local switch_utils = require "switch_utils.utils" local CameraDeviceConfiguration = {} +local managed_capability_map = { + { key = "webrtc", capability = capabilities.webrtc }, + { key = "ptz", capability = capabilities.mechanicalPanTiltZoom }, + { key = "zone_management", capability = capabilities.zoneManagement }, + { key = "local_media_storage", capability = capabilities.localMediaStorage }, + { key = "audio_recording", capability = capabilities.audioRecording }, + { key = "video_stream_settings", capability = capabilities.videoStreamSettings }, + { key = "camera_privacy_mode", capability = capabilities.cameraPrivacyMode }, +} + +local function get_status_light_presence(device) + return device:get_field(camera_fields.STATUS_LIGHT_ENABLED_PRESENT), + device:get_field(camera_fields.STATUS_LIGHT_BRIGHTNESS_PRESENT) +end + +local function set_status_light_presence(device, status_light_enabled_present, status_light_brightness_present) + device:set_field(camera_fields.STATUS_LIGHT_ENABLED_PRESENT, status_light_enabled_present == true, { persist = true }) + device:set_field(camera_fields.STATUS_LIGHT_BRIGHTNESS_PRESENT, status_light_brightness_present == true, { persist = true }) +end + +local function build_webrtc_supported_features() + return { + bundle = true, + order = "audio/video", + audio = "sendrecv", + video = "recvonly", + turnSource = "player", + supportTrickleICE = true + } +end + +local function build_ptz_supported_attributes(device) + 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 + return supported_attributes +end + +local function build_zone_management_supported_features(device) + local 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 + return supported_features +end + +local function build_local_media_storage_supported_attributes(device) + 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 + return supported_attributes +end + +local function build_video_stream_settings_supported_features(device) + 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 + return supported_features +end + +local function build_camera_privacy_supported_attributes() + return { "softRecordingPrivacyMode", "softLivestreamPrivacyMode" } +end + +local function build_camera_privacy_supported_commands() + return { "setSoftRecordingPrivacyMode", "setSoftLivestreamPrivacyMode" } +end + +local function capabilities_needing_reinit(device) + local main = camera_fields.profile_components.main + + local capabilities_to_reinit = {} + + local function state_differs(capability, attribute_name, expected) + local current = device:get_latest_state(main, capability.ID, attribute_name) + return not switch_utils.deep_equals(current, expected, { ignore_functions = true }) + end + + if device:supports_capability(capabilities.webrtc) and + state_differs(capabilities.webrtc, capabilities.webrtc.supportedFeatures.NAME, build_webrtc_supported_features()) then + capabilities_to_reinit.webrtc = true + end + + if device:supports_capability(capabilities.mechanicalPanTiltZoom) and + state_differs(capabilities.mechanicalPanTiltZoom, capabilities.mechanicalPanTiltZoom.supportedAttributes.NAME, build_ptz_supported_attributes(device)) then + capabilities_to_reinit.ptz = true + end + + if device:supports_capability(capabilities.zoneManagement) and + state_differs(capabilities.zoneManagement, capabilities.zoneManagement.supportedFeatures.NAME, build_zone_management_supported_features(device)) then + capabilities_to_reinit.zone_management = true + end + + if device:supports_capability(capabilities.localMediaStorage) and + state_differs(capabilities.localMediaStorage, capabilities.localMediaStorage.supportedAttributes.NAME, build_local_media_storage_supported_attributes(device)) then + capabilities_to_reinit.local_media_storage = true + end + + if device:supports_capability(capabilities.audioRecording) then + local audio_enabled_state = device:get_latest_state(main, capabilities.audioRecording.ID, capabilities.audioRecording.audioRecording.NAME) + if audio_enabled_state == nil then + capabilities_to_reinit.audio_recording = true + end + end + + if device:supports_capability(capabilities.videoStreamSettings) and + state_differs(capabilities.videoStreamSettings, capabilities.videoStreamSettings.supportedFeatures.NAME, build_video_stream_settings_supported_features(device)) then + capabilities_to_reinit.video_stream_settings = true + end + + if device:supports_capability(capabilities.cameraPrivacyMode) and + (state_differs(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedAttributes.NAME, build_camera_privacy_supported_attributes()) or + state_differs(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedCommands.NAME, build_camera_privacy_supported_commands())) then + capabilities_to_reinit.camera_privacy_mode = true + end + + return capabilities_to_reinit +end + function CameraDeviceConfiguration.create_child_devices(driver, device) local num_floodlight_eps = 0 local parent_child_device = false @@ -41,7 +187,8 @@ function CameraDeviceConfiguration.create_child_devices(driver, device) end end -function CameraDeviceConfiguration.match_profile(device, status_light_enabled_present, status_light_brightness_present) +function CameraDeviceConfiguration.match_profile(device) + local status_light_enabled_present, status_light_brightness_present = get_status_light_presence(device) local profile_update_requested = false local optional_supported_component_capabilities = {} local main_component_capabilities = {} @@ -159,68 +306,29 @@ 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 - } - })) + device:emit_event_for_endpoint(transport_provider_ep_ids[1], capabilities.webrtc.supportedFeatures(build_webrtc_supported_features())) 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)) + device:emit_event_for_endpoint(av_settings_ep_ids[1], capabilities.mechanicalPanTiltZoom.supportedAttributes(build_ptz_supported_attributes(device))) 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)) + device:emit_event_for_endpoint(zone_management_ep_ids[1], capabilities.zoneManagement.supportedFeatures(build_zone_management_supported_features(device))) 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)) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.localMediaStorage.supportedAttributes(build_local_media_storage_supported_attributes(device))) end end @@ -239,33 +347,16 @@ 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)) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.videoStreamSettings.supportedFeatures(build_video_stream_settings_supported_features(device))) 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)) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedAttributes(build_camera_privacy_supported_attributes())) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedCommands(build_camera_privacy_supported_commands())) end end @@ -279,6 +370,89 @@ function CameraDeviceConfiguration.initialize_camera_capabilities(device) init_camera_privacy_mode(device) end +function CameraDeviceConfiguration.initialize_camera_capabilities_and_subscriptions(device) + CameraDeviceConfiguration.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 + +local function initialize_selected_camera_capabilities(device, capabilities_to_reinit) + local reinit_targets = capabilities_to_reinit or {} + + if reinit_targets.webrtc then + init_webrtc(device) + end + if reinit_targets.ptz then + init_ptz(device) + end + if reinit_targets.zone_management then + init_zone_management(device) + end + if reinit_targets.local_media_storage then + init_local_media_storage(device) + end + if reinit_targets.audio_recording then + init_audio_recording(device) + end + if reinit_targets.video_stream_settings then + init_video_stream_settings(device) + end + if reinit_targets.camera_privacy_mode then + init_camera_privacy_mode(device) + end +end + +local function profile_capability_set(profile) + local capability_set = {} + for _, component in pairs((profile or {}).components or {}) do + for _, capability in ipairs(component.capabilities or {}) do + if capability.id ~= nil then + capability_set[capability.id] = true + end + end + end + return capability_set +end + +local function changed_capabilities_from_profiles(old_profile, new_profile) + local flags = {} + local old_set = profile_capability_set(old_profile) + local new_set = profile_capability_set(new_profile) + + for _, managed in ipairs(managed_capability_map) do + local id = managed.capability.ID + if old_set[id] ~= new_set[id] and new_set[id] == true then + flags[managed.key] = true + end + end + + return flags +end + +function CameraDeviceConfiguration.reconcile_profile_and_capabilities(device) + local profile_update_requested = CameraDeviceConfiguration.match_profile(device) + if not profile_update_requested then + local capabilities_to_reinit = capabilities_needing_reinit(device) + initialize_selected_camera_capabilities(device, capabilities_to_reinit) + end + return profile_update_requested +end + +function CameraDeviceConfiguration.update_status_light_attribute_presence(device, status_light_enabled_present, status_light_brightness_present) + set_status_light_presence(device, status_light_enabled_present, status_light_brightness_present) +end + +function CameraDeviceConfiguration.reinitialize_changed_camera_capabilities_and_subscriptions(device, old_profile, new_profile) + local changed_capabilities = changed_capabilities_from_profiles(old_profile, new_profile) + initialize_selected_camera_capabilities(device, changed_capabilities) + 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 + function CameraDeviceConfiguration.update_doorbell_component_map(device, ep) local component_map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {} component_map.doorbell = ep 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 index 000008fa51..c88f177707 100644 --- 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 @@ -14,6 +14,12 @@ CameraFields.MAX_RESOLUTION = "__max_resolution" CameraFields.MIN_RESOLUTION = "__min_resolution" CameraFields.TRIGGERED_ZONES = "__triggered_zones" CameraFields.DPTZ_VIEWPORTS = "__dptz_viewports" +CameraFields.STATUS_LIGHT_ENABLED_PRESENT = "__status_light_enabled_present" +CameraFields.STATUS_LIGHT_BRIGHTNESS_PRESENT = "__status_light_brightness_present" + +CameraFields.CameraAVSMFeatureMapAttr = { ID = 0xFFFC, cluster = clusters.CameraAvStreamManagement.ID } +CameraFields.CameraAVSULMFeatureMapAttr = { ID = 0xFFFC, cluster = clusters.CameraAvSettingsUserLevelManagement.ID } +CameraFields.ZoneManagementFeatureMapAttr = { ID = 0xFFFC, cluster = clusters.ZoneManagement.ID } CameraFields.PAN_IDX = "PAN" CameraFields.TILT_IDX = "TILT" 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 index 5793c1d9fc..f3b92aa3b6 100644 --- 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 @@ -6,6 +6,7 @@ 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 cluster_base = require "st.matter.cluster_base" local CameraUtils = {} @@ -297,10 +298,19 @@ function CameraUtils.subscribe(device) local subscribe_request = im.InteractionRequest(im.InteractionRequest.RequestType.SUBSCRIBE, {}) local devices_seen, capabilities_seen, attributes_seen, events_seen = {}, {}, {}, {} + local additional_attributes = {} 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) + table.insert(additional_attributes, clusters.CameraAvStreamManagement.attributes.AttributeList) + table.insert(additional_attributes, camera_fields.CameraAVSMFeatureMapAttr) + end + + if #device:get_endpoints(clusters.CameraAvSettingsUserLevelManagement.ID) > 0 then + table.insert(additional_attributes, camera_fields.CameraAVSULMFeatureMapAttr) + end + + if #device:get_endpoints(clusters.ZoneManagement.ID) > 0 then + table.insert(additional_attributes, camera_fields.ZoneManagementFeatureMapAttr) end for _, endpoint_info in ipairs(device.endpoints) do @@ -313,6 +323,17 @@ function CameraUtils.subscribe(device) end end + for _, attr in ipairs(additional_attributes) 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 + if #subscribe_request.info_blocks > 0 then device:send(subscribe_request) 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 index 1dbad05698..a72aa0b234 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -6,7 +6,6 @@ ------------------------------------------------------------------------------------- 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" @@ -33,7 +32,7 @@ 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) + camera_cfg.match_profile(device) end camera_cfg.create_child_devices(driver, device) camera_cfg.initialize_camera_capabilities(device) @@ -42,26 +41,19 @@ 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) + camera_cfg.match_profile(device) end end function CameraLifecycleHandlers.info_changed(driver, device, event, args) local software_version_changed = device.matter_version ~= nil and args.old_st_store.matter_version ~= nil and device.matter_version.software ~= args.old_st_store.matter_version.software - local profile_update_requested = false local profile_changed = not switch_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) if software_version_changed then - profile_update_requested = camera_cfg.match_profile(device, false, false) - end - - if profile_changed or (software_version_changed and not profile_update_requested) 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 + camera_cfg.reconcile_profile_and_capabilities(device) + elseif profile_changed then + camera_cfg.reinitialize_changed_camera_capabilities_and_subscriptions(device, args.old_st_store.profile, device.profile) end end @@ -107,7 +99,8 @@ local camera_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.CameraAvStreamManagement.attributes.AttributeList.ID] = attribute_handlers.camera_av_stream_management_attribute_list_handler, + [camera_fields.CameraAVSMFeatureMapAttr.ID] = attribute_handlers.camera_feature_map_handler }, [clusters.CameraAvSettingsUserLevelManagement.ID] = { [clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition.ID] = attribute_handlers.ptz_position_handler, @@ -118,14 +111,16 @@ local camera_handler = { [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.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams.ID] = attribute_handlers.dptz_streams_handler + [clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams.ID] = attribute_handlers.dptz_streams_handler, + [camera_fields.CameraAVSULMFeatureMapAttr.ID] = attribute_handlers.camera_feature_map_handler }, [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.ZoneManagement.attributes.Sensitivity.ID] = attribute_handlers.sensitivity_handler, + [camera_fields.ZoneManagementFeatureMapAttr.ID] = attribute_handlers.camera_feature_map_handler }, [clusters.Chime.ID] = { [clusters.Chime.attributes.InstalledChimeSounds.ID] = attribute_handlers.installed_chime_sounds_handler, diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index d248f585b5..7f1f72b108 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -2,7 +2,9 @@ -- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" +local cluster_base = require "st.matter.cluster_base" local clusters = require "st.matter.clusters" +local camera_fields = require "sub_drivers.camera.camera_utils.fields" local t_utils = require "integration_test.utils" local test = require "integration_test" local uint32 = require "st.matter.data_types.Uint32" @@ -154,6 +156,9 @@ local function test_init() parent_assigned_child_key = string.format("%d", FLOODLIGHT_EP) }) subscribe_request = subscribed_attributes[1]:subscribe(mock_device) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, camera_fields.CameraAVSMFeatureMapAttr.cluster, camera_fields.CameraAVSMFeatureMapAttr.ID)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, camera_fields.CameraAVSULMFeatureMapAttr.cluster, camera_fields.CameraAVSULMFeatureMapAttr.ID)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, camera_fields.ZoneManagementFeatureMapAttr.cluster, camera_fields.ZoneManagementFeatureMapAttr.ID)) for i, attr in ipairs(subscribed_attributes) do if i > 1 then subscribe_request:merge(attr:subscribe(mock_device)) end end @@ -435,6 +440,7 @@ test.register_coroutine_test( } }, subscribe = function() subscribe_called = true end, + supports_capability = function() return false end, get_endpoints = function() return { DOORBELL_EP } end, } @@ -461,9 +467,36 @@ test.register_coroutine_test( button_cfg.configure_buttons = original_configure_buttons assert(match_profile_called, "match_profile should be called on software version change") - assert(init_called, "initialize_camera_capabilities should be called") - assert(subscribe_called, "subscribe should be called") - assert(configure_buttons_called, "configure_buttons should be called") + assert(not init_called, "initialize_camera_capabilities should not be called when capability state is unchanged") + assert(not subscribe_called, "subscribe should not be called when capability state is unchanged") + assert(not configure_buttons_called, "configure_buttons should not be called when capability state is unchanged") + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Camera FeatureMap change should reinitialize capabilities when profile is unchanged", + function() + local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" + + local reconcile_called = false + local original_reconcile = camera_cfg.reconcile_profile_and_capabilities + + camera_cfg.reconcile_profile_and_capabilities = function(_) + reconcile_called = true + return false + end + + test.socket.matter:__queue_receive({ + mock_device.id, + cluster_base.build_test_report_data(mock_device, CAMERA_EP, camera_fields.CameraAVSMFeatureMapAttr.cluster, camera_fields.CameraAVSMFeatureMapAttr.ID, uint32(0)) + }) + test.wait_for_events() + + camera_cfg.reconcile_profile_and_capabilities = original_reconcile + assert(reconcile_called, "reconcile_profile_and_capabilities should be called") end, { min_api_version = 17 @@ -2864,6 +2897,11 @@ test.register_coroutine_test( function() update_device_profile() test.wait_for_events() + + local camera_cfg = require("sub_drivers.camera.camera_utils.device_configuration") + local original_reconcile = camera_cfg.reconcile_profile_and_capabilities + camera_cfg.reconcile_profile_and_capabilities = function(...) return false end + test.socket.matter:__queue_receive({ mock_device.id, clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device, CAMERA_EP, { @@ -2871,6 +2909,9 @@ test.register_coroutine_test( uint32(clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) }) }) + test.wait_for_events() + + camera_cfg.reconcile_profile_and_capabilities = original_reconcile end, { min_api_version = 17 From ceb767ca2b23e926251d75a5228b2c0ff62cb9a6 Mon Sep 17 00:00:00 2001 From: laity-w-sudo <1090741189@qq.com> Date: Fri, 10 Apr 2026 04:00:07 +0800 Subject: [PATCH 219/538] Add Sonoff SNZB-04PR2 (WWSTCERT-10731) and SNZB-04P (WWSTCERT-10704) Smart Scene Contact into zigbee-contact (#2539) * Add Sonoff SNZB-04PR2 Smart Scene Contact into zigbee-contact * Add Sonoff profile into zigbee-contact * Delete the program, that is only submit the relevant configuration. * Modify the profile configuration of the fingerprint file * The anti-tampering function has been changed to a standard attribute --- .../SmartThings/zigbee-contact/fingerprints.yml | 10 ++++++++++ .../profiles/contact-battery-tamper.yml | 16 ++++++++++++++++ .../zigbee-contact/src/configurations.lua | 1 - 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml diff --git a/drivers/SmartThings/zigbee-contact/fingerprints.yml b/drivers/SmartThings/zigbee-contact/fingerprints.yml index c30c3296a2..dd4bb9175c 100644 --- a/drivers/SmartThings/zigbee-contact/fingerprints.yml +++ b/drivers/SmartThings/zigbee-contact/fingerprints.yml @@ -204,6 +204,16 @@ zigbeeManufacturer: manufacturer: Third Reality, Inc model: 3RVS01031Z deviceProfileName: thirdreality-multi-sensor + - id: "SONOFF/SNZB-04P" + deviceLabel: SONOFF Contact Sensor + manufacturer: eWeLink + model: SNZB-04P + deviceProfileName: contact-battery-profile + - id: "SONOFF/SNZB-04PR2" + deviceLabel: SONOFF Contact Sensor + manufacturer: SONOFF + model: SNZB-04PR2 + deviceProfileName: contact-battery-profile - id: "Aug. Winkhaus SE/FM.V.ZB" deviceLabel: Funkkontakt FM.V.ZB manufacturer: Aug. Winkhaus SE diff --git a/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml new file mode 100644 index 0000000000..524783af37 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml @@ -0,0 +1,16 @@ +name: contact-battery-tamper +components: +- id: main + capabilities: + - id: contactSensor + version: 1 + - id: battery + version: 1 + - id: tamperAlert + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: ContactSensor diff --git a/drivers/SmartThings/zigbee-contact/src/configurations.lua b/drivers/SmartThings/zigbee-contact/src/configurations.lua index cd5ede1b6e..668e8c5ac6 100644 --- a/drivers/SmartThings/zigbee-contact/src/configurations.lua +++ b/drivers/SmartThings/zigbee-contact/src/configurations.lua @@ -26,7 +26,6 @@ local devices = { EWELINK_HEIMAN = { FINGERPRINTS = { { mfr = "eWeLink", model = "DS01" }, - { mfr = "eWeLink", model = "SNZB-04P" }, { mfr = "HEIMAN", model = "DoorSensor-N" } }, CONFIGURATION = { From 74859d7195caf7c0a6ff8b4045029a19f669f4ac Mon Sep 17 00:00:00 2001 From: Inovelli <37669481+InovelliUSA@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:34:04 -0600 Subject: [PATCH 220/538] WWSTCERT-9786 Inovelli - adding vzw31 red series dimmer switch (#2654) * Inovelli - adding vzw31 red series dimmer switch * needed to add multilevel report handler to pass test suite * adding tests for vzw31 * removing extra code for button value init --- .../SmartThings/zwave-switch/fingerprints.yml | 6 + .../profiles/inovelli-dimmer-vzw31-sn.yml | 284 +++++++++++++++ .../zwave-switch/src/inovelli/can_handle.lua | 3 +- .../zwave-switch/src/inovelli/sub_drivers.lua | 1 + .../src/inovelli/vzw31-sn/can_handle.lua | 19 + .../src/inovelli/vzw31-sn/init.lua | 77 ++++ .../zwave-switch/src/preferences.lua | 27 ++ .../src/test/test_inovelli_vzw31_sn.lua | 287 +++++++++++++++ .../src/test/test_inovelli_vzw31_sn_child.lua | 335 ++++++++++++++++++ .../test_inovelli_vzw31_sn_preferences.lua | 151 ++++++++ 10 files changed, 1189 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zwave-switch/profiles/inovelli-dimmer-vzw31-sn.yml create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/init.lua create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn.lua create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn_child.lua create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn_preferences.lua diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index 46faa7d106..92a11cd388 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -56,6 +56,12 @@ zwaveManufacturer: productType: 0x0003 productId: 0x0001 deviceProfileName: inovelli-dimmer + - id: "Inovelli/VZW31-SN" + deviceLabel: Inovelli Dimmer Red Series + manufacturerId: 0x031E + productType: 0x0015 + productId: 0x0001 + deviceProfileName: inovelli-dimmer-vzw31-sn - id: "Inovelli/VZW32-SN" deviceLabel: Inovelli mmWave Dimmer Red Series manufacturerId: 0x031E diff --git a/drivers/SmartThings/zwave-switch/profiles/inovelli-dimmer-vzw31-sn.yml b/drivers/SmartThings/zwave-switch/profiles/inovelli-dimmer-vzw31-sn.yml new file mode 100644 index 0000000000..e2da4a3239 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/profiles/inovelli-dimmer-vzw31-sn.yml @@ -0,0 +1,284 @@ +name: inovelli-dimmer-vzw31-sn +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - 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: "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: true + preferenceType: enumeration + definition: + options: + "0": "None (default)" + "1": "3-Way Dumb Switch" + "2": "3-Way Aux Switch" + "3": "Single Pole Full Sine Wave" + 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 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/can_handle.lua b/drivers/SmartThings/zwave-switch/src/inovelli/can_handle.lua index 7c0f6be77b..c8ba7ac681 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli/can_handle.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli/can_handle.lua @@ -3,6 +3,7 @@ local INOVELLI_FINGERPRINTS = { { mfr = 0x031E, prod = 0x0017, model = 0x0001 }, -- Inovelli VZW32-SN + { mfr = 0x031E, prod = 0x0015, model = 0x0001 }, -- Inovelli VZW31-SN { mfr = 0x031E, prod = 0x0001, model = 0x0001 }, -- Inovelli LZW31SN { mfr = 0x031E, prod = 0x0003, model = 0x0001 }, -- Inovelli LZW31 } @@ -17,4 +18,4 @@ local function can_handle_inovelli(opts, driver, device, ...) return false end -return can_handle_inovelli +return can_handle_inovelli \ 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 index e182120ece..2fdea81379 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli/sub_drivers.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli/sub_drivers.lua @@ -5,5 +5,6 @@ local lazy_load = require "lazy_load_subdriver" return { lazy_load("inovelli.lzw31-sn"), + lazy_load("inovelli.vzw31-sn"), lazy_load("inovelli.vzw32-sn") } diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/can_handle.lua b/drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/can_handle.lua new file mode 100644 index 0000000000..2446c06dde --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli/vzw31-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_VZW31_SN_PRODUCT_TYPE = 0x0015 +local INOVELLI_DIMMER_PRODUCT_ID = 0x0001 + +local function can_handle_vzw31_sn(opts, driver, device, ...) + if device:id_match( + INOVELLI_MANUFACTURER_ID, + INOVELLI_VZW31_SN_PRODUCT_TYPE, + INOVELLI_DIMMER_PRODUCT_ID + ) then + return true, require("inovelli.vzw31-sn") + end + return false +end + +return can_handle_vzw31_sn diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/init.lua new file mode 100644 index 0000000000..1dec88e745 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/init.lua @@ -0,0 +1,77 @@ +-- 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 cc = require "st.zwave.CommandClass" + + +local supported_button_values = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"} + + +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 ~= "LEDColorConfiguration" then + device:emit_component_event( + component, + capabilities.button.supportedButtonValues( + supported_button_values, + { 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 function onoff_level_report_handler(driver, device, cmd) + local value = cmd.args.target_value and cmd.args.target_value or cmd.args.value + device:emit_event(value == 0 and capabilities.switch.switch.off() or capabilities.switch.switch.on()) + device:emit_event(capabilities.switchLevel.level(value)) +end + +local vzw31_sn = { + NAME = "Inovelli VZW31-SN Dimmer", + lifecycle_handlers = { + added = device_added, + }, + zwave_handlers = { + [cc.SWITCH_MULTILEVEL] = { + [SwitchMultilevel.REPORT] = onoff_level_report_handler + } + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = refresh_handler + } + }, + can_handle = require("inovelli.vzw31-sn.can_handle") +} + +return vzw31_sn \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/preferences.lua b/drivers/SmartThings/zwave-switch/src/preferences.lua index 798efaddf0..09155cdd7d 100644 --- a/drivers/SmartThings/zwave-switch/src/preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/preferences.lua @@ -59,6 +59,33 @@ local devices = { switchType = {parameter_number = 22, size = 1} } }, + INOVELLI_VZW31_SN = { + MATCHING_MATRIX = { + mfrs = 0x031E, + product_types = {0x0015}, + 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}, + parameter22 = {parameter_number = 22, size = 1}, + 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}, + } + }, INOVELLI_VZW32_SN = { MATCHING_MATRIX = { mfrs = 0x031E, diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn.lua new file mode 100644 index 0000000000..a372a94eec --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn.lua @@ -0,0 +1,287 @@ +-- 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 Meter = (require "st.zwave.CommandClass.Meter")({version=3}) +local t_utils = require "integration_test.utils" + +-- Inovelli VZW31-SN device identifiers +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW31_SN_PRODUCT_TYPE = 0x0015 +local INOVELLI_VZW31_SN_PRODUCT_ID = 0x0001 +local LED_BAR_COMPONENT_NAME = "LEDColorConfiguration" + +-- Device endpoints with supported command classes +local inovelli_vzw31_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.METER}, + } + } +} + +-- Create mock device +local mock_inovelli_vzw31_sn = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("inovelli-dimmer-vzw31-sn.yml"), + zwave_endpoints = inovelli_vzw31_sn_endpoints, + zwave_manufacturer_id = INOVELLI_MANUFACTURER_ID, + zwave_product_type = INOVELLI_VZW31_SN_PRODUCT_TYPE, + zwave_product_id = INOVELLI_VZW31_SN_PRODUCT_ID +}) + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzw31_sn) +end +test.set_test_init_function(test_init) + +local supported_button_values = {"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_vzw31_sn.id, "added" }) + + for button_name, _ in pairs(mock_inovelli_vzw31_sn.profile.components) do + if button_name ~= "main" and button_name ~= LED_BAR_COMPONENT_NAME then + test.socket.capability:__expect_send( + mock_inovelli_vzw31_sn:generate_test_message( + button_name, + capabilities.button.supportedButtonValues( + supported_button_values, + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_inovelli_vzw31_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_vzw31_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_vzw31_sn, + SwitchMultilevel:Get({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }) + ) + ) + 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_vzw31_sn.id, + { capability = "switch", command = "on", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_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_vzw31_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_vzw31_sn.id, + { capability = "switch", command = "off", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_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_vzw31_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_vzw31_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_vzw31_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_vzw31_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_vzw31_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_vzw31_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_vzw31_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_vzw31_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 and meter data", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzw31_sn.id, + { capability = "refresh", command = "refresh", args = {} } + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + SwitchMultilevel:Get({}) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn_child.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn_child.lua new file mode 100644 index 0000000000..dea43715ab --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_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 VZW31-SN device identifiers +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW31_SN_PRODUCT_TYPE = 0x0015 +local INOVELLI_VZW31_SN_PRODUCT_ID = 0x0001 + +-- Device endpoints with supported command classes +local inovelli_vzw31_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-dimmer-vzw31-sn.yml"), + zwave_endpoints = inovelli_vzw31_sn_endpoints, + zwave_manufacturer_id = INOVELLI_MANUFACTURER_ID, + zwave_product_type = INOVELLI_VZW31_SN_PRODUCT_TYPE, + zwave_product_id = INOVELLI_VZW31_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 (Gen3 uses parameter 99, same as vzw32) +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 (Gen3) + 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 (Gen3) + 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 (Gen3) + 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) + 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_vzw31_sn_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn_preferences.lua new file mode 100644 index 0000000000..2b4812ad7b --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_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 VZW31-SN device identifiers +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW31_SN_PRODUCT_TYPE = 0x0015 +local INOVELLI_VZW31_SN_PRODUCT_ID = 0x0001 + +-- Device endpoints with supported command classes +local inovelli_vzw31_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_vzw31_sn = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("inovelli-dimmer-vzw31-sn.yml"), + zwave_endpoints = inovelli_vzw31_sn_endpoints, + zwave_manufacturer_id = INOVELLI_MANUFACTURER_ID, + zwave_product_type = INOVELLI_VZW31_SN_PRODUCT_TYPE, + zwave_product_id = INOVELLI_VZW31_SN_PRODUCT_ID +}) + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzw31_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_vzw31_sn:generate_info_changed({preferences = {parameter1 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_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_vzw31_sn:generate_info_changed({preferences = {parameter52 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_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_vzw31_sn:generate_info_changed({preferences = {parameter158 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + Configuration:Set({ + parameter_number = 158, + configuration_value = new_param_value, + size = 1 + }) + ) + ) + end + ) +end + +-- Test parameter 19 (2-byte parameter); must be non-default (default 3600) or driver won't send Configuration:Set +do + local new_param_value = 1800 + test.register_coroutine_test( + "Parameter 19 should be updated in the device configuration after change", + function() + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzw31_sn:generate_info_changed({preferences = {parameter19 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + Configuration:Set({ + parameter_number = 19, + 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_vzw31_sn:generate_info_changed({preferences = {notificationChild = new_param_value}})) + + -- Expect child device creation + mock_inovelli_vzw31_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_vzw31_sn.id, + parent_assigned_child_key = "notification" + }) + end + ) +end + +test.run_registered_tests() From 6f306e39302b74428d57e292096b02930090640f Mon Sep 17 00:00:00 2001 From: JerryYang01 <40193349+JerryYang01@users.noreply.github.com> Date: Fri, 10 Apr 2026 04:37:55 +0800 Subject: [PATCH 221/538] Fix pad19 fingerprint (#2878) * Add fingerprint for PAD19 dimmer * Add fingerprint for PAD19 dimmer * Add fingerprint for PAD19 dimmer --- drivers/SmartThings/zwave-switch/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index 92a11cd388..d0d10f2e5a 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -922,6 +922,12 @@ zwaveManufacturer: manufacturerId: 0x010F productType: 0x0102 deviceProfileName: fibaro-dimmer-2 + - id: 013C/0005/008A + deviceLabel: Philio Dimmer Switch PAD19 + manufacturerId: 0x013C + productType: 0x0005 + productId: 0x008A + deviceProfileName: switch-level #Zooz - id: "Zooz/ZEN05" deviceLabel: Zooz Outdoor Plug ZEN05 From 3d88670a3fb0d54cd23509119532bef19456f78e Mon Sep 17 00:00:00 2001 From: Jeff Page Date: Thu, 9 Apr 2026 15:49:33 -0500 Subject: [PATCH 222/538] WWSTCERT-9857 Add Zooz ZSE50 to zwave-siren (for WWST Cert) (#2681) * Add Zooz ZSE50 to zwave-siren --- .../SmartThings/zwave-siren/fingerprints.yml | 8 +- .../zwave-siren/profiles/zooz-zse50.yml | 309 ++++++++ drivers/SmartThings/zwave-siren/src/init.lua | 3 +- .../zwave-siren/src/preferences.lua | 28 +- .../zwave-siren/src/sub_drivers.lua | 1 + .../zwave-siren/src/test/test_zooz_zse50.lua | 717 ++++++++++++++++++ .../zwave-siren/src/zooz-zse50/can_handle.lua | 14 + .../src/zooz-zse50/fingerprints.lua | 8 + .../zwave-siren/src/zooz-zse50/init.lua | 367 +++++++++ 9 files changed, 1452 insertions(+), 3 deletions(-) create mode 100644 drivers/SmartThings/zwave-siren/profiles/zooz-zse50.yml create mode 100644 drivers/SmartThings/zwave-siren/src/test/test_zooz_zse50.lua create mode 100644 drivers/SmartThings/zwave-siren/src/zooz-zse50/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/zooz-zse50/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-siren/src/zooz-zse50/init.lua diff --git a/drivers/SmartThings/zwave-siren/fingerprints.yml b/drivers/SmartThings/zwave-siren/fingerprints.yml index 1e36e4af81..4b25211366 100644 --- a/drivers/SmartThings/zwave-siren/fingerprints.yml +++ b/drivers/SmartThings/zwave-siren/fingerprints.yml @@ -1,10 +1,16 @@ zwaveManufacturer: - - id: "Zooz" + - id: "Zooz/ZSE19" deviceLabel: Zooz Multisiren manufacturerId: 0x027A productType: 0x000C productId: 0x0003 deviceProfileName: multifunctional-siren + - id: "Zooz/ZSE50" + deviceLabel: Zooz ZSE50 Siren and Chime + manufacturerId: 0x027A + productType: 0x0004 + productId: 0x0369 + deviceProfileName: zooz-zse50 - id: "Everspring" deviceLabel: Everspring Siren manufacturerId: 0x0060 diff --git a/drivers/SmartThings/zwave-siren/profiles/zooz-zse50.yml b/drivers/SmartThings/zwave-siren/profiles/zooz-zse50.yml new file mode 100644 index 0000000000..866b88f436 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/profiles/zooz-zse50.yml @@ -0,0 +1,309 @@ +# Zooz ZSE50 Siren/Chime +# With deviceConfig - allows setting a tone from routines +name: zooz-zse50 +components: + - id: main + capabilities: + - id: alarm + version: 1 + - id: chime + version: 1 + - id: mode + version: 1 + - id: powerSource + version: 1 + - id: audioVolume + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Siren +### PREFERENCES ### +preferences: + #param 1 + - name: "playbackMode" + title: "Playback Mode" + description: "* = Default; Set siren playback mode: once (0), loop for x seconds (1), loop x times (2), loop until cancel (3), no sound (4)." + required: false + preferenceType: enumeration + definition: + options: + 0: "Play once *" + 1: "Play in loop for set duration" + 2: "Play in loop for set number" + 3: "Play in loop until stopped" + 4: "No sound, LED only" + default: 0 + #param 2 + - name: "playbackDuration" + title: "Playback Duration" + description: "Default: 180; Set playback duration for the siren (in seconds) when the siren is in playback mode 1." + required: false + preferenceType: integer + definition: + minimum: 1 + maximum: 900 + default: 180 + #param 3 + - name: "playbackLoop" + title: "Playback Loop Count" + description: "Default: 1; Set the number of playback loops for the selected tone when the siren is in playback mode 2." + required: false + preferenceType: integer + definition: + minimum: 1 + maximum: 99 + default: 1 + #param 4 + - name: "playbackTone" + title: "Playback Tone" + description: "Set the default tone for the siren playback. Choose the number of the file in the library as value. Check the 'modes' list for the id numbers" + required: false + preferenceType: integer + definition: + minimum: 1 + maximum: 50 + default: 1 + #param 5 - playbackVolume ## Handled with volume command + #param 6 + - name: "ledMode" + title: "LED Indicator Mode" + description: "* = Default; Set the LED indicator mode for the siren: off (0), strobe (1), police strobe (2), pulse (3), solid on (4). See documentation for details." + required: false + preferenceType: enumeration + definition: + options: + 0: "LED always off" + 1: "LED strobe single color *" + 2: "LED strobe red and blue" + 3: "LED pulse single color" + 4: "LED solid on single color" + default: 1 + #param 7 + - name: "ledColor" + title: "LED Indicator Color" + description: "Default: 0; Set the LED indicator color: red (0), yellow (42), green (85), indigo (127), blue (170), purple (212), or white (255). More colors available through custom values corresponding to the color wheel. See advanced documentation for details." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 255 + default: 0 + #param 8 + - name: "lowBattery" + title: "Low Battery Report" + description: "Which % level should the device report low battery to the hub." + required: false + preferenceType: enumeration + definition: + options: + 10: "10% [DEFAULT]" + 15: "15%" + 20: "20%" + 25: "25%" + 30: "30%" + 35: "35%" + 40: "40%" + default: 10 + #param 9 + - name: "ledBatteryMode" + title: "LED In Back-Up Battery Mode" + description: "* = Default; Set the LED indicator in back-up battery mode: off (0), regular LED mode (1), pulse white for full battery and red for low battery (2)." + required: false + preferenceType: enumeration + definition: + options: + 0: "LED off" + 1: "Regular LED mode *" + 2: "Pulse white for full, red for low" + default: 1 + #param 10 + - name: "btnToneSelection" + title: "Button Tone Selection" + description: "Disable tone selection from physical buttons on the siren (0). When disabled, you'll only be able to program tones using the advanced parameters in the Z-Wave UI. Expert users only, see documentation for details." + required: false + preferenceType: enumeration + definition: + options: + 0: "Disabled" + 1: "Enabled [DEFAULT]" + default: 1 + #param 11 + - name: "btnVolSelection" + title: "Button Volume Selection" + description: "Disable volume adjustment from physical buttons on the siren (0). When disabled, you'll only be able to adjust volume using the advanced parameters in the Z-Wave UI. Expert users only, see documentation for details." + required: false + preferenceType: enumeration + definition: + options: + 0: "Disabled" + 1: "Enabled [DEFAULT]" + default: 1 + #param 13 + - name: "systemVolume" + title: "System Message Volume" + description: "Default: 50; Set system message volume (0-100, 0 – mute)." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 50 + #param 14 + - name: "ledBrightness" + title: "LED Indicator Brightness" + description: "Default: 5; Choose the LED indicator's brightness level (0 – off, 10 – high brightness)." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 10 + default: 5 + #param 15 + - name: "batteryFrequency" + title: "Battery Reporting Frequency" + description: "Default: 12; Set the reporting interval for battery (1-84 hours)." + required: false + preferenceType: integer + definition: + minimum: 1 + maximum: 84 + default: 12 + #param 16 + - name: "batteryThreshold" + title: "Battery Reporting Threshold" + description: "Default: 0; Set the threshold for battery reporting in % changes. Set to 0 to disable reporting based on threshold." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 20 + default: 0 + +### DEVICE CONFIG ### +deviceConfig: + dashboard: + states: + - component: main + capability: chime + version: 1 + actions: + - component: main + capability: chime + version: 1 + basicPlus: [ ] + detailView: + - component: main + capability: alarm + version: 1 + values: + - key: alarm.value + enabledValues: + - 'off' + - 'both' + - key: "{{enumCommands}}" + enabledValues: + - 'off' + - 'both' + - component: main + capability: chime + version: 1 + - component: main + capability: mode + version: 1 + - component: main + capability: powerSource + version: 1 + values: + - key: powerSource.value + enabledValues: + - 'battery' + - 'mains' + - component: main + capability: audioVolume + version: 1 + - component: main + capability: battery + version: 1 + - component: main + capability: refresh + version: 1 + automation: + conditions: + - component: main + capability: alarm + version: 1 + values: + - key: alarm.value + enabledValues: + - 'off' + - 'both' + - component: main + capability: chime + version: 1 + - component: main + capability: mode + version: 1 + patch: + - op: replace + path: /0/displayType + value: dynamicList + - op: add + path: /0/dynamicList + value: + value: mode.value + command: setMode + supportedValues: + value: supportedArguments.value + - op: remove + path: /0/list + - component: main + capability: powerSource + version: 1 + values: + - key: powerSource.value + enabledValues: + - 'battery' + - 'mains' + step: 1 + - component: main + capability: audioVolume + version: 1 + - component: main + capability: battery + version: 1 + actions: + - component: main + capability: alarm + version: 1 + values: + - key: "{{enumCommands}}" + enabledValues: + - 'off' + - 'both' + - component: main + capability: chime + version: 1 + - component: main + capability: mode + version: 1 + patch: + - op: replace + path: /0/displayType + value: dynamicList + - op: add + path: /0/dynamicList + value: + value: mode.value + command: setMode + supportedValues: + value: supportedArguments.value + - op: remove + path: /0/list + - component: main + capability: audioVolume + version: 1 diff --git a/drivers/SmartThings/zwave-siren/src/init.lua b/drivers/SmartThings/zwave-siren/src/init.lua index b682ea77b7..52ccaba6b9 100644 --- a/drivers/SmartThings/zwave-siren/src/init.lua +++ b/drivers/SmartThings/zwave-siren/src/init.lua @@ -80,7 +80,8 @@ local driver_template = { capabilities.tamperAlert, capabilities.temperatureMeasurement, capabilities.relativeHumidityMeasurement, - capabilities.chime + capabilities.chime, + capabilities.powerSource }, sub_drivers = require("sub_drivers"), lifecycle_handlers = { diff --git a/drivers/SmartThings/zwave-siren/src/preferences.lua b/drivers/SmartThings/zwave-siren/src/preferences.lua index 2c10de6fcb..3cf2fe91b5 100644 --- a/drivers/SmartThings/zwave-siren/src/preferences.lua +++ b/drivers/SmartThings/zwave-siren/src/preferences.lua @@ -46,7 +46,32 @@ local devices = { PARAMETERS = { alarmLength = {parameter_number = 1, size = 2} } - } + }, + ZOOZ_ZSE50_SIREN = { + MATCHING_MATRIX = { + mfrs = 0x027A, + product_types = 0x0004, + product_ids = 0x0369 + }, + PARAMETERS = { + playbackMode = { parameter_number = 1, size = 1 }, + playbackDuration = { parameter_number = 2, size = 2 }, + playbackLoop = { parameter_number = 3, size = 1 }, + playbackTone = { parameter_number = 4, size = 1 }, + playbackVolume = { parameter_number = 5, size = 1 }, + ledMode = { parameter_number = 6, size = 1 }, + ledColor = { parameter_number = 7, size = 1 }, + lowBattery = { parameter_number = 8, size = 1 }, + ledBatteryMode = { parameter_number = 9, size = 1 }, + btnToneSelection = { parameter_number = 10, size = 1 }, + btnVolSelection = { parameter_number = 11, size = 1 }, + basicSetGrp2 = { parameter_number = 12, size = 1 }, --Not Used + systemVolume = { parameter_number = 13, size = 1 }, + ledBrightness = { parameter_number = 14, size = 1 }, + batteryFrequency = { parameter_number = 15, size = 1 }, + batteryThreshold = { parameter_number = 16, size = 1 } + } + }, } local preferences = {} @@ -70,4 +95,5 @@ preferences.to_numeric_value = function(new_value) end return numeric end + return preferences diff --git a/drivers/SmartThings/zwave-siren/src/sub_drivers.lua b/drivers/SmartThings/zwave-siren/src/sub_drivers.lua index a20d559e44..12ce423ba5 100644 --- a/drivers/SmartThings/zwave-siren/src/sub_drivers.lua +++ b/drivers/SmartThings/zwave-siren/src/sub_drivers.lua @@ -4,6 +4,7 @@ local lazy_load_if_possible = require "lazy_load_subdriver" local sub_drivers = { lazy_load_if_possible("multifunctional-siren"), + lazy_load_if_possible("zooz-zse50"), lazy_load_if_possible("zwave-sound-sensor"), lazy_load_if_possible("ecolink-wireless-siren"), lazy_load_if_possible("philio-sound-siren"), diff --git a/drivers/SmartThings/zwave-siren/src/test/test_zooz_zse50.lua b/drivers/SmartThings/zwave-siren/src/test/test_zooz_zse50.lua new file mode 100644 index 0000000000..1454458687 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/test/test_zooz_zse50.lua @@ -0,0 +1,717 @@ +-- Copyright 2026 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 Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) +local SoundSwitch = (require "st.zwave.CommandClass.SoundSwitch")({ version = 1 }) +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 8 }) +local Version = (require "st.zwave.CommandClass.Version")({ version = 1 }) +local t_utils = require "integration_test.utils" + +local siren_endpoints = { + { + command_classes = { + { value = zw.SOUND_SWITCH }, + { value = zw.NOTIFICATION }, + { value = zw.VERSION }, + { value = zw.BASIC } + } + } +} + +--- { manufacturerId = 0x027A, productType = 0x0004, productId = 0x0369 } -- Zooz ZSE50 Siren & Chime +local mock_siren = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("zooz-zse50.yml"), + zwave_endpoints = siren_endpoints, + zwave_manufacturer_id = 0x027A, + zwave_product_type = 0x0004, + zwave_product_id = 0x0369, +}) + +local tones_list = { + [1] = { name = "test_tone1", duration = 2 }, + [2] = { name = "test_tone2", duration = 4 } +} + +local function test_init() + -- Initialize some fields to help with testing + mock_siren:set_field("TONE_DEFAULT", 1, { persist = true }) + mock_siren:set_field("TOTAL_TONES", 2, { persist = true }) + mock_siren:set_field("TONES_LIST", tones_list, { persist = true }) + + test.mock_device.add_test_device(mock_siren) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "init should rebuild tones when tone cache is missing", + function() + mock_siren:set_field("TONES_LIST", nil) + mock_siren:set_field("TONE_DEFAULT", nil) + + test.socket.device_lifecycle:__queue_receive({ mock_siren.id, "init" }) + test.socket.capability:__expect_send( + mock_siren:generate_test_message("main", capabilities.mode.mode("Rebuild List")) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonesNumberGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "added should set startup volume and refresh", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.zwave:__set_channel_ordering("relaxed") + + test.socket.device_lifecycle:__queue_receive({ mock_siren.id, "added" }) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ConfigurationSet({ volume = 10 }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Basic:Get({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Version:Get({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Notification:Get({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.STATE_IDLE, + v1_alarm_type = 0 + }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ConfigurationGet({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "infoChanged should update config and send delayed Basic Set", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.socket.device_lifecycle:__queue_receive( + mock_siren:generate_info_changed({ preferences = { ledColor = 255 } }) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Configuration:Set({ parameter_number = 7, size = 1, configuration_value = -1 }) + ) + ) + + test.mock_time.advance_time(1) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Basic:Set({ value = 0x00 }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "infoChanged should update playbackDuration and send delayed Basic Set", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.socket.device_lifecycle:__queue_receive( + mock_siren:generate_info_changed({ preferences = { playbackDuration = 90 } }) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Configuration:Set({ parameter_number = 2, size = 2, configuration_value = 90 }) + ) + ) + + test.mock_time.advance_time(1) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Basic:Set({ value = 0x00 }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Version report should update firmware version", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(Version:Report({ + application_version = 2, + application_sub_version = 5 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.firmwareUpdate.currentVersion({ value = "2.05" })) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Notification report AC_MAINS_DISCONNECTED should set power source to battery", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.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_siren:generate_test_message("main", capabilities.powerSource.powerSource.battery()) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Notification report AC_MAINS_RE_CONNECTED should set power source to mains", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.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_siren:generate_test_message("main", capabilities.powerSource.powerSource.mains()) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "SoundSwitch ConfigurationReport should update volume", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(SoundSwitch:ConfigurationReport({ + volume = 75, + default_tone_identifer = 5 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.audioVolume.volume(75)) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "SoundSwitch TonesNumberReport should request info on each tone", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(SoundSwitch:TonesNumberReport({ + supported_tones = 2 + })) } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ToneInfoGet({ tone_identifier = 1 }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ToneInfoGet({ tone_identifier = 2 }) + ) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "SoundSwitch ToneInfoReport should update supported modes when all tones received", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(SoundSwitch:ToneInfoReport({ + tone_identifier = 1, + name = "test_tone1", + tone_duration = 2 + })) } + }, + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(SoundSwitch:ToneInfoReport({ + tone_identifier = 2, + name = "test_tone2", + tone_duration = 4 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.mode.supportedModes({ "Rebuild List", "Off", "1: test_tone1 (2s)", "2: test_tone2 (4s)" })) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.mode.supportedArguments({ "Off", "1: test_tone1 (2s)", "2: test_tone2 (4s)" })) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "SoundSwitch TonePlayReport for tone 1 should set alarm on, chime on, and mode to tone name", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(SoundSwitch:TonePlayReport({ + tone_identifier = 1 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.alarm.alarm.both()) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.chime.chime.chime()) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.mode.mode("1: test_tone1 (2s)")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "SoundSwitch TonePlayReport for tone 0 should set alarm off, chime off, and mode Off", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(SoundSwitch:TonePlayReport({ + tone_identifier = 0 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.alarm.alarm.off()) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.chime.chime.off()) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.mode.mode("Off")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Basic report 0x00 should be handled as alarm off, chime off, and mode Off", + { + { + channel = "zwave", + direction = "receive", + message = { + mock_siren.id, + zw_test_utils.zwave_test_build_receive_command(Basic:Report({ value = 0 })) } + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.alarm.alarm.off()) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.chime.chime.off()) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.mode.mode("Off")) + } + }, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "volumeUp should increase volume by 2", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "audioVolume", component = "main", command = "volumeUp", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ConfigurationSet({ volume = 52 }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "volumeUp should decrease volume by 2", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "audioVolume", component = "main", command = "volumeDown", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ConfigurationSet({ volume = 48 }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "setVolume should set volume to specified value", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "audioVolume", component = "main", command = "setVolume", args = { 75 } } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ConfigurationSet({ volume = 75 }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "alarm.both() should send TonePlaySet with default tone and TonePlayGet", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "alarm", component = "main", command = "both", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlaySet({ tone_identifier = 0xFF }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "alarm.off() should send TonePlaySet with tone 0x00 and TonePlayGet", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "alarm", component = "main", command = "off", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlaySet({ tone_identifier = 0x00 }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "chime.chime() should send TonePlaySet with default tone and TonePlayGet", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "chime", component = "main", command = "chime", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlaySet({ tone_identifier = 0xFF }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "chime.off() should send TonePlaySet with tone 0x00 and TonePlayGet", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "chime", component = "main", command = "off", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlaySet({ tone_identifier = 0x00 }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "setMode should play the specified tone", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "mode", component = "main", command = "setMode", args = { "1: test_tone1 (2s)" } } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlaySet({ tone_identifier = 1 }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "setMode to Off should turn off the tone", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "mode", component = "main", command = "setMode", args = { "Off" } } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlaySet({ tone_identifier = 0x00 }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "setMode to Rebuild List should emit mode and send TonesNumberGet", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "mode", component = "main", command = "setMode", args = { "Rebuild List" } } + }) + test.socket.capability:__expect_send( + mock_siren:generate_test_message("main", capabilities.mode.mode("Rebuild List")) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonesNumberGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "refresh should send a series of Z-Wave Gets", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Basic:Get({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Version:Get({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Notification:Get({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.STATE_IDLE, + v1_alarm_type = 0 + }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ConfigurationGet({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-siren/src/zooz-zse50/can_handle.lua b/drivers/SmartThings/zwave-siren/src/zooz-zse50/can_handle.lua new file mode 100644 index 0000000000..90ce30f76b --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/zooz-zse50/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_multifunctional_siren(opts, driver, device, ...) + local FINGERPRINTS = require("zooz-zse50.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("zooz-zse50") + end + end + return false +end + +return can_handle_multifunctional_siren diff --git a/drivers/SmartThings/zwave-siren/src/zooz-zse50/fingerprints.lua b/drivers/SmartThings/zwave-siren/src/zooz-zse50/fingerprints.lua new file mode 100644 index 0000000000..a8c37a04e6 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/zooz-zse50/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZSE50_FINGERPRINTS = { + { manufacturerId = 0x027A, productType = 0x0004, productId = 0x0369 } -- Zooz ZSE50 Siren & Chime +} + +return ZSE50_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-siren/src/zooz-zse50/init.lua b/drivers/SmartThings/zwave-siren/src/zooz-zse50/init.lua new file mode 100644 index 0000000000..a1d58b262a --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/zooz-zse50/init.lua @@ -0,0 +1,367 @@ +-- Copyright 2026 SmartThings +-- Licensed under the Apache License, Version 2.0 + +local preferencesMap = require "preferences" + +local log = require "log" +local st_utils = require "st.utils" +local capabilities = require "st.capabilities" +local defaults = require "st.zwave.defaults" + +local cc = require "st.zwave.CommandClass" +local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 8 }) +local SoundSwitch = (require "st.zwave.CommandClass.SoundSwitch")({ version = 1 }) +local Version = (require "st.zwave.CommandClass.Version")({ version = 1 }) + +--- @param self st.zwave.Driver +--- @param device st.zwave.Device +local function update_firmwareUpdate_capability(self, device, component, major, minor) + if device:supports_capability_by_id(capabilities.firmwareUpdate.ID, component.id) then + local fmtFirmwareVersion = string.format("%d.%02d", major, minor) + device:emit_component_event(component, capabilities.firmwareUpdate.currentVersion({ value = fmtFirmwareVersion })) + end +end + +--- Update the built in capability firmwareUpdate's currentVersion attribute with the +--- Zwave version information received during pairing of the device. +--- @param self st.zwave.Driver +--- @param device st.zwave.Device +local function updateFirmwareVersion(self, device) + local fw_major = (((device.st_store or {}).zwave_version or {}).firmware or {}).major + local fw_minor = (((device.st_store or {}).zwave_version or {}).firmware or {}).minor + if fw_major and fw_minor then + update_firmwareUpdate_capability(self, device, device.profile.components.main, fw_major, fw_minor) + else + device.log.warn("Firmware major or minor version not available.") + end +end + +local function getModeName(toneId, toneInfo) + return string.format("%s: %s (%ss)", toneId, toneInfo.name, toneInfo.duration) +end + +local function playTone(device, tone_id) + local tones_list = device:get_field("TONES_LIST") + local default_tone = device:get_field("TONE_DEFAULT") or 1 + local playbackMode = tonumber(device.preferences.playbackMode) + local duration = 0 + + if playbackMode == 1 then + duration = device.preferences.playbackDuration + elseif playbackMode == 2 then + duration = duration * device.preferences.playbackLoop + elseif tones_list ~= nil and tone_id > 0 then + if tone_id == 0xFF then + duration = tones_list[tonumber(default_tone)].duration + else + duration = tones_list[tonumber(tone_id)].duration + end + end + + log.info(string.format("Playing Tone: %s, playbackMode %s, duration %ss", tone_id, playbackMode, duration)) + + device:send(SoundSwitch:TonePlaySet({ tone_identifier = tone_id })) + device:send(SoundSwitch:TonePlayGet({})) + + local soundSwitch_refresh = function() + local chime = device:get_latest_state("main", capabilities.chime.ID, capabilities.chime.chime.NAME) + local mode = device:get_latest_state("main", capabilities.mode.ID, capabilities.mode.mode.NAME) + log.info(string.format("Running SoundSwitch Refresh: %s | %s", chime, mode)) + if chime ~= "off" or mode ~= "Off" then + device:send(SoundSwitch:TonePlayGet({})) + end + end + + if tone_id > 0 and playbackMode <= 2 then + local minDuration = math.max(duration, 4) + device.thread:call_with_delay(minDuration + 0.5, soundSwitch_refresh) + device.thread:call_with_delay(minDuration + 4, soundSwitch_refresh) + end + +end + +local function rebuildTones(device) + device:emit_event(capabilities.mode.mode("Rebuild List")) + device:send(SoundSwitch:TonesNumberGet({})) +end + +local function refresh_handler(self, device) + device:default_refresh() + device:send(Version:Get({})) + device:send(Notification:Get({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.STATE_IDLE, + v1_alarm_type = 0 + })) + device:send(SoundSwitch:ConfigurationGet({})) + device:send(SoundSwitch:TonePlayGet({})) +end + +local function setMode_handler(self, device, command) + local mode_value = command.args.mode + local mode_split = string.find(mode_value, ":") + + if mode_split ~= nil then + mode_value = string.sub(mode_value, 1, mode_split - 1) + end + log.info(string.format("Command: setMode (%s)", mode_value)) + + if mode_value == 'Rebuild List' then + rebuildTones(device) + elseif mode_value == 'Off' then + playTone(device, 0x00) + else + playTone(device, tonumber(mode_value)) + end +end + +local function setVolume_handler(self, device, cmd) + local new_volume = st_utils.clamp_value(cmd.args.volume, 0, 100) + device:send(SoundSwitch:ConfigurationSet({ volume = new_volume })) +end + +local function volumeUp_handler(self, device, cmd) + local volume = device:get_latest_state("main", capabilities.audioVolume.ID, capabilities.audioVolume.volume.NAME) or 50 + volume = st_utils.clamp_value(volume + 2, 0, 100) + device:send(SoundSwitch:ConfigurationSet({ volume = volume })) +end + +local function volumeDown_handler(self, device, cmd) + local volume = device:get_latest_state("main", capabilities.audioVolume.ID, capabilities.audioVolume.volume.NAME) or 50 + volume = st_utils.clamp_value(volume - 2, 0, 100) + device:send(SoundSwitch:ConfigurationSet({ volume = volume })) +end + +local function tone_on(self, device) + playTone(device, 0xFF) +end + +local function tone_off(self, device) + playTone(device, 0x00) +end + +local function tones_number_report_handler(self, device, cmd) + local total_tones = cmd.args.supported_tones + + --Max 50 tones per Zooz settings + if total_tones > 50 then + total_tones = 50 + end + + local tones_list = { } + device:set_field("TOTAL_TONES", total_tones) + device:set_field("TONES_LIST_TMP", tones_list) + + --Get info on all tones + for tone = 1, total_tones do + device:send(SoundSwitch:ToneInfoGet({ tone_identifier = tone })) + end +end + +local function tone_info_report_handler(self, device, cmd) + local tone_id = tonumber(cmd.args.tone_identifier) + local tone_name = cmd.args.name + local duration = cmd.args.tone_duration + local total_tones = device:get_field("TOTAL_TONES") + local tones_list = device:get_field("TONES_LIST_TMP") or {} + + tones_list[tone_id] = { name = tone_name, duration = duration } + device:set_field("TONES_LIST_TMP", tones_list) + + if tone_id >= total_tones or #tones_list >= total_tones then + log.info(string.format("Received info on all tones: tone_id %s, #tones_list %s, total_tones %s", tone_id, #tones_list, total_tones)) + + local tones_arguments = { "Off" } + for il, vl in ipairs(tones_list) do + table.insert(tones_arguments, getModeName(il, vl)) + end + + device:set_field("TONES_LIST", tones_list, { persist = true }) + device:emit_event(capabilities.mode.supportedModes({ "Rebuild List", table.unpack(tones_arguments) })) + device:emit_event(capabilities.mode.supportedArguments(tones_arguments)) + device:send(SoundSwitch:TonePlayGet({})) + end +end + +--- Handle when tone is played (TONE_PLAY_REPORT or BASIC_REPORT) +local function tone_playing(self, device, tone_id) + local tones_list = device:get_field("TONES_LIST") + + if tones_list == nil or tones_list == {} then + rebuildTones(device) + end + + if tone_id == 0 then + device:emit_event(capabilities.alarm.alarm.off()) + device:emit_event(capabilities.chime.chime.off()) + device:emit_event(capabilities.mode.mode("Off")) + else + local toneInfo = (tones_list or {})[tone_id] or { name = "Unknown", duration = "0" } + local modeName = getModeName(tone_id, toneInfo) + device:emit_event(capabilities.alarm.alarm.both()) + device:emit_event(capabilities.chime.chime.chime()) + device:emit_event(capabilities.mode.mode(modeName)) + end +end + +local function tone_play_report_handler(self, device, cmd) + local tone_id = tonumber(cmd.args.tone_identifier) + tone_playing(self, device, tone_id) +end + +local function basic_report_handler(self, device, cmd) + local tone_id = tonumber(cmd.args.value) + tone_playing(self, device, tone_id) +end + +--- Handle SoundSwitch Config Reports (volume) +local function soundSwitch_configuration_report(self, device, cmd) + local volume = st_utils.clamp_value(cmd.args.volume, 0, 100) + local default_tone = cmd.args.default_tone_identifer + device:emit_event(capabilities.audioVolume.volume(volume)) + device:set_field("TONE_DEFAULT", default_tone, { persist = true }) +end + +--- Handle power source changes +local function notification_report_handler(self, device, cmd) + if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then + local event = cmd.args.event + local powerManagement = Notification.event.power_management + + if event == powerManagement.AC_MAINS_DISCONNECTED then + device:emit_event(capabilities.powerSource.powerSource.battery()) + elseif event == powerManagement.AC_MAINS_RE_CONNECTED or event == powerManagement.STATE_IDLE then + device:emit_event(capabilities.powerSource.powerSource.mains()) + end + end +end + +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param cmd st.zwave.CommandClass.Version.Report +local function version_report_handler(driver, device, cmd) + local major = cmd.args.application_version + local minor = cmd.args.application_sub_version + + -- Update the built in firmware capability, if available + update_firmwareUpdate_capability(driver, device, device.profile.components.main, major, minor) +end + +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +local function device_init(driver, device) + if (device:get_field("TONES_LIST") == nil or device:get_field("TONE_DEFAULT") == nil) then + rebuildTones(device) + end +end + +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +local function device_added(driver, device) + device:send(SoundSwitch:ConfigurationSet({ volume = 10 })) + updateFirmwareVersion(driver, device) + device:refresh() +end + +--- Handle preference changes (same as default but added hack for unsigned parameters) +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param event table +--- @param args +local function info_changed(driver, device, event, args) + local preferences = preferencesMap.get_device_parameters(device) + + if preferences then + local did_configuration_change = false + for id, value in pairs(device.preferences) do + if args.old_st_store.preferences[id] ~= value and preferences[id] then + local new_parameter_value = preferencesMap.to_numeric_value(device.preferences[id]) + --Hack to convert to signed integer + local size_factor = math.floor(256 ^ preferences[id].size) + if new_parameter_value >= (size_factor / 2) then + new_parameter_value = new_parameter_value - size_factor + end + --END Hack + device:send(Configuration:Set({ parameter_number = preferences[id].parameter_number, size = preferences[id].size, configuration_value = new_parameter_value })) + did_configuration_change = true + end + end + + if did_configuration_change then + local delayed_command = function() + device:send(Basic:Set({ value = 0x00 })) + end + device.thread:call_with_delay(1, delayed_command) + end + + end +end + +local zooz_zse50 = { + NAME = "Zooz ZSE50", + can_handle = require("zooz-zse50.can_handle"), + + supported_capabilities = { + capabilities.battery, + capabilities.chime, + capabilities.mode, + capabilities.audioVolume, + capabilities.powerSource, + capabilities.firmwareUpdate, + capabilities.configuration, + capabilities.refresh + }, + + zwave_handlers = { + [cc.BASIC] = { + [Basic.REPORT] = basic_report_handler + }, + [cc.SOUND_SWITCH] = { + [SoundSwitch.TONES_NUMBER_REPORT] = tones_number_report_handler, + [SoundSwitch.TONE_INFO_REPORT] = tone_info_report_handler, + [SoundSwitch.TONE_PLAY_REPORT] = tone_play_report_handler, + [SoundSwitch.CONFIGURATION_REPORT] = soundSwitch_configuration_report + }, + [cc.NOTIFICATION] = { + [Notification.REPORT] = notification_report_handler + }, + [cc.VERSION] = { + [Version.REPORT] = version_report_handler + } + }, + + capability_handlers = { + [capabilities.mode.ID] = { + [capabilities.mode.commands.setMode.NAME] = setMode_handler + }, + [capabilities.audioVolume.ID] = { + [capabilities.audioVolume.commands.setVolume.NAME] = setVolume_handler, + [capabilities.audioVolume.commands.volumeUp.NAME] = volumeUp_handler, + [capabilities.audioVolume.commands.volumeDown.NAME] = volumeDown_handler + }, + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = refresh_handler + }, + [capabilities.alarm.ID] = { + [capabilities.alarm.commands.both.NAME] = tone_on, + [capabilities.alarm.commands.off.NAME] = tone_off + }, + [capabilities.chime.ID] = { + [capabilities.chime.commands.chime.NAME] = tone_on, + [capabilities.chime.commands.off.NAME] = tone_off + }, + + }, + + lifecycle_handlers = { + init = device_init, + added = device_added, + infoChanged = info_changed + } +} + +defaults.register_for_default_handlers(zooz_zse50, zooz_zse50.supported_capabilities) + +return zooz_zse50 From 0e5dbc84b367f112bd7d690a948fa2924c33e056 Mon Sep 17 00:00:00 2001 From: zmguko <160391709+zmguko@users.noreply.github.com> Date: Fri, 10 Apr 2026 22:28:16 +0800 Subject: [PATCH 223/538] WWSTCERT-10696 - add new zigbee-humididt-sensor SNZB-02DR2 (#2573) * add new zigbee-humididt-sensor SNZB-02DR2 --- drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml | 5 +++++ .../zigbee-humidity-sensor/src/configurations.lua | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml b/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml index e5ad9e571a..d897ca7ecf 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml +++ b/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml @@ -83,6 +83,11 @@ zigbeeManufacturer: manufacturer: eWeLink model: SNZB-02P deviceProfileName: humidity-temp-battery + - id: "SONOFF/SNZB-02DR2" + deviceLabel: "SONOFF SNZB-02DR2" + manufacturer: SONOFF + model: SNZB-02DR2 + deviceProfileName: humidity-temp-battery - id: "Third Reality/3RTHS24BZ" deviceLabel: ThirdReality Temperature and Humidity Sensor manufacturer: Third Reality, Inc diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/configurations.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/configurations.lua index 8bc35c8765..043359768f 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/configurations.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/configurations.lua @@ -56,7 +56,8 @@ local devices = { EWELINK_HUMIDITY_TEMP_SENSOR = { FINGERPRINTS = { { mfr = "eWeLink", model = "TH01" }, - { mfr = "eWeLink", model = "SNZB-02P" } + { mfr = "eWeLink", model = "SNZB-02P" }, + { mfr = "SONOFF", model = "SNZB-02DR2" } }, CONFIGURATION = { { From c39614bde08964352d542b83690a4ed4ce361de6 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Fri, 10 Apr 2026 11:48:55 -0500 Subject: [PATCH 224/538] WWSTCERT-10652 ULTRALOQ Matter Door Lock (#2889) --- drivers/SmartThings/matter-lock/fingerprints.yml | 10 ++++++++++ .../matter-lock/src/new-matter-lock/fingerprints.lua | 2 ++ 2 files changed, 12 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 29c37dd69c..0c12932a2b 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -140,6 +140,16 @@ matterManufacturer: vendorId: 0x147F productId: 0x0008 deviceProfileName: lock-user-pin-battery + - id: "5247/7" + deviceLabel: ULTRALOQ Bolt Pro Smart Matter Door Lock + vendorId: 0x147F + productId: 0x0007 + deviceProfileName: lock-modular + - id: "5247/16" + deviceLabel: ULTRALOQ Latch 5 Pro Smart Matter Door Lock + vendorId: 0x147F + productId: 0x0010 + deviceProfileName: lock-modular #Yale - id: "4125/33040" deviceLabel: Yale Lock with Matter diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index ac0352c75a..901c0ea39c 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -8,7 +8,9 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x115f, 0x2804}, -- AQARA, U400 {0x115f, 0x286A}, -- AQARA, U200 US {0x147F, 0x0001}, -- U-tec + {0x147F, 0x0007}, -- ULTRALOQ Bolt Pro Smart Matter Door Lock {0x147F, 0x0008}, -- Ultraloq, Bolt Smart Matter Door Lock + {0x147F, 0x0010}, -- ULTRALOQ Latch 5 Pro Smart Matter Door Lock {0x144F, 0x4002}, -- Yale, Linus Smart Lock L2 {0x101D, 0x8110}, -- Yale, New Lock {0x1533, 0x0001}, -- eufy, E31 From d387762cd8fe7d88f00796f0dd910d89851bbb01 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Mon, 13 Apr 2026 11:54:26 -0500 Subject: [PATCH 225/538] Deep copy latest state, use `pairs` to handle map-like capability tables keyed by strings Co-authored-by: Harrison Carter --- .../camera_utils/device_configuration.lua | 39 +++++++-------- .../src/test/test_matter_camera.lua | 48 +++++++++++++++++++ 2 files changed, 68 insertions(+), 19 deletions(-) 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 index c2ea259aa1..068bee2344 100644 --- 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 @@ -6,6 +6,7 @@ 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 st_utils = require "st.utils" local device_cfg = require "switch_utils.device_configuration" local fields = require "switch_utils.fields" local switch_utils = require "switch_utils.utils" @@ -108,32 +109,34 @@ local function build_camera_privacy_supported_commands() end local function capabilities_needing_reinit(device) - local main = camera_fields.profile_components.main - local capabilities_to_reinit = {} - local function state_differs(capability, attribute_name, expected) - local current = device:get_latest_state(main, capability.ID, attribute_name) - return not switch_utils.deep_equals(current, expected, { ignore_functions = true }) + local function should_init(capability, attribute, expected) + if device:supports_capability(capability) then + local current = st_utils.deep_copy(device:get_latest_state( + camera_fields.profile_components.main, + capability.ID, + attribute.NAME, + {} + )) + return not switch_utils.deep_equals(current, expected) + end + return false end - if device:supports_capability(capabilities.webrtc) and - state_differs(capabilities.webrtc, capabilities.webrtc.supportedFeatures.NAME, build_webrtc_supported_features()) then + if should_init(capabilities.webrtc, capabilities.webrtc.supportedFeatures, build_webrtc_supported_features()) then capabilities_to_reinit.webrtc = true end - if device:supports_capability(capabilities.mechanicalPanTiltZoom) and - state_differs(capabilities.mechanicalPanTiltZoom, capabilities.mechanicalPanTiltZoom.supportedAttributes.NAME, build_ptz_supported_attributes(device)) then + if should_init(capabilities.mechanicalPanTiltZoom, capabilities.mechanicalPanTiltZoom.supportedAttributes, build_ptz_supported_attributes(device)) then capabilities_to_reinit.ptz = true end - if device:supports_capability(capabilities.zoneManagement) and - state_differs(capabilities.zoneManagement, capabilities.zoneManagement.supportedFeatures.NAME, build_zone_management_supported_features(device)) then + if should_init(capabilities.zoneManagement, capabilities.zoneManagement.supportedFeatures, build_zone_management_supported_features(device)) then capabilities_to_reinit.zone_management = true end - if device:supports_capability(capabilities.localMediaStorage) and - state_differs(capabilities.localMediaStorage, capabilities.localMediaStorage.supportedAttributes.NAME, build_local_media_storage_supported_attributes(device)) then + if should_init(capabilities.localMediaStorage, capabilities.localMediaStorage.supportedAttributes, build_local_media_storage_supported_attributes(device)) then capabilities_to_reinit.local_media_storage = true end @@ -144,14 +147,12 @@ local function capabilities_needing_reinit(device) end end - if device:supports_capability(capabilities.videoStreamSettings) and - state_differs(capabilities.videoStreamSettings, capabilities.videoStreamSettings.supportedFeatures.NAME, build_video_stream_settings_supported_features(device)) then + if should_init(capabilities.videoStreamSettings, capabilities.videoStreamSettings.supportedFeatures, build_video_stream_settings_supported_features(device)) then capabilities_to_reinit.video_stream_settings = true end - if device:supports_capability(capabilities.cameraPrivacyMode) and - (state_differs(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedAttributes.NAME, build_camera_privacy_supported_attributes()) or - state_differs(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedCommands.NAME, build_camera_privacy_supported_commands())) then + if should_init(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedAttributes, build_camera_privacy_supported_attributes()) or + should_init(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedCommands, build_camera_privacy_supported_commands()) then capabilities_to_reinit.camera_privacy_mode = true end @@ -407,7 +408,7 @@ end local function profile_capability_set(profile) local capability_set = {} for _, component in pairs((profile or {}).components or {}) do - for _, capability in ipairs(component.capabilities or {}) do + for _, capability in pairs(component.capabilities or {}) do if capability.id ~= nil then capability_set[capability.id] = true end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 7f1f72b108..fceaa4ccf5 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -503,6 +503,54 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Camera privacy mode state compare should ignore table metatable differences", + function() + local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" + + local init_event_count = 0 + local original_match_profile = camera_cfg.match_profile + + camera_cfg.match_profile = function() + return false + end + + local fake_device = { + supports_capability = function(_, capability) + return capability == capabilities.cameraPrivacyMode + end, + get_latest_state = function(_, _, _, attribute_name) + if attribute_name == capabilities.cameraPrivacyMode.supportedAttributes.NAME then + return { "softRecordingPrivacyMode", "softLivestreamPrivacyMode" } + elseif attribute_name == capabilities.cameraPrivacyMode.supportedCommands.NAME then + local commands = { "setSoftRecordingPrivacyMode", "setSoftLivestreamPrivacyMode" } + setmetatable(commands, { + __index = function() + return nil + end + }) + return commands + end + return nil + end, + get_endpoints = function() + return { CAMERA_EP } + end, + emit_event_for_endpoint = function() + init_event_count = init_event_count + 1 + end + } + + camera_cfg.reconcile_profile_and_capabilities(fake_device) + camera_cfg.match_profile = original_match_profile + + assert(init_event_count == 0, "cameraPrivacyMode should not be reinitialized for equal values with metatable differences") + end, + { + min_api_version = 17 + } +) + test.register_coroutine_test( "Reports mapping to EnabledState capability data type should generate appropriate events", function() From 164d90521daa256bf6fbb215a9ec5efd0d89c030 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Mon, 13 Apr 2026 12:00:16 -0500 Subject: [PATCH 226/538] Fix luacheck lints --- .../camera/camera_utils/device_configuration.lua | 6 +++++- .../src/sub_drivers/camera/camera_utils/utils.lua | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) 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 index 068bee2344..a31473f993 100644 --- 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 @@ -141,7 +141,11 @@ local function capabilities_needing_reinit(device) end if device:supports_capability(capabilities.audioRecording) then - local audio_enabled_state = device:get_latest_state(main, capabilities.audioRecording.ID, capabilities.audioRecording.audioRecording.NAME) + 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 capabilities_to_reinit.audio_recording = true end 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 index f3b92aa3b6..78792b94b3 100644 --- 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 @@ -6,7 +6,6 @@ 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 cluster_base = require "st.matter.cluster_base" local CameraUtils = {} From a883224c7fd122d47dcb51a3593198d54aa660b7 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Mon, 13 Apr 2026 14:45:09 -0500 Subject: [PATCH 227/538] add nil check handling for electrical sensor handlers --- .../src/switch_handlers/attribute_handlers.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index b16610cf25..20bd92881e 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -321,6 +321,10 @@ function AttributeHandlers.available_endpoints_handler(driver, device, ib, respo return end local set_topology_eps = device:get_field(fields.ELECTRICAL_SENSOR_EPS) + if set_topology_eps == nil then + device.log.warn("Received an AvailableEndpoints response but no Electrical Sensor endpoints have been identified as supporting the Power Topology cluster with SET feature. Ignoring this response.") + return + end for i, set_ep_info in pairs(set_topology_eps or {}) do if ib.endpoint_id == set_ep_info.endpoint_id then -- since EP response is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table @@ -350,6 +354,10 @@ function AttributeHandlers.parts_list_handler(driver, device, ib, response) return end local tree_topology_eps = device:get_field(fields.ELECTRICAL_SENSOR_EPS) + if tree_topology_eps == nil then + device.log.warn("Received a PartsList response but no Electrical Sensor endpoints have been identified as supporting the Power Topology cluster with TREE feature. Ignoring this response.") + return + end for i, tree_ep_info in pairs(tree_topology_eps or {}) do if ib.endpoint_id == tree_ep_info.endpoint_id then -- since EP response is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table From d04ecd94207c68749b348cbd048814e331287fa3 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Mon, 13 Apr 2026 15:38:59 -0500 Subject: [PATCH 228/538] explicitly check default endpoint for water valves --- .../src/switch_utils/device_configuration.lua | 21 +++++++-------- .../matter-switch/src/switch_utils/fields.lua | 1 + .../matter-switch/src/switch_utils/utils.lua | 27 ++++++++++--------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index ab2e075eb7..8f1f1311fa 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -23,7 +23,7 @@ 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 + return end table.sort(server_cluster_ep_ids) @@ -209,14 +209,6 @@ function DeviceConfiguration.match_profile(driver, 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) @@ -238,8 +230,15 @@ function DeviceConfiguration.match_profile(driver, device) 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 + if #switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.WATER_VALVE) > 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 + + if #switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.FAN) > 0 then updated_profile, optional_component_capabilities = FanDeviceConfiguration.assign_profile_for_fan_ep(device, default_endpoint_id) end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 1432b18c74..39a60e5eaa 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -52,6 +52,7 @@ SwitchFields.DEVICE_TYPE_ID = { DIMMER = 0x0104, COLOR_DIMMER = 0x0105, }, + WATER_VALVE = 0x0042, } SwitchFields.device_type_profile_map = { diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index f949a15a56..e7f5a96187 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -151,10 +151,6 @@ function utils.find_default_endpoint(device) 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 @@ -166,23 +162,24 @@ function utils.find_default_endpoint(device) end -- Return the first fan endpoint as the default endpoint if any is found + local fan_endpoint_ids = utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.FAN) 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) + -- Return the first water valve endpoint as the default endpoint if any is found + local water_valve_endpoint_ids = utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.WATER_VALVE) + if #water_valve_endpoint_ids > 0 then + return get_first_non_zero_endpoint(water_valve_endpoint_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. + -- default endpoint. Else return the first onoff endpoint as the default endpoint. + -- + -- If only one of the two types of endpoints are present, return the first endpoint of the present type. + 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}) 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 @@ -191,6 +188,10 @@ function utils.find_default_endpoint(device) 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 + elseif #onoff_ep_ids > 0 then + return get_first_non_zero_endpoint(onoff_ep_ids) + elseif #momentary_switch_ep_ids > 0 then + return get_first_non_zero_endpoint(momentary_switch_ep_ids) end device.log.warn(string.format("Did not find default endpoint, will use endpoint %d instead", device.MATTER_DEFAULT_ENDPOINT)) From c93c379b8877df5bde48bf5941bba69b4fb21de7 Mon Sep 17 00:00:00 2001 From: Konrad K <33450498+KKlimczukS@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:40:48 +0200 Subject: [PATCH 229/538] WWSTCERT-8045 - Aeotec Home Energy Meter Gen8 (Revert + fixes) (#2791) WWSTCERT-8045 - Aeotec Home Energy Meter Gen8 --- .../zwave-electric-meter/fingerprints.yml | 16 + ...tec-home-energy-meter-gen8-1-phase-con.yml | 107 +++ ...tec-home-energy-meter-gen8-1-phase-pro.yml | 22 + ...tec-home-energy-meter-gen8-2-phase-con.yml | 148 ++++ ...tec-home-energy-meter-gen8-2-phase-pro.yml | 31 + ...tec-home-energy-meter-gen8-3-phase-con.yml | 189 +++++ ...tec-home-energy-meter-gen8-3-phase-pro.yml | 40 + ...aeotec-home-energy-meter-gen8-sald-con.yml | 13 + ...aeotec-home-energy-meter-gen8-sald-pro.yml | 13 + .../1-phase/can_handle.lua | 18 + .../1-phase/init.lua | 123 +++ .../2-phase/can_handle.lua | 17 + .../2-phase/init.lua | 123 +++ .../3-phase/can_handle.lua | 18 + .../3-phase/init.lua | 123 +++ .../can_handle.lua | 22 + .../aeotec-home-energy-meter-gen8/init.lua | 49 ++ .../power_consumption.lua | 32 + .../sub_drivers.lua | 10 + .../zwave-electric-meter/src/init.lua | 21 + .../zwave-electric-meter/src/preferences.lua | 95 +++ .../zwave-electric-meter/src/sub_drivers.lua | 1 + ..._aeotec_home_energy_meter_gen8_1_phase.lua | 560 +++++++++++++ ..._aeotec_home_energy_meter_gen8_2_phase.lua | 654 +++++++++++++++ ..._aeotec_home_energy_meter_gen8_3_phase.lua | 749 ++++++++++++++++++ 25 files changed, 3194 insertions(+) create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-con.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-pro.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-con.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-pro.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-con.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-pro.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-con.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-pro.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/can_handle.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/init.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/can_handle.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/init.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/can_handle.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/init.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/can_handle.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/init.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/power_consumption.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/preferences.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_1_phase.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_2_phase.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_3_phase.lua diff --git a/drivers/SmartThings/zwave-electric-meter/fingerprints.yml b/drivers/SmartThings/zwave-electric-meter/fingerprints.yml index 7c32d646e3..c580510daa 100644 --- a/drivers/SmartThings/zwave-electric-meter/fingerprints.yml +++ b/drivers/SmartThings/zwave-electric-meter/fingerprints.yml @@ -35,6 +35,22 @@ zwaveManufacturer: productType: 0x0002 productId: 0x0001 deviceProfileName: base-electric-meter + - id: "0x0371/0x0003/0x0033" #HEM Gen8 1 Phase EU, AU + deviceLabel: Aeotec Home Energy Meter Gen8 Consumption + manufacturerId: 0x0371 + productId: 0x0033 + deviceProfileName: aeotec-home-energy-meter-gen8-1-phase-con + - id: "0x0371/0x0003/0x0034" # HEM Gen8 3 Phase EU, AU + deviceLabel: Aeotec Home Energy Meter Gen8 Consumption + manufacturerId: 0x0371 + productId: 0x0034 + deviceProfileName: aeotec-home-energy-meter-gen8-3-phase-con + - id: "0x0371/0x0103/0x002E" # HEM Gen8 2 Phase US + deviceLabel: Aeotec Home Energy Meter Gen8 Consumption + manufacturerId: 0x0371 + productType: 0x0103 + productId: 0x002E + deviceProfileName: aeotec-home-energy-meter-gen8-2-phase-con zwaveGeneric: - id: "GenericEnergyMeter" deviceLabel: Energy Monitor diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-con.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-con.yml new file mode 100644 index 0000000000..b4de02cb6a --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-con.yml @@ -0,0 +1,107 @@ +name: aeotec-home-energy-meter-gen8-1-phase-con +components: +- id: main + label: "Sum Consumption" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp1 + label: "Clamp 1" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +preferences: + - name: thresholdCheck + title: "3. Threshold Check Enable/Disable" + description: "Enable selective reporting only when power change reaches a certain threshold or percentage set in 4 -19 below. This is used to reduce network traffic." + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enable" + default: 1 + - name: imWThresholdTotal + title: "4. Import W threshold (total)" + description: "Threshold change in import wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWThresholdPhaseA + title: "5. Import W threshold (Phase A)" + description: "Threshold change in import wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdTotal + title: "8. Export W threshold (total)" + description: "Threshold change in export wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdPhaseA + title: "9. Export W threshold (Phase A)" + description: "Threshold change in export wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWPctThresholdTotal + title: "12. Import W threshold (total)" + description: "Percentage change in import wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: imWPctThresholdPhaseA + title: "13. Import W threshold (Phase A)" + description: "Percentage change in import wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdTotal + title: "16. Export W threshold (total)" + description: "Percentage change in export wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdPhaseA + title: "17. Export W threshold (Phase A)" + description: "Percentage change in export wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: autoRootDeviceReport + title: "32. Auto report of root device" + description: "Enable automatic report of root device." + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enable" + default: 0 diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-pro.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-pro.yml new file mode 100644 index 0000000000..9510e27037 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-pro.yml @@ -0,0 +1,22 @@ +name: aeotec-home-energy-meter-gen8-1-phase-pro +components: +- id: main + label: "Sum Production" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp2 + label: "Clamp 1" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-con.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-con.yml new file mode 100644 index 0000000000..64b2a510f9 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-con.yml @@ -0,0 +1,148 @@ +name: aeotec-home-energy-meter-gen8-2-phase-con +components: +- id: main + label: "Sum Consumption" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp1 + label: "Clamp 1" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp3 + label: "Clamp 2" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +preferences: + - name: thresholdCheck + title: "3. Threshold Check Enable/Disable" + description: "Enable selective reporting only when power change reaches a certain threshold or percentage set in 4 -19 below. This is used to reduce network traffic." + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enable" + default: 1 + - name: imWThresholdTotal + title: "4. Import W threshold (total)" + description: "Threshold change in import wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWThresholdPhaseA + title: "5. Import W threshold (Phase A)" + description: "Threshold change in import wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWhresholdPhaseB + title: "6. Import W threshold (Phase B)" + description: "Threshold change in import wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWhresholdTotal + title: "8. Export W threshold (total)" + description: "Threshold change in export wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdPhaseA + title: "9. Export W threshold (Phase A)" + description: "Threshold change in export wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdPhaseB + title: "10. Export W threshold (Phase B)" + description: "Threshold change in export wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWPctThresholdTotal + title: "12. Import W threshold (total)" + description: "Percentage change in import wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: imWPctThresholdPhaseA + title: "13. Import W threshold (Phase A)" + description: "Percentage change in import wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: imWPctThresholdPhaseB + title: "14. Import W threshold (Phase B)." + description: "Percentage change in import wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdTotal + title: "16. Export W threshold (total)" + description: "Percentage change in export wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdPhaseA + title: "17. Export W threshold (Phase A)" + description: "Percentage change in export wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdPhaseB + title: "18. Export W threshold (Phase B)" + description: "Percentage change in export wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: autoRootDeviceReport + title: "32. Auto report of root device" + description: "Enable automatic report of root device." + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enable" + default: 0 diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-pro.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-pro.yml new file mode 100644 index 0000000000..be4adf21c5 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-pro.yml @@ -0,0 +1,31 @@ +name: aeotec-home-energy-meter-gen8-2-phase-pro +components: +- id: main + label: "Sum Production" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp2 + label: "Clamp 1" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp4 + label: "Clamp 2" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-con.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-con.yml new file mode 100644 index 0000000000..e1d44731bf --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-con.yml @@ -0,0 +1,189 @@ +name: aeotec-home-energy-meter-gen8-3-phase-con +components: +- id: main + label: "Sum Consumption" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp1 + label: "Clamp 1" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp3 + label: "Clamp 2" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp5 + label: "Clamp 3" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +preferences: + - name: thresholdCheck + title: "3. Threshold Check Enable/Disable" + description: "Enable selective reporting only when power change reaches a certain threshold or percentage set in 4 -19 below. This is used to reduce network traffic." + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enable" + default: 1 + - name: imWThresholdTotal + title: "4. Import W threshold (total)" + description: "Threshold change in import wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWThresholdPhaseA + title: "5. Import W threshold (Phase A)" + description: "Threshold change in import wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWhresholdPhaseB + title: "6. Import W threshold (Phase B)" + description: "Threshold change in import wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imtWThresholdPhaseC + title: "7. Import W threshold (Phase C)" + description: "Threshold change in import wattage to induce an automatic report (Phase C)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWhresholdTotal + title: "8. Export W threshold (total)" + description: "Threshold change in export wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdPhaseA + title: "9. Export W threshold (Phase A)" + description: "Threshold change in export wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdPhaseB + title: "10. Export W threshold (Phase B)" + description: "Threshold change in export wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdPhaseC + title: "11. Export W threshold (Phase C)" + description: "Threshold change in export wattage to induce an automatic report (Phase C)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWPctThresholdTotal + title: "12. Import W threshold (total)" + description: "Percentage change in import wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: imWPctThresholdPhaseA + title: "13. Import W threshold (Phase A)" + description: "Percentage change in import wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: imWPctThresholdPhaseB + title: "14. Import W threshold (Phase B)" + description: "Percentage change in import wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: imWPctThresholdPhaseC + title: "15. Import W threshold (Phase C)" + description: "Percentage change in import wattage to induce an automatic report (Phase C)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdTotal + title: "16. Export W threshold (total)" + description: "Percentage change in export wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdPhaseA + title: "17. Export W threshold (Phase A)" + description: "Percentage change in export wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdPhaseB + title: "18. Export W threshold (Phase B)" + description: "Percentage change in export wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdPhaseC + title: "19. Export W threshold (Phase C)" + description: "Percentage change in export wattage to induce an automatic report (Phase C)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: autoRootDeviceReport + title: "32. Auto report of root device" + description: "Enable automatic report of root device." + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enable" + default: 0 diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-pro.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-pro.yml new file mode 100644 index 0000000000..3efa762dda --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-pro.yml @@ -0,0 +1,40 @@ +name: aeotec-home-energy-meter-gen8-3-phase-pro +components: +- id: main + label: "Sum Production" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp2 + label: "Clamp 1" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp4 + label: "Clamp 2" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp6 + label: "Clamp 3" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-con.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-con.yml new file mode 100644 index 0000000000..f0bf405fb7 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-con.yml @@ -0,0 +1,13 @@ +name: aeotec-home-energy-meter-gen8-sald-con +components: +- id: main + label: "Settled Consumption" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-pro.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-pro.yml new file mode 100644 index 0000000000..da05330d46 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-pro.yml @@ -0,0 +1,13 @@ +name: aeotec-home-energy-meter-gen8-sald-pro +components: +- id: main + label: "Settled Production" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/can_handle.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/can_handle.lua new file mode 100644 index 0000000000..6dd02b40fe --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS = { + { mfr = 0x0371, prod = 0x0003, model = 0x0033 }, -- HEM Gen8 1 Phase EU + { mfr = 0x0371, prod = 0x0102, model = 0x002E } -- HEM Gen8 1 Phase AU +} + +local function can_handle_aeotec_meter_gen8_1_phase(opts, driver, device, ...) + for _, fingerprint in ipairs(AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("aeotec-home-energy-meter-gen8.1-phase") + end + end + return false +end + +return can_handle_aeotec_meter_gen8_1_phase diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/init.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/init.lua new file mode 100644 index 0000000000..db69a9d52d --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/init.lua @@ -0,0 +1,123 @@ +-- 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.Meter +local Meter = (require "st.zwave.CommandClass.Meter")({ version=4 }) +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +local utils = require "st.utils" +local power_consumption = require("aeotec-home-energy-meter-gen8.power_consumption") + +local POWER_UNIT_WATT = "W" +local ENERGY_UNIT_KWH = "kWh" + +local HEM8_DEVICES = { + { profile = 'aeotec-home-energy-meter-gen8-1-phase-con', name = 'Aeotec Home Energy Meter 8 Consumption', endpoints = { 1, 3 } }, + { profile = 'aeotec-home-energy-meter-gen8-1-phase-pro', name = 'Aeotec Home Energy Meter 8 Production', child_key = 'pro', endpoints = { 2, 4 } }, + { profile = 'aeotec-home-energy-meter-gen8-sald-con', name = 'Aeotec Home Energy Meter 8 Settled Consumption', child_key = 'sald-con', endpoints = { 5 } }, + { profile = 'aeotec-home-energy-meter-gen8-sald-pro', name = 'Aeotec Home Energy Meter 8 Settled Production', child_key = 'sald-pro', endpoints = { 6 } } +} + +local function find_hem8_child_device_key_by_endpoint(endpoint) + for _, child in ipairs(HEM8_DEVICES) do + if child.endpoints then + for _, e in ipairs(child.endpoints) do + if e == endpoint then + return child.child_key + end + end + end + end +end + +local function meter_report_handler(driver, device, cmd, zb_rx) + local endpoint = cmd.src_channel + local device_to_emit_with = device + local child_device_key = find_hem8_child_device_key_by_endpoint(endpoint); + local child_device = device:get_child_by_parent_assigned_key(child_device_key) + + if(child_device) then + device_to_emit_with = child_device + end + + if cmd.args.scale == Meter.scale.electric_meter.KILOWATT_HOURS then + local event_arguments = { + value = cmd.args.meter_value, + unit = ENERGY_UNIT_KWH + } + -- energyMeter + device_to_emit_with:emit_event_for_endpoint( + cmd.src_channel, + capabilities.energyMeter.energy(event_arguments) + ) + + if endpoint == 5 then + -- powerConsumptionReport + power_consumption.emit_power_consumption_report_event(device, { value = event_arguments.value }) + end + elseif cmd.args.scale == Meter.scale.electric_meter.WATTS then + local event_arguments = { + value = cmd.args.meter_value, + unit = POWER_UNIT_WATT + } + -- powerMeter + device_to_emit_with:emit_event_for_endpoint( + cmd.src_channel, + capabilities.powerMeter.power(event_arguments) + ) + end +end + +local function do_refresh(self, device) + for _, d in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(d.endpoints) do + device:send(Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, {dst_channels = {endpoint}})) + device:send(Meter:Get({scale = Meter.scale.electric_meter.WATTS}, {dst_channels = {endpoint}})) + end + end +end + +local function device_added(driver, device) + if device.network_type == st_device.NETWORK_TYPE_ZWAVE and not (device.child_ids and utils.table_size(device.child_ids) ~= 0) then + for i, hem8_child in ipairs(HEM8_DEVICES) do + if(hem8_child["child_key"]) then + local name = hem8_child.name + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = hem8_child.profile, + parent_device_id = device.id, + parent_assigned_child_key = hem8_child.child_key, + vendor_provided_label = name + } + driver:try_create_device(metadata) + end + end + end + do_refresh(driver, device) +end + +local aeotec_home_energy_meter_gen8_1_phase = { + NAME = "Aeotec Home Energy Meter Gen8", + supported_capabilities = { + capabilities.powerConsumptionReport + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zwave_handlers = { + [cc.METER] = { + [Meter.REPORT] = meter_report_handler + } + }, + lifecycle_handlers = { + added = device_added + }, + can_handle = require("aeotec-home-energy-meter-gen8.1-phase.can_handle") +} + +return aeotec_home_energy_meter_gen8_1_phase diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/can_handle.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/can_handle.lua new file mode 100644 index 0000000000..ec694e2708 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS = { + { mfr = 0x0371, prod = 0x0103, model = 0x002E } -- HEM Gen8 2 Phase US +} + +local function can_handle_aeotec_meter_gen8_2_phase(opts, driver, device, ...) + for _, fingerprint in ipairs(AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("aeotec-home-energy-meter-gen8.2-phase") + end + end + return false +end + +return can_handle_aeotec_meter_gen8_2_phase diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/init.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/init.lua new file mode 100644 index 0000000000..8d78da66f7 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/init.lua @@ -0,0 +1,123 @@ +-- 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.Meter +local Meter = (require "st.zwave.CommandClass.Meter")({ version=4 }) +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +local utils = require "st.utils" +local power_consumption = require("aeotec-home-energy-meter-gen8.power_consumption") + +local POWER_UNIT_WATT = "W" +local ENERGY_UNIT_KWH = "kWh" + +local HEM8_DEVICES = { + { profile = 'aeotec-home-energy-meter-gen8-3-phase-con', name = 'Aeotec Home Energy Meter 8 Consumption', endpoints = { 1, 3, 5 } }, + { profile = 'aeotec-home-energy-meter-gen8-2-phase-pro', name = 'Aeotec Home Energy Meter 8 Production', child_key = 'pro', endpoints = { 2, 4, 6 } }, + { profile = 'aeotec-home-energy-meter-gen8-sald-con', name = 'Aeotec Home Energy Meter 8 Settled Consumption', child_key = 'sald-con', endpoints = { 7 } }, + { profile = 'aeotec-home-energy-meter-gen8-sald-pro', name = 'Aeotec Home Energy Meter 8 Settled Production', child_key = 'sald-pro', endpoints = { 8 } } +} + +local function find_hem8_child_device_key_by_endpoint(endpoint) + for _, child in ipairs(HEM8_DEVICES) do + if child.endpoints then + for _, e in ipairs(child.endpoints) do + if e == endpoint then + return child.child_key + end + end + end + end +end + +local function meter_report_handler(driver, device, cmd, zb_rx) + local endpoint = cmd.src_channel + local device_to_emit_with = device + local child_device_key = find_hem8_child_device_key_by_endpoint(endpoint); + local child_device = device:get_child_by_parent_assigned_key(child_device_key) + + if(child_device) then + device_to_emit_with = child_device + end + + if cmd.args.scale == Meter.scale.electric_meter.KILOWATT_HOURS then + local event_arguments = { + value = cmd.args.meter_value, + unit = ENERGY_UNIT_KWH + } + -- energyMeter + device_to_emit_with:emit_event_for_endpoint( + cmd.src_channel, + capabilities.energyMeter.energy(event_arguments) + ) + + if endpoint == 7 then + -- powerConsumptionReport + power_consumption.emit_power_consumption_report_event(device, { value = event_arguments.value }) + end + elseif cmd.args.scale == Meter.scale.electric_meter.WATTS then + local event_arguments = { + value = cmd.args.meter_value, + unit = POWER_UNIT_WATT + } + -- powerMeter + device_to_emit_with:emit_event_for_endpoint( + cmd.src_channel, + capabilities.powerMeter.power(event_arguments) + ) + end +end + +local function do_refresh(self, device) + for _, d in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(d.endpoints) do + device:send(Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, {dst_channels = {endpoint}})) + device:send(Meter:Get({scale = Meter.scale.electric_meter.WATTS}, {dst_channels = {endpoint}})) + end + end +end + +local function device_added(driver, device) + if device.network_type == st_device.NETWORK_TYPE_ZWAVE and not (device.child_ids and utils.table_size(device.child_ids) ~= 0) then + for i, hem8_child in ipairs(HEM8_DEVICES) do + if(hem8_child["child_key"]) then + local name = hem8_child.name + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = hem8_child.profile, + parent_device_id = device.id, + parent_assigned_child_key = hem8_child.child_key, + vendor_provided_label = name + } + driver:try_create_device(metadata) + end + end + end + do_refresh(driver, device) +end + +local aeotec_home_energy_meter_gen8_2_phase = { + NAME = "Aeotec Home Energy Meter Gen8", + supported_capabilities = { + capabilities.powerConsumptionReport + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zwave_handlers = { + [cc.METER] = { + [Meter.REPORT] = meter_report_handler + } + }, + lifecycle_handlers = { + added = device_added + }, + can_handle = require("aeotec-home-energy-meter-gen8.2-phase.can_handle") +} + +return aeotec_home_energy_meter_gen8_2_phase diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/can_handle.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/can_handle.lua new file mode 100644 index 0000000000..9f3cc6cb03 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS = { + { mfr = 0x0371, prod = 0x0003, model = 0x0034 }, -- HEM Gen8 3 Phase EU + { mfr = 0x0371, prod = 0x0102, model = 0x0034 } -- HEM Gen8 3 Phase AU +} + +local function can_handle_aeotec_meter_gen8_3_phase(opts, driver, device, ...) + for _, fingerprint in ipairs(AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("aeotec-home-energy-meter-gen8.3-phase") + end + end + return false +end + +return can_handle_aeotec_meter_gen8_3_phase diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/init.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/init.lua new file mode 100644 index 0000000000..cb1c582fb6 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/init.lua @@ -0,0 +1,123 @@ +-- 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.Meter +local Meter = (require "st.zwave.CommandClass.Meter")({ version=4 }) +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +local utils = require "st.utils" +local power_consumption = require("aeotec-home-energy-meter-gen8.power_consumption") + +local POWER_UNIT_WATT = "W" +local ENERGY_UNIT_KWH = "kWh" + +local HEM8_DEVICES = { + { profile = 'aeotec-home-energy-meter-gen8-3-phase-con', name = 'Aeotec Home Energy Meter 8 Consumption', endpoints = { 1, 3, 5, 7 } }, + { profile = 'aeotec-home-energy-meter-gen8-3-phase-pro', name = 'Aeotec Home Energy Meter 8 Production', child_key = 'pro', endpoints = { 2, 4, 6, 8 } }, + { profile = 'aeotec-home-energy-meter-gen8-sald-con', name = 'Aeotec Home Energy Meter 8 Settled Consumption', child_key = 'sald-con', endpoints = { 9 } }, + { profile = 'aeotec-home-energy-meter-gen8-sald-pro', name = 'Aeotec Home Energy Meter 8 Settled Production', child_key = 'sald-pro', endpoints = { 10 } } +} + +local function find_hem8_child_device_key_by_endpoint(endpoint) + for _, child in ipairs(HEM8_DEVICES) do + if child.endpoints then + for _, e in ipairs(child.endpoints) do + if e == endpoint then + return child.child_key + end + end + end + end +end + +local function meter_report_handler(driver, device, cmd, zb_rx) + local endpoint = cmd.src_channel + local device_to_emit_with = device + local child_device_key = find_hem8_child_device_key_by_endpoint(endpoint); + local child_device = device:get_child_by_parent_assigned_key(child_device_key) + + if(child_device) then + device_to_emit_with = child_device + end + + if cmd.args.scale == Meter.scale.electric_meter.KILOWATT_HOURS then + local event_arguments = { + value = cmd.args.meter_value, + unit = ENERGY_UNIT_KWH + } + -- energyMeter + device_to_emit_with:emit_event_for_endpoint( + cmd.src_channel, + capabilities.energyMeter.energy(event_arguments) + ) + + if endpoint == 9 then + -- powerConsumptionReport + power_consumption.emit_power_consumption_report_event(device, { value = event_arguments.value }) + end + elseif cmd.args.scale == Meter.scale.electric_meter.WATTS then + local event_arguments = { + value = cmd.args.meter_value, + unit = POWER_UNIT_WATT + } + -- powerMeter + device_to_emit_with:emit_event_for_endpoint( + cmd.src_channel, + capabilities.powerMeter.power(event_arguments) + ) + end +end + +local function do_refresh(self, device) + for _, d in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(d.endpoints) do + device:send(Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, {dst_channels = {endpoint}})) + device:send(Meter:Get({scale = Meter.scale.electric_meter.WATTS}, {dst_channels = {endpoint}})) + end + end +end + +local function device_added(driver, device) + if device.network_type == st_device.NETWORK_TYPE_ZWAVE and not (device.child_ids and utils.table_size(device.child_ids) ~= 0) then + for i, hem8_child in ipairs(HEM8_DEVICES) do + if(hem8_child["child_key"]) then + local name = hem8_child.name + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = hem8_child.profile, + parent_device_id = device.id, + parent_assigned_child_key = hem8_child.child_key, + vendor_provided_label = name + } + driver:try_create_device(metadata) + end + end + end + do_refresh(driver, device) +end + +local aeotec_home_energy_meter_gen8_3_phase = { + NAME = "Aeotec Home Energy Meter Gen8", + supported_capabilities = { + capabilities.powerConsumptionReport + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zwave_handlers = { + [cc.METER] = { + [Meter.REPORT] = meter_report_handler + } + }, + lifecycle_handlers = { + added = device_added + }, + can_handle = require("aeotec-home-energy-meter-gen8.3-phase.can_handle") +} + +return aeotec_home_energy_meter_gen8_3_phase diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/can_handle.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/can_handle.lua new file mode 100644 index 0000000000..918e703b69 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/can_handle.lua @@ -0,0 +1,22 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS = { + { mfr = 0x0371, prod = 0x0003, model = 0x0033 }, -- HEM Gen8 1 Phase EU + { mfr = 0x0371, prod = 0x0003, model = 0x0034 }, -- HEM Gen8 3 Phase EU + { mfr = 0x0371, prod = 0x0103, model = 0x002E }, -- HEM Gen8 2 Phase US + { mfr = 0x0371, prod = 0x0102, model = 0x002E }, -- HEM Gen8 1 Phase AU + { mfr = 0x0371, prod = 0x0102, model = 0x0034 }, -- HEM Gen8 3 Phase AU +} + +local function can_handle_aeotec_meter_gen8(opts, driver, device, ...) + for _, fingerprint in ipairs(AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("aeotec-home-energy-meter-gen8") + return true, subdriver + end + end + return false +end + +return can_handle_aeotec_meter_gen8 diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/init.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/init.lua new file mode 100644 index 0000000000..10d07de685 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/init.lua @@ -0,0 +1,49 @@ +-- Copyright 2025 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 function device_added(driver, device) + device:refresh() +end + +local function component_to_endpoint(device, component_id) + local ep_num = component_id:match("clamp(%d)") + return { ep_num and tonumber(ep_num) } +end + +local function endpoint_to_component(device, ep) + local meter_comp = string.format("clamp%d", ep) + if device.profile.components[meter_comp] ~= nil then + return meter_comp + else + return "main" + end +end + +local device_init = function(self, device) + device:set_component_to_endpoint_fn(component_to_endpoint) + device:set_endpoint_to_component_fn(endpoint_to_component) +end + +local do_configure = function (self, device) + device:send(Configuration:Set({parameter_number = 111, configuration_value = 300, size = 4})) -- ...every 5 min + device:send(Configuration:Set({parameter_number = 112, configuration_value = 300, size = 4})) -- ...every 5 min + device:send(Configuration:Set({parameter_number = 113, configuration_value = 300, size = 4})) -- ...every 5 min +end + +local aeotec_home_energy_meter_gen8 = { + NAME = "Aeotec Home Energy Meter Gen8", + lifecycle_handlers = { + added = device_added, + init = device_init, + doConfigure = do_configure + }, + can_handle = require("aeotec-home-energy-meter-gen8.can_handle"), + sub_drivers = { + require("aeotec-home-energy-meter-gen8.sub_drivers") + } +} + +return aeotec_home_energy_meter_gen8 diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/power_consumption.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/power_consumption.lua new file mode 100644 index 0000000000..2c32c5c964 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/power_consumption.lua @@ -0,0 +1,32 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2. + +local capabilities = require "st.capabilities" + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local power_consumption = {} + +power_consumption.emit_power_consumption_report_event = function (device, value) + -- powerConsumptionReport report interval + local current_time = os.time() + local last_time = device:get_field(LAST_REPORT_TIME) or 0 + local next_time = last_time + 60 * 15 -- 15 mins, the minimum interval allowed between reports + if current_time < next_time then + return + end + device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) + local raw_value = value.value * 1000 -- 'Wh' + + 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 - current_power_consumption.energy, 0.0) + end + device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ + energy = raw_value, + deltaEnergy = delta_energy + })) +end + +return power_consumption \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/sub_drivers.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/sub_drivers.lua new file mode 100644 index 0000000000..98c3fb45f8 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/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-home-energy-meter-gen8.1-phase"), + lazy_load_if_possible("aeotec-home-energy-meter-gen8.2-phase"), + lazy_load_if_possible("aeotec-home-energy-meter-gen8.3-phase") +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-electric-meter/src/init.lua b/drivers/SmartThings/zwave-electric-meter/src/init.lua index 66205758bd..851de3096f 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/init.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/init.lua @@ -7,11 +7,31 @@ local capabilities = require "st.capabilities" local defaults = require "st.zwave.defaults" --- @type st.zwave.Driver local ZwaveDriver = require "st.zwave.driver" +--- @type st.zwave.CommandClass.Configuration +local Configuration = (require "st.zwave.CommandClass.Configuration")({version=1}) + +local preferencesMap = require "preferences" local device_added = function (self, device) device:refresh() end +--- Handle preference changes +--- +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param event table +--- @param args +local function info_changed(driver, device, event, args) + local preferences = preferencesMap.get_device_parameters(device) + 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 = preferencesMap.to_numeric_value(device.preferences[id]) + device:send(Configuration:Set({ parameter_number = preferences[id].parameter_number, size = preferences[id].size, configuration_value = new_parameter_value })) + end + end +end + local driver_template = { supported_capabilities = { capabilities.powerMeter, @@ -19,6 +39,7 @@ local driver_template = { capabilities.refresh }, lifecycle_handlers = { + infoChanged = info_changed, added = device_added }, sub_drivers = require("sub_drivers"), diff --git a/drivers/SmartThings/zwave-electric-meter/src/preferences.lua b/drivers/SmartThings/zwave-electric-meter/src/preferences.lua new file mode 100644 index 0000000000..b1f14c25bd --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/preferences.lua @@ -0,0 +1,95 @@ +local devices = { + AEOTEC_HOME_ENERGY_METER_GEN8_1_PHASE = { + MATCHING_MATRIX = { + mfrs = 0x0371, + product_types = {0x0003, 0x0102 }, + product_ids = 0x0033 + }, + PARAMETERS = { + thresholdCheck = {parameter_number = 3, size = 1}, + imWThresholdTotal = {parameter_number = 4, size = 2}, + imWThresholdPhaseA = {parameter_number = 5, size = 2}, + exWThresholdTotal = {parameter_number = 8, size = 2}, + exWThresholdPhaseA = {parameter_number = 9, size = 2}, + imtWPctThresholdTotal = {parameter_number = 12, size = 1}, + imWPctThresholdPhaseA = {parameter_number = 13, size = 1}, + exWPctThresholdTotal = {parameter_number = 16, size = 1}, + exWPctThresholdPhaseA = {parameter_number = 17, size = 1}, + autoRootDeviceReport = {parameter_number = 32, size = 1}, + } + }, + AEOTEC_HOME_ENERGY_METER_GEN8_2_PHASE = { + MATCHING_MATRIX = { + mfrs = 0x0371, + product_types = 0x0103, + product_ids = 0x002E + }, + PARAMETERS = { + thresholdCheck = {parameter_number = 3, size = 1}, + imWThresholdTotal = {parameter_number = 4, size = 2}, + imWThresholdPhaseA = {parameter_number = 5, size = 2}, + imWThresholdPhaseB = {parameter_number = 6, size = 2}, + exWThresholdTotal = {parameter_number = 8, size = 2}, + exWThresholdPhaseA = {parameter_number = 9, size = 2}, + exWThresholdPhaseB = {parameter_number = 10, size = 2}, + imtWPctThresholdTotal = {parameter_number = 12, size = 1}, + imWPctThresholdPhaseA = {parameter_number = 13, size = 1}, + imWPctThresholdPhaseB = {parameter_number = 14, size = 1}, + exWPctThresholdTotal = {parameter_number = 16, size = 1}, + exWPctThresholdPhaseA = {parameter_number = 17, size = 1}, + exWPctThresholdPhaseB = {parameter_number = 18, size = 1}, + autoRootDeviceReport = {parameter_number = 32, size = 1}, + } + }, + AEOTEC_HOME_ENERGY_METER_GEN8_3_PHASE = { + MATCHING_MATRIX = { + mfrs = 0x0371, + product_types = {0x0003, 0x0102}, + product_ids = 0x0034 + }, + PARAMETERS = { + thresholdCheck = {parameter_number = 3, size = 1}, + imWThresholdTotal = {parameter_number = 4, size = 2}, + imWThresholdPhaseA = {parameter_number = 5, size = 2}, + imWThresholdPhaseB = {parameter_number = 6, size = 2}, + imWThresholdPhaseC = {parameter_number = 7, size = 2}, + exWThresholdTotal = {parameter_number = 8, size = 2}, + exWThresholdPhaseA = {parameter_number = 9, size = 2}, + exWThresholdPhaseB = {parameter_number = 10, size = 2}, + exWThresholdPhaseC = {parameter_number = 11, size = 2}, + imtWPctThresholdTotal = {parameter_number = 12, size = 1}, + imWPctThresholdPhaseA = {parameter_number = 13, size = 1}, + imWPctThresholdPhaseB = {parameter_number = 14, size = 1}, + imWPctThresholdPhaseC = {parameter_number = 15, size = 1}, + exWPctThresholdTotal = {parameter_number = 16, size = 1}, + exWPctThresholdPhaseA = {parameter_number = 17, size = 1}, + exWPctThresholdPhaseB = {parameter_number = 18, size = 1}, + exWPctThresholdPhaseC = {parameter_number = 19, size = 1}, + autoRootDeviceReport = {parameter_number = 32, size = 1}, + } + } +} + +local preferences = {} + +preferences.get_device_parameters = 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.PARAMETERS + end + end + return nil +end + +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 + +return preferences diff --git a/drivers/SmartThings/zwave-electric-meter/src/sub_drivers.lua b/drivers/SmartThings/zwave-electric-meter/src/sub_drivers.lua index 60d4a7380b..3c0e482bda 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/sub_drivers.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/sub_drivers.lua @@ -6,5 +6,6 @@ local sub_drivers = { lazy_load_if_possible("qubino-meter"), lazy_load_if_possible("aeotec-gen5-meter"), lazy_load_if_possible("aeon-meter"), + lazy_load_if_possible("aeotec-home-energy-meter-gen8"), } return sub_drivers diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_1_phase.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_1_phase.lua new file mode 100644 index 0000000000..f581f457df --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_1_phase.lua @@ -0,0 +1,560 @@ +-- 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 Meter = (require "st.zwave.CommandClass.Meter")({version=4}) +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) +local t_utils = require "integration_test.utils" + +local AEOTEC_MFR_ID = 0x0371 +local AEOTEC_METER_PROD_TYPE = 0x0003 +local AEOTEC_METER_PROD_ID = 0x0033 + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local aeotec_meter_endpoints = { + { + command_classes = { + {value = zw.METER} + } + } +} + +local HEM8_DEVICES = { + { + profile = 'aeotec-home-energy-meter-gen8-1-phase-con', + name = 'Aeotec Home Energy Meter 8 Consumption', + endpoints = { 1, 3 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-1-phase-pro', + name = 'Aeotec Home Energy Meter 8 Production', + child_key = 'pro', + endpoints = { 2, 4 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-sald-con', + name = 'Aeotec Home Energy Meter 8 Settled Consumption', + child_key = 'sald-con', + endpoints = { 5 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-sald-pro', + name = 'Aeotec Home Energy Meter 8 Settled Production', + child_key = 'sald-pro', + endpoints = { 6 } + } +} + +local mock_parent = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[1].profile .. '.yml'), + zwave_endpoints = aeotec_meter_endpoints, + zwave_manufacturer_id = AEOTEC_MFR_ID, + zwave_product_type = AEOTEC_METER_PROD_TYPE, + zwave_product_id = AEOTEC_METER_PROD_ID +}) + +local mock_child_prod = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[2].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[2].child_key +}) + +local mock_child_sald_con = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[3].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[3].child_key +}) + +local mock_child_sald_prod = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[4].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[4].child_key +}) + +local function test_init() + test.mock_device.add_test_device(mock_parent) + test.mock_device.add_test_device(mock_child_prod) + test.mock_device.add_test_device(mock_child_sald_con) + test.mock_device.add_test_device(mock_child_sald_prod) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Added lifecycle event should create children for parent device", + function() + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_parent.id, "added" }) + + for _, child in ipairs(HEM8_DEVICES) do + if(child["child_key"]) then + mock_parent:expect_device_create( + { + type = "EDGE_CHILD", + label = child.name, + profile = child.profile, + parent_device_id = mock_parent.id, + parent_assigned_child_key = child.child_key + } + ) + end + end + -- Refresh + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.WATTS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + end + end + end +) + +test.register_coroutine_test( + "Configure should configure all necessary attributes", + function() + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_parent.id, "doConfigure" }) + + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 111, size = 4, configuration_value = 300}) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 112, size = 4, configuration_value = 300}) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 113, size = 4, configuration_value = 300}) + )) + mock_parent:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_coroutine_test( + "Power meter report should be handled", + function() + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + local component = "main" + if endpoint ~= 3 and endpoint ~= 4 and endpoint ~= 5 and endpoint ~= 6 then + component = string.format("clamp%d", endpoint) + end + test.socket.zwave:__queue_receive({ + mock_parent.id, + Meter:Report({ + scale = Meter.scale.electric_meter.WATTS, + meter_value = 27 + }, { + encap = zw.ENCAP.AUTO, + src_channel = endpoint, + dst_channels = {0} + }) + }) + if(device["child_key"]) then + if(device["child_key"] == "pro") then + test.socket.capability:__expect_send( + mock_child_prod:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + elseif (device["child_key"] == "sald-pro") then + test.socket.capability:__expect_send( + mock_child_sald_prod:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + elseif (device["child_key"] == "sald-con") then + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + end + else + test.socket.capability:__expect_send( + mock_parent:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + end + end + end + end +) + +test.register_coroutine_test( + "Energy meter report should be handled", + function() + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + local component = "main" + if endpoint ~= 3 and endpoint ~= 4 and endpoint ~= 5 and endpoint ~= 6 then + component = string.format("clamp%d", endpoint) + end + test.socket.zwave:__queue_receive({ + mock_parent.id, + Meter:Report({ + scale = Meter.scale.electric_meter.KILOWATT_HOURS, + meter_value = 5 + }, { + encap = zw.ENCAP.AUTO, + src_channel = endpoint, + dst_channels = {0} + }) + }) + if(device["child_key"]) then + if(device["child_key"] == "pro") then + test.socket.capability:__expect_send( + mock_child_prod:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + elseif (device["child_key"] == "sald-pro") then + test.socket.capability:__expect_send( + mock_child_sald_prod:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + elseif (device["child_key"] == "sald-con") then + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + end + else + test.socket.capability:__expect_send( + mock_parent:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + end + end + end + end +) + +test.register_coroutine_test( + "Report consumption and power consumption report after 15 minutes", function() + -- set time to trigger power consumption report + local current_time = os.time() - 60 * 20 + --mock_child_sald_con:set_field(LAST_REPORT_TIME, current_time) + mock_parent:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zwave:__queue_receive( + { + --mock_child_sald_con.id, + mock_parent.id, + zw_test_utils.zwave_test_build_receive_command(Meter:Report( + { + scale = Meter.scale.electric_meter.KILOWATT_HOURS, + meter_value = 5 + }, + { + encap = zw.ENCAP.AUTO, + src_channel = 5, + dst_channels = {0} + } + )) + } + ) + + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + + test.socket.capability:__expect_send( + -- mock_parent + mock_parent:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 5000 })) + ) + end +) + +test.register_coroutine_test( + "Handle preference: thresholdCheck (parameter 3) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + thresholdCheck = 0 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 3, + configuration_value = 0, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdTotal (parameter 4) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdTotal = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 4, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdPhaseA (parameter 5) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdPhaseA = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 5, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdTotal (parameter 8) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdTotal = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 8, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdPhaseA (parameter 9) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdPhaseA = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 9, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imtWPctThresholdTotal (parameter 12) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imtWPctThresholdTotal = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 12, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWPctThresholdPhaseA (parameter 13) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWPctThresholdPhaseA = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 13, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdTotal (parameter 16) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdTotal = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 16, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdPhaseA (parameter 17) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdPhaseA = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 17, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: autoRootDeviceReport (parameter 32) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + autoRootDeviceReport = 1 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 32, + configuration_value = 1, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Refresh sends commands to all components including base device", + function() + -- refresh commands for zwave devices do not have guaranteed ordering + test.socket.zwave:__set_channel_ordering("relaxed") + + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.WATTS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + end + end + + test.socket.capability:__queue_receive({ + mock_parent.id, + { capability = "refresh", component = "main", command = "refresh", args = { } } + }) + end +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_2_phase.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_2_phase.lua new file mode 100644 index 0000000000..43ea5cdbe3 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_2_phase.lua @@ -0,0 +1,654 @@ +-- 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 Meter = (require "st.zwave.CommandClass.Meter")({version=4}) +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) +local t_utils = require "integration_test.utils" + +local AEOTEC_MFR_ID = 0x0371 +local AEOTEC_METER_PROD_TYPE = 0x0103 +local AEOTEC_METER_PROD_ID = 0x002E + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local aeotec_meter_endpoints = { + { + command_classes = { + {value = zw.METER} + } + } +} + +local HEM8_DEVICES = { + { + profile = 'aeotec-home-energy-meter-gen8-2-phase-con', + name = 'Aeotec Home Energy Meter 8 Consumption', + endpoints = { 1, 3, 5 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-2-phase-pro', + name = 'Aeotec Home Energy Meter 8 Production', + child_key = 'pro', + endpoints = { 2, 4, 6 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-sald-con', + name = 'Aeotec Home Energy Meter 8 Settled Consumption', + child_key = 'sald-con', + endpoints = { 7 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-sald-pro', + name = 'Aeotec Home Energy Meter 8 Settled Production', + child_key = 'sald-pro', + endpoints = { 8 } + } +} + +local mock_parent = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[1].profile .. '.yml'), + zwave_endpoints = aeotec_meter_endpoints, + zwave_manufacturer_id = AEOTEC_MFR_ID, + zwave_product_type = AEOTEC_METER_PROD_TYPE, + zwave_product_id = AEOTEC_METER_PROD_ID +}) + +local mock_child_prod = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[2].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[2].child_key +}) + +local mock_child_sald_con = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[3].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[3].child_key +}) + +local mock_child_sald_prod = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[4].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[4].child_key +}) + +local function test_init() + test.mock_device.add_test_device(mock_parent) + test.mock_device.add_test_device(mock_child_prod) + test.mock_device.add_test_device(mock_child_sald_con) + test.mock_device.add_test_device(mock_child_sald_prod) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Added lifecycle event should create children for parent device", + function() + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_parent.id, "added" }) + + for _, child in ipairs(HEM8_DEVICES) do + if(child["child_key"]) then + mock_parent:expect_device_create( + { + type = "EDGE_CHILD", + label = child.name, + profile = child.profile, + parent_device_id = mock_parent.id, + parent_assigned_child_key = child.child_key + } + ) + end + end + -- Refresh + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.WATTS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + end + end + end +) + +test.register_coroutine_test( + "Configure should configure all necessary attributes", + function() + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_parent.id, "doConfigure" }) + + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 111, size = 4, configuration_value = 300}) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 112, size = 4, configuration_value = 300}) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 113, size = 4, configuration_value = 300}) + )) + mock_parent:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_coroutine_test( + "Power meter report should be handled", + function() + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + local component = "main" + if endpoint ~= 5 and endpoint ~= 6 and endpoint ~= 7 and endpoint ~= 8 then + component = string.format("clamp%d", endpoint) + end + test.socket.zwave:__queue_receive({ + mock_parent.id, + Meter:Report({ + scale = Meter.scale.electric_meter.WATTS, + meter_value = 27 + }, { + encap = zw.ENCAP.AUTO, + src_channel = endpoint, + dst_channels = {0} + }) + }) + if(device["child_key"]) then + if(device["child_key"] == "pro") then + test.socket.capability:__expect_send( + mock_child_prod:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + elseif (device["child_key"] == "sald-pro") then + test.socket.capability:__expect_send( + mock_child_sald_prod:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + elseif (device["child_key"] == "sald-con") then + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + end + else + test.socket.capability:__expect_send( + mock_parent:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + end + end + end + end +) + +test.register_coroutine_test( + "Energy meter report should be handled", + function() + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + local component = "main" + if endpoint ~= 5 and endpoint ~= 6 and endpoint ~= 7 and endpoint ~= 8 then + component = string.format("clamp%d", endpoint) + end + test.socket.zwave:__queue_receive({ + mock_parent.id, + Meter:Report({ + scale = Meter.scale.electric_meter.KILOWATT_HOURS, + meter_value = 5 + }, { + encap = zw.ENCAP.AUTO, + src_channel = endpoint, + dst_channels = {0} + }) + }) + if(device["child_key"]) then + if(device["child_key"] == "pro") then + test.socket.capability:__expect_send( + mock_child_prod:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + elseif (device["child_key"] == "sald-pro") then + test.socket.capability:__expect_send( + mock_child_sald_prod:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + elseif (device["child_key"] == "sald-con") then + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + end + else + test.socket.capability:__expect_send( + mock_parent:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + end + end + end + end +) + +test.register_coroutine_test( + "Report consumption and power consumption report after 15 minutes", function() + -- set time to trigger power consumption report + local current_time = os.time() - 60 * 20 + --mock_child_sald_con:set_field(LAST_REPORT_TIME, current_time) + mock_parent:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zwave:__queue_receive( + { + mock_parent.id, + zw_test_utils.zwave_test_build_receive_command(Meter:Report( + { + scale = Meter.scale.electric_meter.KILOWATT_HOURS, + meter_value = 5 + }, + { + encap = zw.ENCAP.AUTO, + src_channel = 7, + dst_channels = {0} + } + )) + } + ) + + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + + test.socket.capability:__expect_send( + mock_parent:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 5000 })) + ) + end +) + +test.register_coroutine_test( + "Handle preference: thresholdCheck (parameter 3) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + thresholdCheck = 0 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 3, + configuration_value = 0, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdTotal (parameter 4) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdTotal = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 4, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdPhaseA (parameter 5) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdPhaseA = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 5, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdPhaseB (parameter 6) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdPhaseB = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 6, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdTotal (parameter 8) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdTotal = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 8, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdPhaseA (parameter 9) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdPhaseA = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 9, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdPhaseB (parameter 10) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdPhaseB = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 10, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imtWPctThresholdTotal (parameter 12) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imtWPctThresholdTotal = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 12, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWPctThresholdPhaseA (parameter 13) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWPctThresholdPhaseA = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 13, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWPctThresholdPhaseB (parameter 14) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWPctThresholdPhaseB = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 14, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdTotal (parameter 16) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdTotal = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 16, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdPhaseA (parameter 17) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdPhaseA = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 17, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdPhaseB (parameter 18) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdPhaseB = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 18, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: autoRootDeviceReport (parameter 32) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + autoRootDeviceReport = 1 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 32, + configuration_value = 1, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Refresh sends commands to all components including base device", + function() + -- refresh commands for zwave devices do not have guaranteed ordering + test.socket.zwave:__set_channel_ordering("relaxed") + + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.WATTS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + end + end + + test.socket.capability:__queue_receive({ + mock_parent.id, + { capability = "refresh", component = "main", command = "refresh", args = { } } + }) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_3_phase.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_3_phase.lua new file mode 100644 index 0000000000..465e3add9f --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_3_phase.lua @@ -0,0 +1,749 @@ +-- 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 Meter = (require "st.zwave.CommandClass.Meter")({version=4}) +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=1 }) +local t_utils = require "integration_test.utils" + +local AEOTEC_MFR_ID = 0x0371 +local AEOTEC_METER_PROD_TYPE = 0x0003 +local AEOTEC_METER_PROD_ID = 0x0034 + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local aeotec_meter_endpoints = { + { + command_classes = { + {value = zw.METER} + } + } +} + +local HEM8_DEVICES = { + { + profile = 'aeotec-home-energy-meter-gen8-3-phase-con', + name = 'Aeotec Home Energy Meter 8 Consumption', + endpoints = { 1, 3, 5, 7 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-3-phase-pro', + name = 'Aeotec Home Energy Meter 8 Production', + child_key = 'pro', + endpoints = { 2, 4, 6, 8 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-sald-con', + name = 'Aeotec Home Energy Meter 8 Settled Consumption', + child_key = 'sald-con', + endpoints = { 9 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-sald-pro', + name = 'Aeotec Home Energy Meter 8 Settled Production', + child_key = 'sald-pro', + endpoints = { 10 } + } +} + +local mock_parent = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[1].profile .. '.yml'), + zwave_endpoints = aeotec_meter_endpoints, + zwave_manufacturer_id = AEOTEC_MFR_ID, + zwave_product_type = AEOTEC_METER_PROD_TYPE, + zwave_product_id = AEOTEC_METER_PROD_ID +}) + +local mock_child_prod = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[2].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[2].child_key +}) + +local mock_child_sald_con = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[3].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[3].child_key +}) + +local mock_child_sald_prod = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[4].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[4].child_key +}) + +local function test_init() + test.mock_device.add_test_device(mock_parent) + test.mock_device.add_test_device(mock_child_prod) + test.mock_device.add_test_device(mock_child_sald_con) + test.mock_device.add_test_device(mock_child_sald_prod) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Added lifecycle event should create children for parent device", + function() + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_parent.id, "added" }) + + for _, child in ipairs(HEM8_DEVICES) do + if(child["child_key"]) then + mock_parent:expect_device_create( + { + type = "EDGE_CHILD", + label = child.name, + profile = child.profile, + parent_device_id = mock_parent.id, + parent_assigned_child_key = child.child_key + } + ) + end + end + -- Refresh + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.WATTS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + end + end + end +) + +test.register_coroutine_test( + "Configure should configure all necessary attributes", + function() + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_parent.id, "doConfigure" }) + + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 111, size = 4, configuration_value = 300}) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 112, size = 4, configuration_value = 300}) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 113, size = 4, configuration_value = 300}) + )) + mock_parent:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_coroutine_test( + "Power meter report should be handled", + function() + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + local component = "main" + if endpoint ~= 7 and endpoint ~= 8 and endpoint ~= 9 and endpoint ~= 10 then + component = string.format("clamp%d", endpoint) + end + test.socket.zwave:__queue_receive({ + mock_parent.id, + Meter:Report({ + scale = Meter.scale.electric_meter.WATTS, + meter_value = 27 + }, { + encap = zw.ENCAP.AUTO, + src_channel = endpoint, + dst_channels = {0} + }) + }) + if(device["child_key"]) then + if(device["child_key"] == "pro") then + test.socket.capability:__expect_send( + mock_child_prod:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + elseif (device["child_key"] == "sald-pro") then + test.socket.capability:__expect_send( + mock_child_sald_prod:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + elseif (device["child_key"] == "sald-con") then + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + end + else + test.socket.capability:__expect_send( + mock_parent:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + end + end + end + end +) + +test.register_coroutine_test( + "Energy meter report should be handled", + function() + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + local component = "main" + if endpoint ~= 7 and endpoint ~= 8 and endpoint ~= 9 and endpoint ~= 10 then + component = string.format("clamp%d", endpoint) + end + test.socket.zwave:__queue_receive({ + mock_parent.id, + Meter:Report({ + scale = Meter.scale.electric_meter.KILOWATT_HOURS, + meter_value = 5 + }, { + encap = zw.ENCAP.AUTO, + src_channel = endpoint, + dst_channels = {0} + }) + }) + if(device["child_key"]) then + if(device["child_key"] == "pro") then + test.socket.capability:__expect_send( + mock_child_prod:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + elseif (device["child_key"] == "sald-pro") then + test.socket.capability:__expect_send( + mock_child_sald_prod:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + elseif (device["child_key"] == "sald-con") then + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + end + else + test.socket.capability:__expect_send( + mock_parent:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + end + end + end + end +) + +test.register_coroutine_test( + "Report consumption and power consumption report after 15 minutes", function() + -- set time to trigger power consumption report + local current_time = os.time() - 60 * 20 + -- mock_child_sald_con:set_field(LAST_REPORT_TIME, current_time) + mock_parent:set_field(LAST_REPORT_TIME, current_time) + test.socket.zwave:__queue_receive( + { + mock_parent.id, + zw_test_utils.zwave_test_build_receive_command(Meter:Report( + { + scale = Meter.scale.electric_meter.KILOWATT_HOURS, + meter_value = 5 + }, + { + encap = zw.ENCAP.AUTO, + src_channel = 9, + dst_channels = {0} + } + )) + } + ) + + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + + test.socket.capability:__expect_send( + mock_parent:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 5000 })) + ) + end +) + +test.register_coroutine_test( + "Handle preference: thresholdCheck (parameter 3) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + thresholdCheck = 0 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 3, + configuration_value = 0, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdTotal (parameter 4) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdTotal = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 4, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdPhaseA (parameter 5) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdPhaseA = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 5, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdPhaseB (parameter 6) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdPhaseB = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 6, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdPhaseC (parameter 7) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdPhaseC = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 7, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdTotal (parameter 8) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdTotal = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 8, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdPhaseA (parameter 9) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdPhaseA = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 9, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdPhaseB (parameter 10) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdPhaseB = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 10, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdPhaseC (parameter 11) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdPhaseC = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 11, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imtWPctThresholdTotal (parameter 12) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imtWPctThresholdTotal = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 12, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWPctThresholdPhaseA (parameter 13) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWPctThresholdPhaseA = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 13, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWPctThresholdPhaseB (parameter 14) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWPctThresholdPhaseB = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 14, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWPctThresholdPhaseC (parameter 15) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWPctThresholdPhaseC = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 15, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdTotal (parameter 16) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdTotal = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 16, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdPhaseA (parameter 17) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdPhaseA = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 17, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdPhaseB (parameter 18) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdPhaseB = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 18, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: thresholdCheck (exWPctThresholdPhaseC 19) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdPhaseC = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 19, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: autoRootDeviceReport (parameter 32) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + autoRootDeviceReport = 1 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 32, + configuration_value = 1, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Refresh sends commands to all components including base device", + function() + -- refresh commands for zwave devices do not have guaranteed ordering + test.socket.zwave:__set_channel_ordering("relaxed") + + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.WATTS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + end + end + + test.socket.capability:__queue_receive({ + mock_parent.id, + { capability = "refresh", component = "main", command = "refresh", args = { } } + }) + end +) + +test.run_registered_tests() \ No newline at end of file From ff3cdb042f37b18b9d4adf4d4ad6a8d19b5921f8 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Tue, 14 Apr 2026 11:43:52 -0500 Subject: [PATCH 230/538] Remove unused init function Co-authored-by: Nick DeBoom --- .../camera/camera_utils/device_configuration.lua | 8 -------- 1 file changed, 8 deletions(-) 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 index a31473f993..398150d063 100644 --- 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 @@ -375,14 +375,6 @@ function CameraDeviceConfiguration.initialize_camera_capabilities(device) init_camera_privacy_mode(device) end -function CameraDeviceConfiguration.initialize_camera_capabilities_and_subscriptions(device) - CameraDeviceConfiguration.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 - local function initialize_selected_camera_capabilities(device, capabilities_to_reinit) local reinit_targets = capabilities_to_reinit or {} From 8c3a810ddd99ef41a8f9cb52d76a4f9c63f3a659 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Thu, 9 Apr 2026 14:00:54 -0500 Subject: [PATCH 231/538] Fixing zigbee-switch tests for ColorTempPhysMireds - Splitting tests based off api_version --- .github/workflows/jenkins-driver-tests.yml | 6 +- .../test/test_all_capability_zigbee_bulb.lua | 147 +++++++++- .../src/test/test_aqara_led_bulb.lua | 70 ++++- .../src/test/test_aqara_light.lua | 72 ++++- .../test/test_duragreen_color_temp_bulb.lua | 126 ++++++++- .../zigbee-switch/src/test/test_rgbw_bulb.lua | 78 +++++- .../src/test/test_sengled_color_temp_bulb.lua | 125 ++++++++- .../src/test/test_white_color_temp_bulb.lua | 127 ++++++++- .../src/test/test_zll_color_temp_bulb.lua | 143 +++++++++- .../src/test/test_zll_rgbw_bulb.lua | 251 +++++++++++++++++- 10 files changed, 1121 insertions(+), 24 deletions(-) diff --git a/.github/workflows/jenkins-driver-tests.yml b/.github/workflows/jenkins-driver-tests.yml index 1e29ea8afc..153b8f6913 100644 --- a/.github/workflows/jenkins-driver-tests.yml +++ b/.github/workflows/jenkins-driver-tests.yml @@ -4,6 +4,10 @@ on: paths: - 'drivers/**' + pull_request_target: + paths: + - 'drivers/**' + permissions: statuses: write @@ -12,7 +16,7 @@ jobs: strategy: matrix: version: - [ 59, 60 ] + [ 59, 60, dev] runs-on: ubuntu-latest steps: 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 cf4afb92c2..5e27983718 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 @@ -423,10 +423,155 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) +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, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + OnOff.attributes.OnOff:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Level.attributes.CurrentLevel:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.CurrentHue:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.CurrentSaturation: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, + OnOff.ID) + }) + 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) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePower:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, + zigbee_test_utils.mock_hub_eui, + Level.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, + zigbee_test_utils.mock_hub_eui, + ColorControl.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 0x0010) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.CurrentHue:configure_reporting(mock_device, 1, 3600, 0x0010) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.CurrentSaturation:configure_reporting(mock_device, 1, 3600, 0x0010) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + }) + 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, + 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, + zigbee_test_utils.build_bind_request(mock_device, + zigbee_test_utils.mock_hub_eui, + ElectricalMeasurement.ID) + }) + 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, + 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.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.Multiplier:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Divisor:read(mock_device) + }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 20 + } +) -- test.register_coroutine_test( -- "health check coroutine", -- function() 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 b9914ff4af..cc3e86d218 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 @@ -109,7 +109,75 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 + } +) + +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, + ColorControl.commands.MoveToColorTemperature(mock_device, 200, 0) + } + ) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Level.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + 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, + OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 20 } ) 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 f0b57744d3..a80bf1dcfc 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua @@ -106,15 +106,85 @@ test.register_coroutine_test( OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300, 1) } ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 20 + } +) + + +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, Level.attributes.OnTransitionTime:write(mock_device, 0) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OffTransitionTime:write(mock_device, 0) }) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.commands.MoveToColorTemperature(mock_device, 200, 0) + } + ) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Level.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + 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, + OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300, 1) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) 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 be8bc457e5..d1bebb68d5 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 @@ -38,6 +38,9 @@ test.register_coroutine_test( 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, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds: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, OnOff.ID) @@ -69,6 +72,18 @@ test.register_coroutine_test( ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) } ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) @@ -78,7 +93,113 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +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, Level.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.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, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17, + max_api_version = 19 + } +) + +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, + OnOff.attributes.OnOff:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + Level.attributes.CurrentLevel:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) + } + } + }, + { + inner_block_ordering = "relaxed", + min_api_version = 20 } ) @@ -117,7 +238,8 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) 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 f025655b26..4a83d8b920 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_rgbw_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_rgbw_bulb.lua @@ -81,6 +81,18 @@ test.register_coroutine_test( ColorControl.attributes.CurrentSaturation:configure_reporting(mock_device, 1, 3600, 16) } ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) @@ -90,7 +102,71 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + + +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, Level.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.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, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.CurrentHue:configure_reporting(mock_device, 1, 3600, 16) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.CurrentSaturation:configure_reporting(mock_device, 1, 3600, 16) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17, + max_api_version = 19 } ) 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 eae21e8ed7..356d7dbe31 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 @@ -69,16 +69,136 @@ test.register_coroutine_test( ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) } ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +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, Level.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.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, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17, + max_api_version = 19 + } +) + +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, + OnOff.attributes.OnOff:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + Level.attributes.CurrentLevel:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) + } + } + }, + { + inner_block_ordering = "relaxed", + min_api_version = 20 } ) @@ -117,7 +237,8 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) 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 bdcd61d03a..8a0db9d65c 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 @@ -38,6 +38,9 @@ test.register_coroutine_test( 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, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds: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, OnOff.ID) @@ -69,6 +72,18 @@ test.register_coroutine_test( ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) } ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) @@ -78,7 +93,114 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +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, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds: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, OnOff.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Level.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.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, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17, + max_api_version = 19 + } +) + +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, + OnOff.attributes.OnOff:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + Level.attributes.CurrentLevel:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) + } + } + }, + { + inner_block_ordering = "relaxed", + min_api_version = 20 } ) @@ -117,7 +239,8 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) 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 2ada61a3e6..a02e3978f5 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 @@ -42,12 +42,54 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 20 } ) +test.register_coroutine_test( + "Refresh necessary attributes", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + end, + { + min_api_version = 17, + max_api_version = 19 + } +) + +test.register_coroutine_test( + "ZLL periodic poll should occur", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + test.wait_for_events() + + test.mock_time.advance_time(50000) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.wait_for_events() + end, + { + test_init = function() + test.mock_device.add_test_device(mock_device) + test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "polling") + end, + min_api_version = 20 + } +) + test.register_coroutine_test( "ZLL periodic poll should occur", function() @@ -67,7 +109,8 @@ test.register_coroutine_test( test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "polling") end, - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) @@ -84,9 +127,31 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +test.register_coroutine_test( + "Switch command on should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "on", args = {} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "on") end + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.On(mock_device)}) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + end, + { + min_api_version = 17, + max_api_version = 19 } ) @@ -103,9 +168,31 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +test.register_coroutine_test( + "Switch command off should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "off", args = {} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "off") end + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.Off(mock_device)}) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + end, + { + min_api_version = 17, + max_api_version = 19 } ) @@ -122,9 +209,31 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +test.register_coroutine_test( + "SwitchLevel command setLevel should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switchLevel", component = "main", command = "setLevel", args = {50} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switchLevel", "setLevel") end + test.socket.zigbee:__expect_send({ mock_device.id, Level.commands.MoveToLevelWithOnOff(mock_device, math.floor(50 / 100.0 * 254), 0xFFFF)}) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + end, + { + min_api_version = 17, + max_api_version = 19 } ) @@ -142,10 +251,32 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 20 } ) +test.register_coroutine_test( + "ColorTemperature command setColorTemperature should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {200} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("colorTemperature", "setColorTemperature") end + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.On(mock_device)}) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.commands.MoveToColorTemperature(mock_device, 5000, 0x0000)}) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + end, + { + min_api_version = 17, + max_api_version = 19 + } +) test.run_registered_tests() 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 e05344d6a8..4e2ad43b06 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 @@ -34,6 +34,78 @@ end test.set_test_init_function(test_init) +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, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds: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, OnOff.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Level.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.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, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.CurrentHue:configure_reporting(mock_device, 1, 3600, 16) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.CurrentSaturation:configure_reporting(mock_device, 1, 3600, 16) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 20 + } +) + + test.register_coroutine_test( "Configure should configure all necessary attributes and refresh device", function() @@ -88,7 +160,8 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) @@ -102,12 +175,58 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +test.register_coroutine_test( + "Refresh necessary attributes", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + end, + { + min_api_version = 17, + max_api_version = 19 } ) +test.register_coroutine_test( + "ZLL periodic poll should occur", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + test.wait_for_events() + + test.mock_time.advance_time(5*60) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.wait_for_events() + end, + { + test_init = function() + test.mock_device.add_test_device(mock_device) + test.timer.__create_and_queue_test_time_advance_timer(5*60, "interval", "polling") + end, + min_api_version = 20 + } +) + test.register_coroutine_test( "ZLL periodic poll should occur", function() @@ -129,7 +248,8 @@ test.register_coroutine_test( test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(5*60, "interval", "polling") end, - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) @@ -151,9 +271,65 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + end, { - min_api_version = 17 + min_api_version = 20 + } +) + +test.register_coroutine_test( + "Capability 'switch' command 'on' should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "on", args = {} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "on") end + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.On(mock_device) }) + + test.wait_for_events() + test.mock_time.advance_time(2) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + + end, + { + min_api_version = 17, + max_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability 'switch' command 'off' should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "off", args = {} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "off") end + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.Off(mock_device) }) + + test.wait_for_events() + test.mock_time.advance_time(2) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + + end, + { + min_api_version = 20 } ) @@ -175,9 +351,38 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability 'switchLevel' command 'setLevel' on should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 57 } } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switchLevel", "setLevel") end + + test.socket.zigbee:__expect_send({ mock_device.id, Level.server.commands.MoveToLevelWithOnOff(mock_device, 144, 0xFFFF) }) + + test.wait_for_events() + test.mock_time.advance_time(2) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + + end, + { + min_api_version = 20 } ) @@ -199,9 +404,39 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 + } +) + +test.register_coroutine_test( + "ColorTemperature command setColorTemperature should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {200} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("colorTemperature", "setColorTemperature") end + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.On(mock_device)}) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.commands.MoveToColorTemperature(mock_device, 5000, 0x0000)}) + + test.wait_for_events() + test.mock_time.advance_time(2) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + + end, + { + min_api_version = 20 } ) @@ -224,9 +459,11 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) From 29fb178ab05c9acad89535e67d7cba31746237cb Mon Sep 17 00:00:00 2001 From: zmguko <160391709+zmguko@users.noreply.github.com> Date: Fri, 10 Apr 2026 22:28:16 +0800 Subject: [PATCH 232/538] WWSTCERT-10696 - add new zigbee-humididt-sensor SNZB-02DR2 (#2573) * add new zigbee-humididt-sensor SNZB-02DR2 --- drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml | 5 +++++ .../zigbee-humidity-sensor/src/configurations.lua | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml b/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml index e5ad9e571a..d897ca7ecf 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml +++ b/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml @@ -83,6 +83,11 @@ zigbeeManufacturer: manufacturer: eWeLink model: SNZB-02P deviceProfileName: humidity-temp-battery + - id: "SONOFF/SNZB-02DR2" + deviceLabel: "SONOFF SNZB-02DR2" + manufacturer: SONOFF + model: SNZB-02DR2 + deviceProfileName: humidity-temp-battery - id: "Third Reality/3RTHS24BZ" deviceLabel: ThirdReality Temperature and Humidity Sensor manufacturer: Third Reality, Inc diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/configurations.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/configurations.lua index 8bc35c8765..043359768f 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/configurations.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/configurations.lua @@ -56,7 +56,8 @@ local devices = { EWELINK_HUMIDITY_TEMP_SENSOR = { FINGERPRINTS = { { mfr = "eWeLink", model = "TH01" }, - { mfr = "eWeLink", model = "SNZB-02P" } + { mfr = "eWeLink", model = "SNZB-02P" }, + { mfr = "SONOFF", model = "SNZB-02DR2" } }, CONFIGURATION = { { From 6f65fd411924b9e3bee955d9cc8469343f5f576c Mon Sep 17 00:00:00 2001 From: JerryYang01 <40193349+JerryYang01@users.noreply.github.com> Date: Fri, 10 Apr 2026 04:37:55 +0800 Subject: [PATCH 233/538] Fix pad19 fingerprint (#2878) * Add fingerprint for PAD19 dimmer * Add fingerprint for PAD19 dimmer * Add fingerprint for PAD19 dimmer --- drivers/SmartThings/zwave-switch/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index 46faa7d106..ccd11184d5 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -916,6 +916,12 @@ zwaveManufacturer: manufacturerId: 0x010F productType: 0x0102 deviceProfileName: fibaro-dimmer-2 + - id: 013C/0005/008A + deviceLabel: Philio Dimmer Switch PAD19 + manufacturerId: 0x013C + productType: 0x0005 + productId: 0x008A + deviceProfileName: switch-level #Zooz - id: "Zooz/ZEN05" deviceLabel: Zooz Outdoor Plug ZEN05 From a44596dc9c0f916deeed2ffb7cc5fbd8cfbc03ad Mon Sep 17 00:00:00 2001 From: Inovelli <37669481+InovelliUSA@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:34:04 -0600 Subject: [PATCH 234/538] WWSTCERT-9786 Inovelli - adding vzw31 red series dimmer switch (#2654) * Inovelli - adding vzw31 red series dimmer switch * needed to add multilevel report handler to pass test suite * adding tests for vzw31 * removing extra code for button value init --- .../SmartThings/zwave-switch/fingerprints.yml | 6 + .../profiles/inovelli-dimmer-vzw31-sn.yml | 284 +++++++++++++++ .../zwave-switch/src/inovelli/can_handle.lua | 3 +- .../zwave-switch/src/inovelli/sub_drivers.lua | 1 + .../src/inovelli/vzw31-sn/can_handle.lua | 19 + .../src/inovelli/vzw31-sn/init.lua | 77 ++++ .../zwave-switch/src/preferences.lua | 27 ++ .../src/test/test_inovelli_vzw31_sn.lua | 287 +++++++++++++++ .../src/test/test_inovelli_vzw31_sn_child.lua | 335 ++++++++++++++++++ .../test_inovelli_vzw31_sn_preferences.lua | 151 ++++++++ 10 files changed, 1189 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zwave-switch/profiles/inovelli-dimmer-vzw31-sn.yml create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/init.lua create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn.lua create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn_child.lua create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn_preferences.lua diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index ccd11184d5..d0d10f2e5a 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -56,6 +56,12 @@ zwaveManufacturer: productType: 0x0003 productId: 0x0001 deviceProfileName: inovelli-dimmer + - id: "Inovelli/VZW31-SN" + deviceLabel: Inovelli Dimmer Red Series + manufacturerId: 0x031E + productType: 0x0015 + productId: 0x0001 + deviceProfileName: inovelli-dimmer-vzw31-sn - id: "Inovelli/VZW32-SN" deviceLabel: Inovelli mmWave Dimmer Red Series manufacturerId: 0x031E diff --git a/drivers/SmartThings/zwave-switch/profiles/inovelli-dimmer-vzw31-sn.yml b/drivers/SmartThings/zwave-switch/profiles/inovelli-dimmer-vzw31-sn.yml new file mode 100644 index 0000000000..e2da4a3239 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/profiles/inovelli-dimmer-vzw31-sn.yml @@ -0,0 +1,284 @@ +name: inovelli-dimmer-vzw31-sn +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - 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: "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: true + preferenceType: enumeration + definition: + options: + "0": "None (default)" + "1": "3-Way Dumb Switch" + "2": "3-Way Aux Switch" + "3": "Single Pole Full Sine Wave" + 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 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/can_handle.lua b/drivers/SmartThings/zwave-switch/src/inovelli/can_handle.lua index 7c0f6be77b..c8ba7ac681 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli/can_handle.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli/can_handle.lua @@ -3,6 +3,7 @@ local INOVELLI_FINGERPRINTS = { { mfr = 0x031E, prod = 0x0017, model = 0x0001 }, -- Inovelli VZW32-SN + { mfr = 0x031E, prod = 0x0015, model = 0x0001 }, -- Inovelli VZW31-SN { mfr = 0x031E, prod = 0x0001, model = 0x0001 }, -- Inovelli LZW31SN { mfr = 0x031E, prod = 0x0003, model = 0x0001 }, -- Inovelli LZW31 } @@ -17,4 +18,4 @@ local function can_handle_inovelli(opts, driver, device, ...) return false end -return can_handle_inovelli +return can_handle_inovelli \ 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 index e182120ece..2fdea81379 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli/sub_drivers.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli/sub_drivers.lua @@ -5,5 +5,6 @@ local lazy_load = require "lazy_load_subdriver" return { lazy_load("inovelli.lzw31-sn"), + lazy_load("inovelli.vzw31-sn"), lazy_load("inovelli.vzw32-sn") } diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/can_handle.lua b/drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/can_handle.lua new file mode 100644 index 0000000000..2446c06dde --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli/vzw31-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_VZW31_SN_PRODUCT_TYPE = 0x0015 +local INOVELLI_DIMMER_PRODUCT_ID = 0x0001 + +local function can_handle_vzw31_sn(opts, driver, device, ...) + if device:id_match( + INOVELLI_MANUFACTURER_ID, + INOVELLI_VZW31_SN_PRODUCT_TYPE, + INOVELLI_DIMMER_PRODUCT_ID + ) then + return true, require("inovelli.vzw31-sn") + end + return false +end + +return can_handle_vzw31_sn diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/init.lua new file mode 100644 index 0000000000..1dec88e745 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli/vzw31-sn/init.lua @@ -0,0 +1,77 @@ +-- 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 cc = require "st.zwave.CommandClass" + + +local supported_button_values = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"} + + +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 ~= "LEDColorConfiguration" then + device:emit_component_event( + component, + capabilities.button.supportedButtonValues( + supported_button_values, + { 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 function onoff_level_report_handler(driver, device, cmd) + local value = cmd.args.target_value and cmd.args.target_value or cmd.args.value + device:emit_event(value == 0 and capabilities.switch.switch.off() or capabilities.switch.switch.on()) + device:emit_event(capabilities.switchLevel.level(value)) +end + +local vzw31_sn = { + NAME = "Inovelli VZW31-SN Dimmer", + lifecycle_handlers = { + added = device_added, + }, + zwave_handlers = { + [cc.SWITCH_MULTILEVEL] = { + [SwitchMultilevel.REPORT] = onoff_level_report_handler + } + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = refresh_handler + } + }, + can_handle = require("inovelli.vzw31-sn.can_handle") +} + +return vzw31_sn \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/preferences.lua b/drivers/SmartThings/zwave-switch/src/preferences.lua index 798efaddf0..09155cdd7d 100644 --- a/drivers/SmartThings/zwave-switch/src/preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/preferences.lua @@ -59,6 +59,33 @@ local devices = { switchType = {parameter_number = 22, size = 1} } }, + INOVELLI_VZW31_SN = { + MATCHING_MATRIX = { + mfrs = 0x031E, + product_types = {0x0015}, + 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}, + parameter22 = {parameter_number = 22, size = 1}, + 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}, + } + }, INOVELLI_VZW32_SN = { MATCHING_MATRIX = { mfrs = 0x031E, diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn.lua new file mode 100644 index 0000000000..a372a94eec --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn.lua @@ -0,0 +1,287 @@ +-- 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 Meter = (require "st.zwave.CommandClass.Meter")({version=3}) +local t_utils = require "integration_test.utils" + +-- Inovelli VZW31-SN device identifiers +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW31_SN_PRODUCT_TYPE = 0x0015 +local INOVELLI_VZW31_SN_PRODUCT_ID = 0x0001 +local LED_BAR_COMPONENT_NAME = "LEDColorConfiguration" + +-- Device endpoints with supported command classes +local inovelli_vzw31_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.METER}, + } + } +} + +-- Create mock device +local mock_inovelli_vzw31_sn = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("inovelli-dimmer-vzw31-sn.yml"), + zwave_endpoints = inovelli_vzw31_sn_endpoints, + zwave_manufacturer_id = INOVELLI_MANUFACTURER_ID, + zwave_product_type = INOVELLI_VZW31_SN_PRODUCT_TYPE, + zwave_product_id = INOVELLI_VZW31_SN_PRODUCT_ID +}) + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzw31_sn) +end +test.set_test_init_function(test_init) + +local supported_button_values = {"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_vzw31_sn.id, "added" }) + + for button_name, _ in pairs(mock_inovelli_vzw31_sn.profile.components) do + if button_name ~= "main" and button_name ~= LED_BAR_COMPONENT_NAME then + test.socket.capability:__expect_send( + mock_inovelli_vzw31_sn:generate_test_message( + button_name, + capabilities.button.supportedButtonValues( + supported_button_values, + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_inovelli_vzw31_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_vzw31_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_vzw31_sn, + SwitchMultilevel:Get({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }) + ) + ) + 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_vzw31_sn.id, + { capability = "switch", command = "on", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_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_vzw31_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_vzw31_sn.id, + { capability = "switch", command = "off", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_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_vzw31_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_vzw31_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_vzw31_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_vzw31_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_vzw31_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_vzw31_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_vzw31_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_vzw31_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 and meter data", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzw31_sn.id, + { capability = "refresh", command = "refresh", args = {} } + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + SwitchMultilevel:Get({}) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn_child.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn_child.lua new file mode 100644 index 0000000000..dea43715ab --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_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 VZW31-SN device identifiers +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW31_SN_PRODUCT_TYPE = 0x0015 +local INOVELLI_VZW31_SN_PRODUCT_ID = 0x0001 + +-- Device endpoints with supported command classes +local inovelli_vzw31_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-dimmer-vzw31-sn.yml"), + zwave_endpoints = inovelli_vzw31_sn_endpoints, + zwave_manufacturer_id = INOVELLI_MANUFACTURER_ID, + zwave_product_type = INOVELLI_VZW31_SN_PRODUCT_TYPE, + zwave_product_id = INOVELLI_VZW31_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 (Gen3 uses parameter 99, same as vzw32) +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 (Gen3) + 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 (Gen3) + 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 (Gen3) + 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) + 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_vzw31_sn_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_sn_preferences.lua new file mode 100644 index 0000000000..2b4812ad7b --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw31_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 VZW31-SN device identifiers +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW31_SN_PRODUCT_TYPE = 0x0015 +local INOVELLI_VZW31_SN_PRODUCT_ID = 0x0001 + +-- Device endpoints with supported command classes +local inovelli_vzw31_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_vzw31_sn = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("inovelli-dimmer-vzw31-sn.yml"), + zwave_endpoints = inovelli_vzw31_sn_endpoints, + zwave_manufacturer_id = INOVELLI_MANUFACTURER_ID, + zwave_product_type = INOVELLI_VZW31_SN_PRODUCT_TYPE, + zwave_product_id = INOVELLI_VZW31_SN_PRODUCT_ID +}) + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzw31_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_vzw31_sn:generate_info_changed({preferences = {parameter1 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_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_vzw31_sn:generate_info_changed({preferences = {parameter52 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_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_vzw31_sn:generate_info_changed({preferences = {parameter158 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + Configuration:Set({ + parameter_number = 158, + configuration_value = new_param_value, + size = 1 + }) + ) + ) + end + ) +end + +-- Test parameter 19 (2-byte parameter); must be non-default (default 3600) or driver won't send Configuration:Set +do + local new_param_value = 1800 + test.register_coroutine_test( + "Parameter 19 should be updated in the device configuration after change", + function() + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzw31_sn:generate_info_changed({preferences = {parameter19 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw31_sn, + Configuration:Set({ + parameter_number = 19, + 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_vzw31_sn:generate_info_changed({preferences = {notificationChild = new_param_value}})) + + -- Expect child device creation + mock_inovelli_vzw31_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_vzw31_sn.id, + parent_assigned_child_key = "notification" + }) + end + ) +end + +test.run_registered_tests() From 201672667a998a93fd5e65b02645b53f1763d8ea Mon Sep 17 00:00:00 2001 From: Jeff Page Date: Thu, 9 Apr 2026 15:49:33 -0500 Subject: [PATCH 235/538] WWSTCERT-9857 Add Zooz ZSE50 to zwave-siren (for WWST Cert) (#2681) * Add Zooz ZSE50 to zwave-siren --- .../SmartThings/zwave-siren/fingerprints.yml | 8 +- .../zwave-siren/profiles/zooz-zse50.yml | 309 ++++++++ drivers/SmartThings/zwave-siren/src/init.lua | 3 +- .../zwave-siren/src/preferences.lua | 28 +- .../zwave-siren/src/sub_drivers.lua | 1 + .../zwave-siren/src/test/test_zooz_zse50.lua | 717 ++++++++++++++++++ .../zwave-siren/src/zooz-zse50/can_handle.lua | 14 + .../src/zooz-zse50/fingerprints.lua | 8 + .../zwave-siren/src/zooz-zse50/init.lua | 367 +++++++++ 9 files changed, 1452 insertions(+), 3 deletions(-) create mode 100644 drivers/SmartThings/zwave-siren/profiles/zooz-zse50.yml create mode 100644 drivers/SmartThings/zwave-siren/src/test/test_zooz_zse50.lua create mode 100644 drivers/SmartThings/zwave-siren/src/zooz-zse50/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/zooz-zse50/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-siren/src/zooz-zse50/init.lua diff --git a/drivers/SmartThings/zwave-siren/fingerprints.yml b/drivers/SmartThings/zwave-siren/fingerprints.yml index 1e36e4af81..4b25211366 100644 --- a/drivers/SmartThings/zwave-siren/fingerprints.yml +++ b/drivers/SmartThings/zwave-siren/fingerprints.yml @@ -1,10 +1,16 @@ zwaveManufacturer: - - id: "Zooz" + - id: "Zooz/ZSE19" deviceLabel: Zooz Multisiren manufacturerId: 0x027A productType: 0x000C productId: 0x0003 deviceProfileName: multifunctional-siren + - id: "Zooz/ZSE50" + deviceLabel: Zooz ZSE50 Siren and Chime + manufacturerId: 0x027A + productType: 0x0004 + productId: 0x0369 + deviceProfileName: zooz-zse50 - id: "Everspring" deviceLabel: Everspring Siren manufacturerId: 0x0060 diff --git a/drivers/SmartThings/zwave-siren/profiles/zooz-zse50.yml b/drivers/SmartThings/zwave-siren/profiles/zooz-zse50.yml new file mode 100644 index 0000000000..866b88f436 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/profiles/zooz-zse50.yml @@ -0,0 +1,309 @@ +# Zooz ZSE50 Siren/Chime +# With deviceConfig - allows setting a tone from routines +name: zooz-zse50 +components: + - id: main + capabilities: + - id: alarm + version: 1 + - id: chime + version: 1 + - id: mode + version: 1 + - id: powerSource + version: 1 + - id: audioVolume + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Siren +### PREFERENCES ### +preferences: + #param 1 + - name: "playbackMode" + title: "Playback Mode" + description: "* = Default; Set siren playback mode: once (0), loop for x seconds (1), loop x times (2), loop until cancel (3), no sound (4)." + required: false + preferenceType: enumeration + definition: + options: + 0: "Play once *" + 1: "Play in loop for set duration" + 2: "Play in loop for set number" + 3: "Play in loop until stopped" + 4: "No sound, LED only" + default: 0 + #param 2 + - name: "playbackDuration" + title: "Playback Duration" + description: "Default: 180; Set playback duration for the siren (in seconds) when the siren is in playback mode 1." + required: false + preferenceType: integer + definition: + minimum: 1 + maximum: 900 + default: 180 + #param 3 + - name: "playbackLoop" + title: "Playback Loop Count" + description: "Default: 1; Set the number of playback loops for the selected tone when the siren is in playback mode 2." + required: false + preferenceType: integer + definition: + minimum: 1 + maximum: 99 + default: 1 + #param 4 + - name: "playbackTone" + title: "Playback Tone" + description: "Set the default tone for the siren playback. Choose the number of the file in the library as value. Check the 'modes' list for the id numbers" + required: false + preferenceType: integer + definition: + minimum: 1 + maximum: 50 + default: 1 + #param 5 - playbackVolume ## Handled with volume command + #param 6 + - name: "ledMode" + title: "LED Indicator Mode" + description: "* = Default; Set the LED indicator mode for the siren: off (0), strobe (1), police strobe (2), pulse (3), solid on (4). See documentation for details." + required: false + preferenceType: enumeration + definition: + options: + 0: "LED always off" + 1: "LED strobe single color *" + 2: "LED strobe red and blue" + 3: "LED pulse single color" + 4: "LED solid on single color" + default: 1 + #param 7 + - name: "ledColor" + title: "LED Indicator Color" + description: "Default: 0; Set the LED indicator color: red (0), yellow (42), green (85), indigo (127), blue (170), purple (212), or white (255). More colors available through custom values corresponding to the color wheel. See advanced documentation for details." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 255 + default: 0 + #param 8 + - name: "lowBattery" + title: "Low Battery Report" + description: "Which % level should the device report low battery to the hub." + required: false + preferenceType: enumeration + definition: + options: + 10: "10% [DEFAULT]" + 15: "15%" + 20: "20%" + 25: "25%" + 30: "30%" + 35: "35%" + 40: "40%" + default: 10 + #param 9 + - name: "ledBatteryMode" + title: "LED In Back-Up Battery Mode" + description: "* = Default; Set the LED indicator in back-up battery mode: off (0), regular LED mode (1), pulse white for full battery and red for low battery (2)." + required: false + preferenceType: enumeration + definition: + options: + 0: "LED off" + 1: "Regular LED mode *" + 2: "Pulse white for full, red for low" + default: 1 + #param 10 + - name: "btnToneSelection" + title: "Button Tone Selection" + description: "Disable tone selection from physical buttons on the siren (0). When disabled, you'll only be able to program tones using the advanced parameters in the Z-Wave UI. Expert users only, see documentation for details." + required: false + preferenceType: enumeration + definition: + options: + 0: "Disabled" + 1: "Enabled [DEFAULT]" + default: 1 + #param 11 + - name: "btnVolSelection" + title: "Button Volume Selection" + description: "Disable volume adjustment from physical buttons on the siren (0). When disabled, you'll only be able to adjust volume using the advanced parameters in the Z-Wave UI. Expert users only, see documentation for details." + required: false + preferenceType: enumeration + definition: + options: + 0: "Disabled" + 1: "Enabled [DEFAULT]" + default: 1 + #param 13 + - name: "systemVolume" + title: "System Message Volume" + description: "Default: 50; Set system message volume (0-100, 0 – mute)." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 50 + #param 14 + - name: "ledBrightness" + title: "LED Indicator Brightness" + description: "Default: 5; Choose the LED indicator's brightness level (0 – off, 10 – high brightness)." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 10 + default: 5 + #param 15 + - name: "batteryFrequency" + title: "Battery Reporting Frequency" + description: "Default: 12; Set the reporting interval for battery (1-84 hours)." + required: false + preferenceType: integer + definition: + minimum: 1 + maximum: 84 + default: 12 + #param 16 + - name: "batteryThreshold" + title: "Battery Reporting Threshold" + description: "Default: 0; Set the threshold for battery reporting in % changes. Set to 0 to disable reporting based on threshold." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 20 + default: 0 + +### DEVICE CONFIG ### +deviceConfig: + dashboard: + states: + - component: main + capability: chime + version: 1 + actions: + - component: main + capability: chime + version: 1 + basicPlus: [ ] + detailView: + - component: main + capability: alarm + version: 1 + values: + - key: alarm.value + enabledValues: + - 'off' + - 'both' + - key: "{{enumCommands}}" + enabledValues: + - 'off' + - 'both' + - component: main + capability: chime + version: 1 + - component: main + capability: mode + version: 1 + - component: main + capability: powerSource + version: 1 + values: + - key: powerSource.value + enabledValues: + - 'battery' + - 'mains' + - component: main + capability: audioVolume + version: 1 + - component: main + capability: battery + version: 1 + - component: main + capability: refresh + version: 1 + automation: + conditions: + - component: main + capability: alarm + version: 1 + values: + - key: alarm.value + enabledValues: + - 'off' + - 'both' + - component: main + capability: chime + version: 1 + - component: main + capability: mode + version: 1 + patch: + - op: replace + path: /0/displayType + value: dynamicList + - op: add + path: /0/dynamicList + value: + value: mode.value + command: setMode + supportedValues: + value: supportedArguments.value + - op: remove + path: /0/list + - component: main + capability: powerSource + version: 1 + values: + - key: powerSource.value + enabledValues: + - 'battery' + - 'mains' + step: 1 + - component: main + capability: audioVolume + version: 1 + - component: main + capability: battery + version: 1 + actions: + - component: main + capability: alarm + version: 1 + values: + - key: "{{enumCommands}}" + enabledValues: + - 'off' + - 'both' + - component: main + capability: chime + version: 1 + - component: main + capability: mode + version: 1 + patch: + - op: replace + path: /0/displayType + value: dynamicList + - op: add + path: /0/dynamicList + value: + value: mode.value + command: setMode + supportedValues: + value: supportedArguments.value + - op: remove + path: /0/list + - component: main + capability: audioVolume + version: 1 diff --git a/drivers/SmartThings/zwave-siren/src/init.lua b/drivers/SmartThings/zwave-siren/src/init.lua index b682ea77b7..52ccaba6b9 100644 --- a/drivers/SmartThings/zwave-siren/src/init.lua +++ b/drivers/SmartThings/zwave-siren/src/init.lua @@ -80,7 +80,8 @@ local driver_template = { capabilities.tamperAlert, capabilities.temperatureMeasurement, capabilities.relativeHumidityMeasurement, - capabilities.chime + capabilities.chime, + capabilities.powerSource }, sub_drivers = require("sub_drivers"), lifecycle_handlers = { diff --git a/drivers/SmartThings/zwave-siren/src/preferences.lua b/drivers/SmartThings/zwave-siren/src/preferences.lua index 2c10de6fcb..3cf2fe91b5 100644 --- a/drivers/SmartThings/zwave-siren/src/preferences.lua +++ b/drivers/SmartThings/zwave-siren/src/preferences.lua @@ -46,7 +46,32 @@ local devices = { PARAMETERS = { alarmLength = {parameter_number = 1, size = 2} } - } + }, + ZOOZ_ZSE50_SIREN = { + MATCHING_MATRIX = { + mfrs = 0x027A, + product_types = 0x0004, + product_ids = 0x0369 + }, + PARAMETERS = { + playbackMode = { parameter_number = 1, size = 1 }, + playbackDuration = { parameter_number = 2, size = 2 }, + playbackLoop = { parameter_number = 3, size = 1 }, + playbackTone = { parameter_number = 4, size = 1 }, + playbackVolume = { parameter_number = 5, size = 1 }, + ledMode = { parameter_number = 6, size = 1 }, + ledColor = { parameter_number = 7, size = 1 }, + lowBattery = { parameter_number = 8, size = 1 }, + ledBatteryMode = { parameter_number = 9, size = 1 }, + btnToneSelection = { parameter_number = 10, size = 1 }, + btnVolSelection = { parameter_number = 11, size = 1 }, + basicSetGrp2 = { parameter_number = 12, size = 1 }, --Not Used + systemVolume = { parameter_number = 13, size = 1 }, + ledBrightness = { parameter_number = 14, size = 1 }, + batteryFrequency = { parameter_number = 15, size = 1 }, + batteryThreshold = { parameter_number = 16, size = 1 } + } + }, } local preferences = {} @@ -70,4 +95,5 @@ preferences.to_numeric_value = function(new_value) end return numeric end + return preferences diff --git a/drivers/SmartThings/zwave-siren/src/sub_drivers.lua b/drivers/SmartThings/zwave-siren/src/sub_drivers.lua index a20d559e44..12ce423ba5 100644 --- a/drivers/SmartThings/zwave-siren/src/sub_drivers.lua +++ b/drivers/SmartThings/zwave-siren/src/sub_drivers.lua @@ -4,6 +4,7 @@ local lazy_load_if_possible = require "lazy_load_subdriver" local sub_drivers = { lazy_load_if_possible("multifunctional-siren"), + lazy_load_if_possible("zooz-zse50"), lazy_load_if_possible("zwave-sound-sensor"), lazy_load_if_possible("ecolink-wireless-siren"), lazy_load_if_possible("philio-sound-siren"), diff --git a/drivers/SmartThings/zwave-siren/src/test/test_zooz_zse50.lua b/drivers/SmartThings/zwave-siren/src/test/test_zooz_zse50.lua new file mode 100644 index 0000000000..1454458687 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/test/test_zooz_zse50.lua @@ -0,0 +1,717 @@ +-- Copyright 2026 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 Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) +local SoundSwitch = (require "st.zwave.CommandClass.SoundSwitch")({ version = 1 }) +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 8 }) +local Version = (require "st.zwave.CommandClass.Version")({ version = 1 }) +local t_utils = require "integration_test.utils" + +local siren_endpoints = { + { + command_classes = { + { value = zw.SOUND_SWITCH }, + { value = zw.NOTIFICATION }, + { value = zw.VERSION }, + { value = zw.BASIC } + } + } +} + +--- { manufacturerId = 0x027A, productType = 0x0004, productId = 0x0369 } -- Zooz ZSE50 Siren & Chime +local mock_siren = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("zooz-zse50.yml"), + zwave_endpoints = siren_endpoints, + zwave_manufacturer_id = 0x027A, + zwave_product_type = 0x0004, + zwave_product_id = 0x0369, +}) + +local tones_list = { + [1] = { name = "test_tone1", duration = 2 }, + [2] = { name = "test_tone2", duration = 4 } +} + +local function test_init() + -- Initialize some fields to help with testing + mock_siren:set_field("TONE_DEFAULT", 1, { persist = true }) + mock_siren:set_field("TOTAL_TONES", 2, { persist = true }) + mock_siren:set_field("TONES_LIST", tones_list, { persist = true }) + + test.mock_device.add_test_device(mock_siren) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "init should rebuild tones when tone cache is missing", + function() + mock_siren:set_field("TONES_LIST", nil) + mock_siren:set_field("TONE_DEFAULT", nil) + + test.socket.device_lifecycle:__queue_receive({ mock_siren.id, "init" }) + test.socket.capability:__expect_send( + mock_siren:generate_test_message("main", capabilities.mode.mode("Rebuild List")) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonesNumberGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "added should set startup volume and refresh", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.zwave:__set_channel_ordering("relaxed") + + test.socket.device_lifecycle:__queue_receive({ mock_siren.id, "added" }) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ConfigurationSet({ volume = 10 }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Basic:Get({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Version:Get({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Notification:Get({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.STATE_IDLE, + v1_alarm_type = 0 + }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ConfigurationGet({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "infoChanged should update config and send delayed Basic Set", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.socket.device_lifecycle:__queue_receive( + mock_siren:generate_info_changed({ preferences = { ledColor = 255 } }) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Configuration:Set({ parameter_number = 7, size = 1, configuration_value = -1 }) + ) + ) + + test.mock_time.advance_time(1) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Basic:Set({ value = 0x00 }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "infoChanged should update playbackDuration and send delayed Basic Set", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.socket.device_lifecycle:__queue_receive( + mock_siren:generate_info_changed({ preferences = { playbackDuration = 90 } }) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Configuration:Set({ parameter_number = 2, size = 2, configuration_value = 90 }) + ) + ) + + test.mock_time.advance_time(1) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Basic:Set({ value = 0x00 }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Version report should update firmware version", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(Version:Report({ + application_version = 2, + application_sub_version = 5 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.firmwareUpdate.currentVersion({ value = "2.05" })) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Notification report AC_MAINS_DISCONNECTED should set power source to battery", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.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_siren:generate_test_message("main", capabilities.powerSource.powerSource.battery()) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Notification report AC_MAINS_RE_CONNECTED should set power source to mains", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.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_siren:generate_test_message("main", capabilities.powerSource.powerSource.mains()) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "SoundSwitch ConfigurationReport should update volume", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(SoundSwitch:ConfigurationReport({ + volume = 75, + default_tone_identifer = 5 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.audioVolume.volume(75)) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "SoundSwitch TonesNumberReport should request info on each tone", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(SoundSwitch:TonesNumberReport({ + supported_tones = 2 + })) } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ToneInfoGet({ tone_identifier = 1 }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ToneInfoGet({ tone_identifier = 2 }) + ) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "SoundSwitch ToneInfoReport should update supported modes when all tones received", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(SoundSwitch:ToneInfoReport({ + tone_identifier = 1, + name = "test_tone1", + tone_duration = 2 + })) } + }, + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(SoundSwitch:ToneInfoReport({ + tone_identifier = 2, + name = "test_tone2", + tone_duration = 4 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.mode.supportedModes({ "Rebuild List", "Off", "1: test_tone1 (2s)", "2: test_tone2 (4s)" })) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.mode.supportedArguments({ "Off", "1: test_tone1 (2s)", "2: test_tone2 (4s)" })) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "SoundSwitch TonePlayReport for tone 1 should set alarm on, chime on, and mode to tone name", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(SoundSwitch:TonePlayReport({ + tone_identifier = 1 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.alarm.alarm.both()) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.chime.chime.chime()) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.mode.mode("1: test_tone1 (2s)")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "SoundSwitch TonePlayReport for tone 0 should set alarm off, chime off, and mode Off", + { + { + channel = "zwave", + direction = "receive", + message = { mock_siren.id, zw_test_utils.zwave_test_build_receive_command(SoundSwitch:TonePlayReport({ + tone_identifier = 0 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.alarm.alarm.off()) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.chime.chime.off()) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.mode.mode("Off")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Basic report 0x00 should be handled as alarm off, chime off, and mode Off", + { + { + channel = "zwave", + direction = "receive", + message = { + mock_siren.id, + zw_test_utils.zwave_test_build_receive_command(Basic:Report({ value = 0 })) } + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.alarm.alarm.off()) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.chime.chime.off()) + }, + { + channel = "capability", + direction = "send", + message = mock_siren:generate_test_message("main", capabilities.mode.mode("Off")) + } + }, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "volumeUp should increase volume by 2", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "audioVolume", component = "main", command = "volumeUp", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ConfigurationSet({ volume = 52 }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "volumeUp should decrease volume by 2", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "audioVolume", component = "main", command = "volumeDown", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ConfigurationSet({ volume = 48 }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "setVolume should set volume to specified value", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "audioVolume", component = "main", command = "setVolume", args = { 75 } } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ConfigurationSet({ volume = 75 }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "alarm.both() should send TonePlaySet with default tone and TonePlayGet", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "alarm", component = "main", command = "both", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlaySet({ tone_identifier = 0xFF }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "alarm.off() should send TonePlaySet with tone 0x00 and TonePlayGet", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "alarm", component = "main", command = "off", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlaySet({ tone_identifier = 0x00 }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "chime.chime() should send TonePlaySet with default tone and TonePlayGet", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "chime", component = "main", command = "chime", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlaySet({ tone_identifier = 0xFF }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "chime.off() should send TonePlaySet with tone 0x00 and TonePlayGet", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "chime", component = "main", command = "off", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlaySet({ tone_identifier = 0x00 }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "setMode should play the specified tone", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "mode", component = "main", command = "setMode", args = { "1: test_tone1 (2s)" } } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlaySet({ tone_identifier = 1 }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "setMode to Off should turn off the tone", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "mode", component = "main", command = "setMode", args = { "Off" } } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlaySet({ tone_identifier = 0x00 }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "setMode to Rebuild List should emit mode and send TonesNumberGet", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "mode", component = "main", command = "setMode", args = { "Rebuild List" } } + }) + test.socket.capability:__expect_send( + mock_siren:generate_test_message("main", capabilities.mode.mode("Rebuild List")) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonesNumberGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "refresh should send a series of Z-Wave Gets", + function() + test.socket.capability:__queue_receive({ + mock_siren.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Basic:Get({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Version:Get({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + Notification:Get({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.STATE_IDLE, + v1_alarm_type = 0 + }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:ConfigurationGet({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_siren, + SoundSwitch:TonePlayGet({}) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-siren/src/zooz-zse50/can_handle.lua b/drivers/SmartThings/zwave-siren/src/zooz-zse50/can_handle.lua new file mode 100644 index 0000000000..90ce30f76b --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/zooz-zse50/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_multifunctional_siren(opts, driver, device, ...) + local FINGERPRINTS = require("zooz-zse50.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("zooz-zse50") + end + end + return false +end + +return can_handle_multifunctional_siren diff --git a/drivers/SmartThings/zwave-siren/src/zooz-zse50/fingerprints.lua b/drivers/SmartThings/zwave-siren/src/zooz-zse50/fingerprints.lua new file mode 100644 index 0000000000..a8c37a04e6 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/zooz-zse50/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZSE50_FINGERPRINTS = { + { manufacturerId = 0x027A, productType = 0x0004, productId = 0x0369 } -- Zooz ZSE50 Siren & Chime +} + +return ZSE50_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-siren/src/zooz-zse50/init.lua b/drivers/SmartThings/zwave-siren/src/zooz-zse50/init.lua new file mode 100644 index 0000000000..a1d58b262a --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/zooz-zse50/init.lua @@ -0,0 +1,367 @@ +-- Copyright 2026 SmartThings +-- Licensed under the Apache License, Version 2.0 + +local preferencesMap = require "preferences" + +local log = require "log" +local st_utils = require "st.utils" +local capabilities = require "st.capabilities" +local defaults = require "st.zwave.defaults" + +local cc = require "st.zwave.CommandClass" +local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 8 }) +local SoundSwitch = (require "st.zwave.CommandClass.SoundSwitch")({ version = 1 }) +local Version = (require "st.zwave.CommandClass.Version")({ version = 1 }) + +--- @param self st.zwave.Driver +--- @param device st.zwave.Device +local function update_firmwareUpdate_capability(self, device, component, major, minor) + if device:supports_capability_by_id(capabilities.firmwareUpdate.ID, component.id) then + local fmtFirmwareVersion = string.format("%d.%02d", major, minor) + device:emit_component_event(component, capabilities.firmwareUpdate.currentVersion({ value = fmtFirmwareVersion })) + end +end + +--- Update the built in capability firmwareUpdate's currentVersion attribute with the +--- Zwave version information received during pairing of the device. +--- @param self st.zwave.Driver +--- @param device st.zwave.Device +local function updateFirmwareVersion(self, device) + local fw_major = (((device.st_store or {}).zwave_version or {}).firmware or {}).major + local fw_minor = (((device.st_store or {}).zwave_version or {}).firmware or {}).minor + if fw_major and fw_minor then + update_firmwareUpdate_capability(self, device, device.profile.components.main, fw_major, fw_minor) + else + device.log.warn("Firmware major or minor version not available.") + end +end + +local function getModeName(toneId, toneInfo) + return string.format("%s: %s (%ss)", toneId, toneInfo.name, toneInfo.duration) +end + +local function playTone(device, tone_id) + local tones_list = device:get_field("TONES_LIST") + local default_tone = device:get_field("TONE_DEFAULT") or 1 + local playbackMode = tonumber(device.preferences.playbackMode) + local duration = 0 + + if playbackMode == 1 then + duration = device.preferences.playbackDuration + elseif playbackMode == 2 then + duration = duration * device.preferences.playbackLoop + elseif tones_list ~= nil and tone_id > 0 then + if tone_id == 0xFF then + duration = tones_list[tonumber(default_tone)].duration + else + duration = tones_list[tonumber(tone_id)].duration + end + end + + log.info(string.format("Playing Tone: %s, playbackMode %s, duration %ss", tone_id, playbackMode, duration)) + + device:send(SoundSwitch:TonePlaySet({ tone_identifier = tone_id })) + device:send(SoundSwitch:TonePlayGet({})) + + local soundSwitch_refresh = function() + local chime = device:get_latest_state("main", capabilities.chime.ID, capabilities.chime.chime.NAME) + local mode = device:get_latest_state("main", capabilities.mode.ID, capabilities.mode.mode.NAME) + log.info(string.format("Running SoundSwitch Refresh: %s | %s", chime, mode)) + if chime ~= "off" or mode ~= "Off" then + device:send(SoundSwitch:TonePlayGet({})) + end + end + + if tone_id > 0 and playbackMode <= 2 then + local minDuration = math.max(duration, 4) + device.thread:call_with_delay(minDuration + 0.5, soundSwitch_refresh) + device.thread:call_with_delay(minDuration + 4, soundSwitch_refresh) + end + +end + +local function rebuildTones(device) + device:emit_event(capabilities.mode.mode("Rebuild List")) + device:send(SoundSwitch:TonesNumberGet({})) +end + +local function refresh_handler(self, device) + device:default_refresh() + device:send(Version:Get({})) + device:send(Notification:Get({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.STATE_IDLE, + v1_alarm_type = 0 + })) + device:send(SoundSwitch:ConfigurationGet({})) + device:send(SoundSwitch:TonePlayGet({})) +end + +local function setMode_handler(self, device, command) + local mode_value = command.args.mode + local mode_split = string.find(mode_value, ":") + + if mode_split ~= nil then + mode_value = string.sub(mode_value, 1, mode_split - 1) + end + log.info(string.format("Command: setMode (%s)", mode_value)) + + if mode_value == 'Rebuild List' then + rebuildTones(device) + elseif mode_value == 'Off' then + playTone(device, 0x00) + else + playTone(device, tonumber(mode_value)) + end +end + +local function setVolume_handler(self, device, cmd) + local new_volume = st_utils.clamp_value(cmd.args.volume, 0, 100) + device:send(SoundSwitch:ConfigurationSet({ volume = new_volume })) +end + +local function volumeUp_handler(self, device, cmd) + local volume = device:get_latest_state("main", capabilities.audioVolume.ID, capabilities.audioVolume.volume.NAME) or 50 + volume = st_utils.clamp_value(volume + 2, 0, 100) + device:send(SoundSwitch:ConfigurationSet({ volume = volume })) +end + +local function volumeDown_handler(self, device, cmd) + local volume = device:get_latest_state("main", capabilities.audioVolume.ID, capabilities.audioVolume.volume.NAME) or 50 + volume = st_utils.clamp_value(volume - 2, 0, 100) + device:send(SoundSwitch:ConfigurationSet({ volume = volume })) +end + +local function tone_on(self, device) + playTone(device, 0xFF) +end + +local function tone_off(self, device) + playTone(device, 0x00) +end + +local function tones_number_report_handler(self, device, cmd) + local total_tones = cmd.args.supported_tones + + --Max 50 tones per Zooz settings + if total_tones > 50 then + total_tones = 50 + end + + local tones_list = { } + device:set_field("TOTAL_TONES", total_tones) + device:set_field("TONES_LIST_TMP", tones_list) + + --Get info on all tones + for tone = 1, total_tones do + device:send(SoundSwitch:ToneInfoGet({ tone_identifier = tone })) + end +end + +local function tone_info_report_handler(self, device, cmd) + local tone_id = tonumber(cmd.args.tone_identifier) + local tone_name = cmd.args.name + local duration = cmd.args.tone_duration + local total_tones = device:get_field("TOTAL_TONES") + local tones_list = device:get_field("TONES_LIST_TMP") or {} + + tones_list[tone_id] = { name = tone_name, duration = duration } + device:set_field("TONES_LIST_TMP", tones_list) + + if tone_id >= total_tones or #tones_list >= total_tones then + log.info(string.format("Received info on all tones: tone_id %s, #tones_list %s, total_tones %s", tone_id, #tones_list, total_tones)) + + local tones_arguments = { "Off" } + for il, vl in ipairs(tones_list) do + table.insert(tones_arguments, getModeName(il, vl)) + end + + device:set_field("TONES_LIST", tones_list, { persist = true }) + device:emit_event(capabilities.mode.supportedModes({ "Rebuild List", table.unpack(tones_arguments) })) + device:emit_event(capabilities.mode.supportedArguments(tones_arguments)) + device:send(SoundSwitch:TonePlayGet({})) + end +end + +--- Handle when tone is played (TONE_PLAY_REPORT or BASIC_REPORT) +local function tone_playing(self, device, tone_id) + local tones_list = device:get_field("TONES_LIST") + + if tones_list == nil or tones_list == {} then + rebuildTones(device) + end + + if tone_id == 0 then + device:emit_event(capabilities.alarm.alarm.off()) + device:emit_event(capabilities.chime.chime.off()) + device:emit_event(capabilities.mode.mode("Off")) + else + local toneInfo = (tones_list or {})[tone_id] or { name = "Unknown", duration = "0" } + local modeName = getModeName(tone_id, toneInfo) + device:emit_event(capabilities.alarm.alarm.both()) + device:emit_event(capabilities.chime.chime.chime()) + device:emit_event(capabilities.mode.mode(modeName)) + end +end + +local function tone_play_report_handler(self, device, cmd) + local tone_id = tonumber(cmd.args.tone_identifier) + tone_playing(self, device, tone_id) +end + +local function basic_report_handler(self, device, cmd) + local tone_id = tonumber(cmd.args.value) + tone_playing(self, device, tone_id) +end + +--- Handle SoundSwitch Config Reports (volume) +local function soundSwitch_configuration_report(self, device, cmd) + local volume = st_utils.clamp_value(cmd.args.volume, 0, 100) + local default_tone = cmd.args.default_tone_identifer + device:emit_event(capabilities.audioVolume.volume(volume)) + device:set_field("TONE_DEFAULT", default_tone, { persist = true }) +end + +--- Handle power source changes +local function notification_report_handler(self, device, cmd) + if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then + local event = cmd.args.event + local powerManagement = Notification.event.power_management + + if event == powerManagement.AC_MAINS_DISCONNECTED then + device:emit_event(capabilities.powerSource.powerSource.battery()) + elseif event == powerManagement.AC_MAINS_RE_CONNECTED or event == powerManagement.STATE_IDLE then + device:emit_event(capabilities.powerSource.powerSource.mains()) + end + end +end + +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param cmd st.zwave.CommandClass.Version.Report +local function version_report_handler(driver, device, cmd) + local major = cmd.args.application_version + local minor = cmd.args.application_sub_version + + -- Update the built in firmware capability, if available + update_firmwareUpdate_capability(driver, device, device.profile.components.main, major, minor) +end + +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +local function device_init(driver, device) + if (device:get_field("TONES_LIST") == nil or device:get_field("TONE_DEFAULT") == nil) then + rebuildTones(device) + end +end + +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +local function device_added(driver, device) + device:send(SoundSwitch:ConfigurationSet({ volume = 10 })) + updateFirmwareVersion(driver, device) + device:refresh() +end + +--- Handle preference changes (same as default but added hack for unsigned parameters) +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param event table +--- @param args +local function info_changed(driver, device, event, args) + local preferences = preferencesMap.get_device_parameters(device) + + if preferences then + local did_configuration_change = false + for id, value in pairs(device.preferences) do + if args.old_st_store.preferences[id] ~= value and preferences[id] then + local new_parameter_value = preferencesMap.to_numeric_value(device.preferences[id]) + --Hack to convert to signed integer + local size_factor = math.floor(256 ^ preferences[id].size) + if new_parameter_value >= (size_factor / 2) then + new_parameter_value = new_parameter_value - size_factor + end + --END Hack + device:send(Configuration:Set({ parameter_number = preferences[id].parameter_number, size = preferences[id].size, configuration_value = new_parameter_value })) + did_configuration_change = true + end + end + + if did_configuration_change then + local delayed_command = function() + device:send(Basic:Set({ value = 0x00 })) + end + device.thread:call_with_delay(1, delayed_command) + end + + end +end + +local zooz_zse50 = { + NAME = "Zooz ZSE50", + can_handle = require("zooz-zse50.can_handle"), + + supported_capabilities = { + capabilities.battery, + capabilities.chime, + capabilities.mode, + capabilities.audioVolume, + capabilities.powerSource, + capabilities.firmwareUpdate, + capabilities.configuration, + capabilities.refresh + }, + + zwave_handlers = { + [cc.BASIC] = { + [Basic.REPORT] = basic_report_handler + }, + [cc.SOUND_SWITCH] = { + [SoundSwitch.TONES_NUMBER_REPORT] = tones_number_report_handler, + [SoundSwitch.TONE_INFO_REPORT] = tone_info_report_handler, + [SoundSwitch.TONE_PLAY_REPORT] = tone_play_report_handler, + [SoundSwitch.CONFIGURATION_REPORT] = soundSwitch_configuration_report + }, + [cc.NOTIFICATION] = { + [Notification.REPORT] = notification_report_handler + }, + [cc.VERSION] = { + [Version.REPORT] = version_report_handler + } + }, + + capability_handlers = { + [capabilities.mode.ID] = { + [capabilities.mode.commands.setMode.NAME] = setMode_handler + }, + [capabilities.audioVolume.ID] = { + [capabilities.audioVolume.commands.setVolume.NAME] = setVolume_handler, + [capabilities.audioVolume.commands.volumeUp.NAME] = volumeUp_handler, + [capabilities.audioVolume.commands.volumeDown.NAME] = volumeDown_handler + }, + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = refresh_handler + }, + [capabilities.alarm.ID] = { + [capabilities.alarm.commands.both.NAME] = tone_on, + [capabilities.alarm.commands.off.NAME] = tone_off + }, + [capabilities.chime.ID] = { + [capabilities.chime.commands.chime.NAME] = tone_on, + [capabilities.chime.commands.off.NAME] = tone_off + }, + + }, + + lifecycle_handlers = { + init = device_init, + added = device_added, + infoChanged = info_changed + } +} + +defaults.register_for_default_handlers(zooz_zse50, zooz_zse50.supported_capabilities) + +return zooz_zse50 From 7e913bb9d663d9726e0707ed83ddcb5e7ccfa919 Mon Sep 17 00:00:00 2001 From: Konrad K <33450498+KKlimczukS@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:40:48 +0200 Subject: [PATCH 236/538] WWSTCERT-8045 - Aeotec Home Energy Meter Gen8 (Revert + fixes) (#2791) WWSTCERT-8045 - Aeotec Home Energy Meter Gen8 --- .../zwave-electric-meter/fingerprints.yml | 16 + ...tec-home-energy-meter-gen8-1-phase-con.yml | 107 +++ ...tec-home-energy-meter-gen8-1-phase-pro.yml | 22 + ...tec-home-energy-meter-gen8-2-phase-con.yml | 148 ++++ ...tec-home-energy-meter-gen8-2-phase-pro.yml | 31 + ...tec-home-energy-meter-gen8-3-phase-con.yml | 189 +++++ ...tec-home-energy-meter-gen8-3-phase-pro.yml | 40 + ...aeotec-home-energy-meter-gen8-sald-con.yml | 13 + ...aeotec-home-energy-meter-gen8-sald-pro.yml | 13 + .../1-phase/can_handle.lua | 18 + .../1-phase/init.lua | 123 +++ .../2-phase/can_handle.lua | 17 + .../2-phase/init.lua | 123 +++ .../3-phase/can_handle.lua | 18 + .../3-phase/init.lua | 123 +++ .../can_handle.lua | 22 + .../aeotec-home-energy-meter-gen8/init.lua | 49 ++ .../power_consumption.lua | 32 + .../sub_drivers.lua | 10 + .../zwave-electric-meter/src/init.lua | 21 + .../zwave-electric-meter/src/preferences.lua | 95 +++ .../zwave-electric-meter/src/sub_drivers.lua | 1 + ..._aeotec_home_energy_meter_gen8_1_phase.lua | 560 +++++++++++++ ..._aeotec_home_energy_meter_gen8_2_phase.lua | 654 +++++++++++++++ ..._aeotec_home_energy_meter_gen8_3_phase.lua | 749 ++++++++++++++++++ 25 files changed, 3194 insertions(+) create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-con.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-pro.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-con.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-pro.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-con.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-pro.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-con.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-pro.yml create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/can_handle.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/init.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/can_handle.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/init.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/can_handle.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/init.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/can_handle.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/init.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/power_consumption.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/preferences.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_1_phase.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_2_phase.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_3_phase.lua diff --git a/drivers/SmartThings/zwave-electric-meter/fingerprints.yml b/drivers/SmartThings/zwave-electric-meter/fingerprints.yml index 7c32d646e3..c580510daa 100644 --- a/drivers/SmartThings/zwave-electric-meter/fingerprints.yml +++ b/drivers/SmartThings/zwave-electric-meter/fingerprints.yml @@ -35,6 +35,22 @@ zwaveManufacturer: productType: 0x0002 productId: 0x0001 deviceProfileName: base-electric-meter + - id: "0x0371/0x0003/0x0033" #HEM Gen8 1 Phase EU, AU + deviceLabel: Aeotec Home Energy Meter Gen8 Consumption + manufacturerId: 0x0371 + productId: 0x0033 + deviceProfileName: aeotec-home-energy-meter-gen8-1-phase-con + - id: "0x0371/0x0003/0x0034" # HEM Gen8 3 Phase EU, AU + deviceLabel: Aeotec Home Energy Meter Gen8 Consumption + manufacturerId: 0x0371 + productId: 0x0034 + deviceProfileName: aeotec-home-energy-meter-gen8-3-phase-con + - id: "0x0371/0x0103/0x002E" # HEM Gen8 2 Phase US + deviceLabel: Aeotec Home Energy Meter Gen8 Consumption + manufacturerId: 0x0371 + productType: 0x0103 + productId: 0x002E + deviceProfileName: aeotec-home-energy-meter-gen8-2-phase-con zwaveGeneric: - id: "GenericEnergyMeter" deviceLabel: Energy Monitor diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-con.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-con.yml new file mode 100644 index 0000000000..b4de02cb6a --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-con.yml @@ -0,0 +1,107 @@ +name: aeotec-home-energy-meter-gen8-1-phase-con +components: +- id: main + label: "Sum Consumption" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp1 + label: "Clamp 1" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +preferences: + - name: thresholdCheck + title: "3. Threshold Check Enable/Disable" + description: "Enable selective reporting only when power change reaches a certain threshold or percentage set in 4 -19 below. This is used to reduce network traffic." + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enable" + default: 1 + - name: imWThresholdTotal + title: "4. Import W threshold (total)" + description: "Threshold change in import wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWThresholdPhaseA + title: "5. Import W threshold (Phase A)" + description: "Threshold change in import wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdTotal + title: "8. Export W threshold (total)" + description: "Threshold change in export wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdPhaseA + title: "9. Export W threshold (Phase A)" + description: "Threshold change in export wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWPctThresholdTotal + title: "12. Import W threshold (total)" + description: "Percentage change in import wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: imWPctThresholdPhaseA + title: "13. Import W threshold (Phase A)" + description: "Percentage change in import wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdTotal + title: "16. Export W threshold (total)" + description: "Percentage change in export wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdPhaseA + title: "17. Export W threshold (Phase A)" + description: "Percentage change in export wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: autoRootDeviceReport + title: "32. Auto report of root device" + description: "Enable automatic report of root device." + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enable" + default: 0 diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-pro.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-pro.yml new file mode 100644 index 0000000000..9510e27037 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-1-phase-pro.yml @@ -0,0 +1,22 @@ +name: aeotec-home-energy-meter-gen8-1-phase-pro +components: +- id: main + label: "Sum Production" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp2 + label: "Clamp 1" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-con.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-con.yml new file mode 100644 index 0000000000..64b2a510f9 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-con.yml @@ -0,0 +1,148 @@ +name: aeotec-home-energy-meter-gen8-2-phase-con +components: +- id: main + label: "Sum Consumption" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp1 + label: "Clamp 1" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp3 + label: "Clamp 2" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +preferences: + - name: thresholdCheck + title: "3. Threshold Check Enable/Disable" + description: "Enable selective reporting only when power change reaches a certain threshold or percentage set in 4 -19 below. This is used to reduce network traffic." + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enable" + default: 1 + - name: imWThresholdTotal + title: "4. Import W threshold (total)" + description: "Threshold change in import wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWThresholdPhaseA + title: "5. Import W threshold (Phase A)" + description: "Threshold change in import wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWhresholdPhaseB + title: "6. Import W threshold (Phase B)" + description: "Threshold change in import wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWhresholdTotal + title: "8. Export W threshold (total)" + description: "Threshold change in export wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdPhaseA + title: "9. Export W threshold (Phase A)" + description: "Threshold change in export wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdPhaseB + title: "10. Export W threshold (Phase B)" + description: "Threshold change in export wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWPctThresholdTotal + title: "12. Import W threshold (total)" + description: "Percentage change in import wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: imWPctThresholdPhaseA + title: "13. Import W threshold (Phase A)" + description: "Percentage change in import wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: imWPctThresholdPhaseB + title: "14. Import W threshold (Phase B)." + description: "Percentage change in import wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdTotal + title: "16. Export W threshold (total)" + description: "Percentage change in export wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdPhaseA + title: "17. Export W threshold (Phase A)" + description: "Percentage change in export wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdPhaseB + title: "18. Export W threshold (Phase B)" + description: "Percentage change in export wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: autoRootDeviceReport + title: "32. Auto report of root device" + description: "Enable automatic report of root device." + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enable" + default: 0 diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-pro.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-pro.yml new file mode 100644 index 0000000000..be4adf21c5 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-2-phase-pro.yml @@ -0,0 +1,31 @@ +name: aeotec-home-energy-meter-gen8-2-phase-pro +components: +- id: main + label: "Sum Production" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp2 + label: "Clamp 1" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp4 + label: "Clamp 2" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-con.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-con.yml new file mode 100644 index 0000000000..e1d44731bf --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-con.yml @@ -0,0 +1,189 @@ +name: aeotec-home-energy-meter-gen8-3-phase-con +components: +- id: main + label: "Sum Consumption" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp1 + label: "Clamp 1" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp3 + label: "Clamp 2" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp5 + label: "Clamp 3" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +preferences: + - name: thresholdCheck + title: "3. Threshold Check Enable/Disable" + description: "Enable selective reporting only when power change reaches a certain threshold or percentage set in 4 -19 below. This is used to reduce network traffic." + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enable" + default: 1 + - name: imWThresholdTotal + title: "4. Import W threshold (total)" + description: "Threshold change in import wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWThresholdPhaseA + title: "5. Import W threshold (Phase A)" + description: "Threshold change in import wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWhresholdPhaseB + title: "6. Import W threshold (Phase B)" + description: "Threshold change in import wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imtWThresholdPhaseC + title: "7. Import W threshold (Phase C)" + description: "Threshold change in import wattage to induce an automatic report (Phase C)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWhresholdTotal + title: "8. Export W threshold (total)" + description: "Threshold change in export wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdPhaseA + title: "9. Export W threshold (Phase A)" + description: "Threshold change in export wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdPhaseB + title: "10. Export W threshold (Phase B)" + description: "Threshold change in export wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: exWThresholdPhaseC + title: "11. Export W threshold (Phase C)" + description: "Threshold change in export wattage to induce an automatic report (Phase C)." + preferenceType: integer + definition: + minimum: 0 + maximum: 60000 + default: 50 + - name: imWPctThresholdTotal + title: "12. Import W threshold (total)" + description: "Percentage change in import wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: imWPctThresholdPhaseA + title: "13. Import W threshold (Phase A)" + description: "Percentage change in import wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: imWPctThresholdPhaseB + title: "14. Import W threshold (Phase B)" + description: "Percentage change in import wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: imWPctThresholdPhaseC + title: "15. Import W threshold (Phase C)" + description: "Percentage change in import wattage to induce an automatic report (Phase C)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdTotal + title: "16. Export W threshold (total)" + description: "Percentage change in export wattage to induce an automatic report (Whole HEM)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdPhaseA + title: "17. Export W threshold (Phase A)" + description: "Percentage change in export wattage to induce an automatic report (Phase A)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdPhaseB + title: "18. Export W threshold (Phase B)" + description: "Percentage change in export wattage to induce an automatic report (Phase B)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: exWPctThresholdPhaseC + title: "19. Export W threshold (Phase C)" + description: "Percentage change in export wattage to induce an automatic report (Phase C)." + preferenceType: integer + definition: + minimum: 0 + maximum: 100 + default: 20 + - name: autoRootDeviceReport + title: "32. Auto report of root device" + description: "Enable automatic report of root device." + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enable" + default: 0 diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-pro.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-pro.yml new file mode 100644 index 0000000000..3efa762dda --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-3-phase-pro.yml @@ -0,0 +1,40 @@ +name: aeotec-home-energy-meter-gen8-3-phase-pro +components: +- id: main + label: "Sum Production" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp2 + label: "Clamp 1" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp4 + label: "Clamp 2" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor +- id: clamp6 + label: "Clamp 3" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + categories: + - name: PowerMeasurementSensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-con.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-con.yml new file mode 100644 index 0000000000..f0bf405fb7 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-con.yml @@ -0,0 +1,13 @@ +name: aeotec-home-energy-meter-gen8-sald-con +components: +- id: main + label: "Settled Consumption" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-pro.yml b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-pro.yml new file mode 100644 index 0000000000..da05330d46 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/profiles/aeotec-home-energy-meter-gen8-sald-pro.yml @@ -0,0 +1,13 @@ +name: aeotec-home-energy-meter-gen8-sald-pro +components: +- id: main + label: "Settled Production" + capabilities: + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: PowerMeasurementSensor \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/can_handle.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/can_handle.lua new file mode 100644 index 0000000000..6dd02b40fe --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS = { + { mfr = 0x0371, prod = 0x0003, model = 0x0033 }, -- HEM Gen8 1 Phase EU + { mfr = 0x0371, prod = 0x0102, model = 0x002E } -- HEM Gen8 1 Phase AU +} + +local function can_handle_aeotec_meter_gen8_1_phase(opts, driver, device, ...) + for _, fingerprint in ipairs(AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("aeotec-home-energy-meter-gen8.1-phase") + end + end + return false +end + +return can_handle_aeotec_meter_gen8_1_phase diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/init.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/init.lua new file mode 100644 index 0000000000..db69a9d52d --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/1-phase/init.lua @@ -0,0 +1,123 @@ +-- 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.Meter +local Meter = (require "st.zwave.CommandClass.Meter")({ version=4 }) +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +local utils = require "st.utils" +local power_consumption = require("aeotec-home-energy-meter-gen8.power_consumption") + +local POWER_UNIT_WATT = "W" +local ENERGY_UNIT_KWH = "kWh" + +local HEM8_DEVICES = { + { profile = 'aeotec-home-energy-meter-gen8-1-phase-con', name = 'Aeotec Home Energy Meter 8 Consumption', endpoints = { 1, 3 } }, + { profile = 'aeotec-home-energy-meter-gen8-1-phase-pro', name = 'Aeotec Home Energy Meter 8 Production', child_key = 'pro', endpoints = { 2, 4 } }, + { profile = 'aeotec-home-energy-meter-gen8-sald-con', name = 'Aeotec Home Energy Meter 8 Settled Consumption', child_key = 'sald-con', endpoints = { 5 } }, + { profile = 'aeotec-home-energy-meter-gen8-sald-pro', name = 'Aeotec Home Energy Meter 8 Settled Production', child_key = 'sald-pro', endpoints = { 6 } } +} + +local function find_hem8_child_device_key_by_endpoint(endpoint) + for _, child in ipairs(HEM8_DEVICES) do + if child.endpoints then + for _, e in ipairs(child.endpoints) do + if e == endpoint then + return child.child_key + end + end + end + end +end + +local function meter_report_handler(driver, device, cmd, zb_rx) + local endpoint = cmd.src_channel + local device_to_emit_with = device + local child_device_key = find_hem8_child_device_key_by_endpoint(endpoint); + local child_device = device:get_child_by_parent_assigned_key(child_device_key) + + if(child_device) then + device_to_emit_with = child_device + end + + if cmd.args.scale == Meter.scale.electric_meter.KILOWATT_HOURS then + local event_arguments = { + value = cmd.args.meter_value, + unit = ENERGY_UNIT_KWH + } + -- energyMeter + device_to_emit_with:emit_event_for_endpoint( + cmd.src_channel, + capabilities.energyMeter.energy(event_arguments) + ) + + if endpoint == 5 then + -- powerConsumptionReport + power_consumption.emit_power_consumption_report_event(device, { value = event_arguments.value }) + end + elseif cmd.args.scale == Meter.scale.electric_meter.WATTS then + local event_arguments = { + value = cmd.args.meter_value, + unit = POWER_UNIT_WATT + } + -- powerMeter + device_to_emit_with:emit_event_for_endpoint( + cmd.src_channel, + capabilities.powerMeter.power(event_arguments) + ) + end +end + +local function do_refresh(self, device) + for _, d in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(d.endpoints) do + device:send(Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, {dst_channels = {endpoint}})) + device:send(Meter:Get({scale = Meter.scale.electric_meter.WATTS}, {dst_channels = {endpoint}})) + end + end +end + +local function device_added(driver, device) + if device.network_type == st_device.NETWORK_TYPE_ZWAVE and not (device.child_ids and utils.table_size(device.child_ids) ~= 0) then + for i, hem8_child in ipairs(HEM8_DEVICES) do + if(hem8_child["child_key"]) then + local name = hem8_child.name + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = hem8_child.profile, + parent_device_id = device.id, + parent_assigned_child_key = hem8_child.child_key, + vendor_provided_label = name + } + driver:try_create_device(metadata) + end + end + end + do_refresh(driver, device) +end + +local aeotec_home_energy_meter_gen8_1_phase = { + NAME = "Aeotec Home Energy Meter Gen8", + supported_capabilities = { + capabilities.powerConsumptionReport + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zwave_handlers = { + [cc.METER] = { + [Meter.REPORT] = meter_report_handler + } + }, + lifecycle_handlers = { + added = device_added + }, + can_handle = require("aeotec-home-energy-meter-gen8.1-phase.can_handle") +} + +return aeotec_home_energy_meter_gen8_1_phase diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/can_handle.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/can_handle.lua new file mode 100644 index 0000000000..ec694e2708 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS = { + { mfr = 0x0371, prod = 0x0103, model = 0x002E } -- HEM Gen8 2 Phase US +} + +local function can_handle_aeotec_meter_gen8_2_phase(opts, driver, device, ...) + for _, fingerprint in ipairs(AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("aeotec-home-energy-meter-gen8.2-phase") + end + end + return false +end + +return can_handle_aeotec_meter_gen8_2_phase diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/init.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/init.lua new file mode 100644 index 0000000000..8d78da66f7 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/2-phase/init.lua @@ -0,0 +1,123 @@ +-- 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.Meter +local Meter = (require "st.zwave.CommandClass.Meter")({ version=4 }) +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +local utils = require "st.utils" +local power_consumption = require("aeotec-home-energy-meter-gen8.power_consumption") + +local POWER_UNIT_WATT = "W" +local ENERGY_UNIT_KWH = "kWh" + +local HEM8_DEVICES = { + { profile = 'aeotec-home-energy-meter-gen8-3-phase-con', name = 'Aeotec Home Energy Meter 8 Consumption', endpoints = { 1, 3, 5 } }, + { profile = 'aeotec-home-energy-meter-gen8-2-phase-pro', name = 'Aeotec Home Energy Meter 8 Production', child_key = 'pro', endpoints = { 2, 4, 6 } }, + { profile = 'aeotec-home-energy-meter-gen8-sald-con', name = 'Aeotec Home Energy Meter 8 Settled Consumption', child_key = 'sald-con', endpoints = { 7 } }, + { profile = 'aeotec-home-energy-meter-gen8-sald-pro', name = 'Aeotec Home Energy Meter 8 Settled Production', child_key = 'sald-pro', endpoints = { 8 } } +} + +local function find_hem8_child_device_key_by_endpoint(endpoint) + for _, child in ipairs(HEM8_DEVICES) do + if child.endpoints then + for _, e in ipairs(child.endpoints) do + if e == endpoint then + return child.child_key + end + end + end + end +end + +local function meter_report_handler(driver, device, cmd, zb_rx) + local endpoint = cmd.src_channel + local device_to_emit_with = device + local child_device_key = find_hem8_child_device_key_by_endpoint(endpoint); + local child_device = device:get_child_by_parent_assigned_key(child_device_key) + + if(child_device) then + device_to_emit_with = child_device + end + + if cmd.args.scale == Meter.scale.electric_meter.KILOWATT_HOURS then + local event_arguments = { + value = cmd.args.meter_value, + unit = ENERGY_UNIT_KWH + } + -- energyMeter + device_to_emit_with:emit_event_for_endpoint( + cmd.src_channel, + capabilities.energyMeter.energy(event_arguments) + ) + + if endpoint == 7 then + -- powerConsumptionReport + power_consumption.emit_power_consumption_report_event(device, { value = event_arguments.value }) + end + elseif cmd.args.scale == Meter.scale.electric_meter.WATTS then + local event_arguments = { + value = cmd.args.meter_value, + unit = POWER_UNIT_WATT + } + -- powerMeter + device_to_emit_with:emit_event_for_endpoint( + cmd.src_channel, + capabilities.powerMeter.power(event_arguments) + ) + end +end + +local function do_refresh(self, device) + for _, d in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(d.endpoints) do + device:send(Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, {dst_channels = {endpoint}})) + device:send(Meter:Get({scale = Meter.scale.electric_meter.WATTS}, {dst_channels = {endpoint}})) + end + end +end + +local function device_added(driver, device) + if device.network_type == st_device.NETWORK_TYPE_ZWAVE and not (device.child_ids and utils.table_size(device.child_ids) ~= 0) then + for i, hem8_child in ipairs(HEM8_DEVICES) do + if(hem8_child["child_key"]) then + local name = hem8_child.name + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = hem8_child.profile, + parent_device_id = device.id, + parent_assigned_child_key = hem8_child.child_key, + vendor_provided_label = name + } + driver:try_create_device(metadata) + end + end + end + do_refresh(driver, device) +end + +local aeotec_home_energy_meter_gen8_2_phase = { + NAME = "Aeotec Home Energy Meter Gen8", + supported_capabilities = { + capabilities.powerConsumptionReport + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zwave_handlers = { + [cc.METER] = { + [Meter.REPORT] = meter_report_handler + } + }, + lifecycle_handlers = { + added = device_added + }, + can_handle = require("aeotec-home-energy-meter-gen8.2-phase.can_handle") +} + +return aeotec_home_energy_meter_gen8_2_phase diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/can_handle.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/can_handle.lua new file mode 100644 index 0000000000..9f3cc6cb03 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS = { + { mfr = 0x0371, prod = 0x0003, model = 0x0034 }, -- HEM Gen8 3 Phase EU + { mfr = 0x0371, prod = 0x0102, model = 0x0034 } -- HEM Gen8 3 Phase AU +} + +local function can_handle_aeotec_meter_gen8_3_phase(opts, driver, device, ...) + for _, fingerprint in ipairs(AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("aeotec-home-energy-meter-gen8.3-phase") + end + end + return false +end + +return can_handle_aeotec_meter_gen8_3_phase diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/init.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/init.lua new file mode 100644 index 0000000000..cb1c582fb6 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/3-phase/init.lua @@ -0,0 +1,123 @@ +-- 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.Meter +local Meter = (require "st.zwave.CommandClass.Meter")({ version=4 }) +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +local utils = require "st.utils" +local power_consumption = require("aeotec-home-energy-meter-gen8.power_consumption") + +local POWER_UNIT_WATT = "W" +local ENERGY_UNIT_KWH = "kWh" + +local HEM8_DEVICES = { + { profile = 'aeotec-home-energy-meter-gen8-3-phase-con', name = 'Aeotec Home Energy Meter 8 Consumption', endpoints = { 1, 3, 5, 7 } }, + { profile = 'aeotec-home-energy-meter-gen8-3-phase-pro', name = 'Aeotec Home Energy Meter 8 Production', child_key = 'pro', endpoints = { 2, 4, 6, 8 } }, + { profile = 'aeotec-home-energy-meter-gen8-sald-con', name = 'Aeotec Home Energy Meter 8 Settled Consumption', child_key = 'sald-con', endpoints = { 9 } }, + { profile = 'aeotec-home-energy-meter-gen8-sald-pro', name = 'Aeotec Home Energy Meter 8 Settled Production', child_key = 'sald-pro', endpoints = { 10 } } +} + +local function find_hem8_child_device_key_by_endpoint(endpoint) + for _, child in ipairs(HEM8_DEVICES) do + if child.endpoints then + for _, e in ipairs(child.endpoints) do + if e == endpoint then + return child.child_key + end + end + end + end +end + +local function meter_report_handler(driver, device, cmd, zb_rx) + local endpoint = cmd.src_channel + local device_to_emit_with = device + local child_device_key = find_hem8_child_device_key_by_endpoint(endpoint); + local child_device = device:get_child_by_parent_assigned_key(child_device_key) + + if(child_device) then + device_to_emit_with = child_device + end + + if cmd.args.scale == Meter.scale.electric_meter.KILOWATT_HOURS then + local event_arguments = { + value = cmd.args.meter_value, + unit = ENERGY_UNIT_KWH + } + -- energyMeter + device_to_emit_with:emit_event_for_endpoint( + cmd.src_channel, + capabilities.energyMeter.energy(event_arguments) + ) + + if endpoint == 9 then + -- powerConsumptionReport + power_consumption.emit_power_consumption_report_event(device, { value = event_arguments.value }) + end + elseif cmd.args.scale == Meter.scale.electric_meter.WATTS then + local event_arguments = { + value = cmd.args.meter_value, + unit = POWER_UNIT_WATT + } + -- powerMeter + device_to_emit_with:emit_event_for_endpoint( + cmd.src_channel, + capabilities.powerMeter.power(event_arguments) + ) + end +end + +local function do_refresh(self, device) + for _, d in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(d.endpoints) do + device:send(Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, {dst_channels = {endpoint}})) + device:send(Meter:Get({scale = Meter.scale.electric_meter.WATTS}, {dst_channels = {endpoint}})) + end + end +end + +local function device_added(driver, device) + if device.network_type == st_device.NETWORK_TYPE_ZWAVE and not (device.child_ids and utils.table_size(device.child_ids) ~= 0) then + for i, hem8_child in ipairs(HEM8_DEVICES) do + if(hem8_child["child_key"]) then + local name = hem8_child.name + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = hem8_child.profile, + parent_device_id = device.id, + parent_assigned_child_key = hem8_child.child_key, + vendor_provided_label = name + } + driver:try_create_device(metadata) + end + end + end + do_refresh(driver, device) +end + +local aeotec_home_energy_meter_gen8_3_phase = { + NAME = "Aeotec Home Energy Meter Gen8", + supported_capabilities = { + capabilities.powerConsumptionReport + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zwave_handlers = { + [cc.METER] = { + [Meter.REPORT] = meter_report_handler + } + }, + lifecycle_handlers = { + added = device_added + }, + can_handle = require("aeotec-home-energy-meter-gen8.3-phase.can_handle") +} + +return aeotec_home_energy_meter_gen8_3_phase diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/can_handle.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/can_handle.lua new file mode 100644 index 0000000000..918e703b69 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/can_handle.lua @@ -0,0 +1,22 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS = { + { mfr = 0x0371, prod = 0x0003, model = 0x0033 }, -- HEM Gen8 1 Phase EU + { mfr = 0x0371, prod = 0x0003, model = 0x0034 }, -- HEM Gen8 3 Phase EU + { mfr = 0x0371, prod = 0x0103, model = 0x002E }, -- HEM Gen8 2 Phase US + { mfr = 0x0371, prod = 0x0102, model = 0x002E }, -- HEM Gen8 1 Phase AU + { mfr = 0x0371, prod = 0x0102, model = 0x0034 }, -- HEM Gen8 3 Phase AU +} + +local function can_handle_aeotec_meter_gen8(opts, driver, device, ...) + for _, fingerprint in ipairs(AEOTEC_HOME_ENERGY_METER_GEN8_FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("aeotec-home-energy-meter-gen8") + return true, subdriver + end + end + return false +end + +return can_handle_aeotec_meter_gen8 diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/init.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/init.lua new file mode 100644 index 0000000000..10d07de685 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/init.lua @@ -0,0 +1,49 @@ +-- Copyright 2025 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 function device_added(driver, device) + device:refresh() +end + +local function component_to_endpoint(device, component_id) + local ep_num = component_id:match("clamp(%d)") + return { ep_num and tonumber(ep_num) } +end + +local function endpoint_to_component(device, ep) + local meter_comp = string.format("clamp%d", ep) + if device.profile.components[meter_comp] ~= nil then + return meter_comp + else + return "main" + end +end + +local device_init = function(self, device) + device:set_component_to_endpoint_fn(component_to_endpoint) + device:set_endpoint_to_component_fn(endpoint_to_component) +end + +local do_configure = function (self, device) + device:send(Configuration:Set({parameter_number = 111, configuration_value = 300, size = 4})) -- ...every 5 min + device:send(Configuration:Set({parameter_number = 112, configuration_value = 300, size = 4})) -- ...every 5 min + device:send(Configuration:Set({parameter_number = 113, configuration_value = 300, size = 4})) -- ...every 5 min +end + +local aeotec_home_energy_meter_gen8 = { + NAME = "Aeotec Home Energy Meter Gen8", + lifecycle_handlers = { + added = device_added, + init = device_init, + doConfigure = do_configure + }, + can_handle = require("aeotec-home-energy-meter-gen8.can_handle"), + sub_drivers = { + require("aeotec-home-energy-meter-gen8.sub_drivers") + } +} + +return aeotec_home_energy_meter_gen8 diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/power_consumption.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/power_consumption.lua new file mode 100644 index 0000000000..2c32c5c964 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/power_consumption.lua @@ -0,0 +1,32 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2. + +local capabilities = require "st.capabilities" + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local power_consumption = {} + +power_consumption.emit_power_consumption_report_event = function (device, value) + -- powerConsumptionReport report interval + local current_time = os.time() + local last_time = device:get_field(LAST_REPORT_TIME) or 0 + local next_time = last_time + 60 * 15 -- 15 mins, the minimum interval allowed between reports + if current_time < next_time then + return + end + device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) + local raw_value = value.value * 1000 -- 'Wh' + + 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 - current_power_consumption.energy, 0.0) + end + device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ + energy = raw_value, + deltaEnergy = delta_energy + })) +end + +return power_consumption \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/sub_drivers.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/sub_drivers.lua new file mode 100644 index 0000000000..98c3fb45f8 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-home-energy-meter-gen8/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-home-energy-meter-gen8.1-phase"), + lazy_load_if_possible("aeotec-home-energy-meter-gen8.2-phase"), + lazy_load_if_possible("aeotec-home-energy-meter-gen8.3-phase") +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-electric-meter/src/init.lua b/drivers/SmartThings/zwave-electric-meter/src/init.lua index 66205758bd..851de3096f 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/init.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/init.lua @@ -7,11 +7,31 @@ local capabilities = require "st.capabilities" local defaults = require "st.zwave.defaults" --- @type st.zwave.Driver local ZwaveDriver = require "st.zwave.driver" +--- @type st.zwave.CommandClass.Configuration +local Configuration = (require "st.zwave.CommandClass.Configuration")({version=1}) + +local preferencesMap = require "preferences" local device_added = function (self, device) device:refresh() end +--- Handle preference changes +--- +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param event table +--- @param args +local function info_changed(driver, device, event, args) + local preferences = preferencesMap.get_device_parameters(device) + 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 = preferencesMap.to_numeric_value(device.preferences[id]) + device:send(Configuration:Set({ parameter_number = preferences[id].parameter_number, size = preferences[id].size, configuration_value = new_parameter_value })) + end + end +end + local driver_template = { supported_capabilities = { capabilities.powerMeter, @@ -19,6 +39,7 @@ local driver_template = { capabilities.refresh }, lifecycle_handlers = { + infoChanged = info_changed, added = device_added }, sub_drivers = require("sub_drivers"), diff --git a/drivers/SmartThings/zwave-electric-meter/src/preferences.lua b/drivers/SmartThings/zwave-electric-meter/src/preferences.lua new file mode 100644 index 0000000000..b1f14c25bd --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/preferences.lua @@ -0,0 +1,95 @@ +local devices = { + AEOTEC_HOME_ENERGY_METER_GEN8_1_PHASE = { + MATCHING_MATRIX = { + mfrs = 0x0371, + product_types = {0x0003, 0x0102 }, + product_ids = 0x0033 + }, + PARAMETERS = { + thresholdCheck = {parameter_number = 3, size = 1}, + imWThresholdTotal = {parameter_number = 4, size = 2}, + imWThresholdPhaseA = {parameter_number = 5, size = 2}, + exWThresholdTotal = {parameter_number = 8, size = 2}, + exWThresholdPhaseA = {parameter_number = 9, size = 2}, + imtWPctThresholdTotal = {parameter_number = 12, size = 1}, + imWPctThresholdPhaseA = {parameter_number = 13, size = 1}, + exWPctThresholdTotal = {parameter_number = 16, size = 1}, + exWPctThresholdPhaseA = {parameter_number = 17, size = 1}, + autoRootDeviceReport = {parameter_number = 32, size = 1}, + } + }, + AEOTEC_HOME_ENERGY_METER_GEN8_2_PHASE = { + MATCHING_MATRIX = { + mfrs = 0x0371, + product_types = 0x0103, + product_ids = 0x002E + }, + PARAMETERS = { + thresholdCheck = {parameter_number = 3, size = 1}, + imWThresholdTotal = {parameter_number = 4, size = 2}, + imWThresholdPhaseA = {parameter_number = 5, size = 2}, + imWThresholdPhaseB = {parameter_number = 6, size = 2}, + exWThresholdTotal = {parameter_number = 8, size = 2}, + exWThresholdPhaseA = {parameter_number = 9, size = 2}, + exWThresholdPhaseB = {parameter_number = 10, size = 2}, + imtWPctThresholdTotal = {parameter_number = 12, size = 1}, + imWPctThresholdPhaseA = {parameter_number = 13, size = 1}, + imWPctThresholdPhaseB = {parameter_number = 14, size = 1}, + exWPctThresholdTotal = {parameter_number = 16, size = 1}, + exWPctThresholdPhaseA = {parameter_number = 17, size = 1}, + exWPctThresholdPhaseB = {parameter_number = 18, size = 1}, + autoRootDeviceReport = {parameter_number = 32, size = 1}, + } + }, + AEOTEC_HOME_ENERGY_METER_GEN8_3_PHASE = { + MATCHING_MATRIX = { + mfrs = 0x0371, + product_types = {0x0003, 0x0102}, + product_ids = 0x0034 + }, + PARAMETERS = { + thresholdCheck = {parameter_number = 3, size = 1}, + imWThresholdTotal = {parameter_number = 4, size = 2}, + imWThresholdPhaseA = {parameter_number = 5, size = 2}, + imWThresholdPhaseB = {parameter_number = 6, size = 2}, + imWThresholdPhaseC = {parameter_number = 7, size = 2}, + exWThresholdTotal = {parameter_number = 8, size = 2}, + exWThresholdPhaseA = {parameter_number = 9, size = 2}, + exWThresholdPhaseB = {parameter_number = 10, size = 2}, + exWThresholdPhaseC = {parameter_number = 11, size = 2}, + imtWPctThresholdTotal = {parameter_number = 12, size = 1}, + imWPctThresholdPhaseA = {parameter_number = 13, size = 1}, + imWPctThresholdPhaseB = {parameter_number = 14, size = 1}, + imWPctThresholdPhaseC = {parameter_number = 15, size = 1}, + exWPctThresholdTotal = {parameter_number = 16, size = 1}, + exWPctThresholdPhaseA = {parameter_number = 17, size = 1}, + exWPctThresholdPhaseB = {parameter_number = 18, size = 1}, + exWPctThresholdPhaseC = {parameter_number = 19, size = 1}, + autoRootDeviceReport = {parameter_number = 32, size = 1}, + } + } +} + +local preferences = {} + +preferences.get_device_parameters = 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.PARAMETERS + end + end + return nil +end + +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 + +return preferences diff --git a/drivers/SmartThings/zwave-electric-meter/src/sub_drivers.lua b/drivers/SmartThings/zwave-electric-meter/src/sub_drivers.lua index 60d4a7380b..3c0e482bda 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/sub_drivers.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/sub_drivers.lua @@ -6,5 +6,6 @@ local sub_drivers = { lazy_load_if_possible("qubino-meter"), lazy_load_if_possible("aeotec-gen5-meter"), lazy_load_if_possible("aeon-meter"), + lazy_load_if_possible("aeotec-home-energy-meter-gen8"), } return sub_drivers diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_1_phase.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_1_phase.lua new file mode 100644 index 0000000000..f581f457df --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_1_phase.lua @@ -0,0 +1,560 @@ +-- 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 Meter = (require "st.zwave.CommandClass.Meter")({version=4}) +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) +local t_utils = require "integration_test.utils" + +local AEOTEC_MFR_ID = 0x0371 +local AEOTEC_METER_PROD_TYPE = 0x0003 +local AEOTEC_METER_PROD_ID = 0x0033 + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local aeotec_meter_endpoints = { + { + command_classes = { + {value = zw.METER} + } + } +} + +local HEM8_DEVICES = { + { + profile = 'aeotec-home-energy-meter-gen8-1-phase-con', + name = 'Aeotec Home Energy Meter 8 Consumption', + endpoints = { 1, 3 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-1-phase-pro', + name = 'Aeotec Home Energy Meter 8 Production', + child_key = 'pro', + endpoints = { 2, 4 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-sald-con', + name = 'Aeotec Home Energy Meter 8 Settled Consumption', + child_key = 'sald-con', + endpoints = { 5 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-sald-pro', + name = 'Aeotec Home Energy Meter 8 Settled Production', + child_key = 'sald-pro', + endpoints = { 6 } + } +} + +local mock_parent = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[1].profile .. '.yml'), + zwave_endpoints = aeotec_meter_endpoints, + zwave_manufacturer_id = AEOTEC_MFR_ID, + zwave_product_type = AEOTEC_METER_PROD_TYPE, + zwave_product_id = AEOTEC_METER_PROD_ID +}) + +local mock_child_prod = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[2].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[2].child_key +}) + +local mock_child_sald_con = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[3].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[3].child_key +}) + +local mock_child_sald_prod = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[4].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[4].child_key +}) + +local function test_init() + test.mock_device.add_test_device(mock_parent) + test.mock_device.add_test_device(mock_child_prod) + test.mock_device.add_test_device(mock_child_sald_con) + test.mock_device.add_test_device(mock_child_sald_prod) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Added lifecycle event should create children for parent device", + function() + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_parent.id, "added" }) + + for _, child in ipairs(HEM8_DEVICES) do + if(child["child_key"]) then + mock_parent:expect_device_create( + { + type = "EDGE_CHILD", + label = child.name, + profile = child.profile, + parent_device_id = mock_parent.id, + parent_assigned_child_key = child.child_key + } + ) + end + end + -- Refresh + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.WATTS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + end + end + end +) + +test.register_coroutine_test( + "Configure should configure all necessary attributes", + function() + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_parent.id, "doConfigure" }) + + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 111, size = 4, configuration_value = 300}) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 112, size = 4, configuration_value = 300}) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 113, size = 4, configuration_value = 300}) + )) + mock_parent:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_coroutine_test( + "Power meter report should be handled", + function() + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + local component = "main" + if endpoint ~= 3 and endpoint ~= 4 and endpoint ~= 5 and endpoint ~= 6 then + component = string.format("clamp%d", endpoint) + end + test.socket.zwave:__queue_receive({ + mock_parent.id, + Meter:Report({ + scale = Meter.scale.electric_meter.WATTS, + meter_value = 27 + }, { + encap = zw.ENCAP.AUTO, + src_channel = endpoint, + dst_channels = {0} + }) + }) + if(device["child_key"]) then + if(device["child_key"] == "pro") then + test.socket.capability:__expect_send( + mock_child_prod:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + elseif (device["child_key"] == "sald-pro") then + test.socket.capability:__expect_send( + mock_child_sald_prod:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + elseif (device["child_key"] == "sald-con") then + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + end + else + test.socket.capability:__expect_send( + mock_parent:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + end + end + end + end +) + +test.register_coroutine_test( + "Energy meter report should be handled", + function() + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + local component = "main" + if endpoint ~= 3 and endpoint ~= 4 and endpoint ~= 5 and endpoint ~= 6 then + component = string.format("clamp%d", endpoint) + end + test.socket.zwave:__queue_receive({ + mock_parent.id, + Meter:Report({ + scale = Meter.scale.electric_meter.KILOWATT_HOURS, + meter_value = 5 + }, { + encap = zw.ENCAP.AUTO, + src_channel = endpoint, + dst_channels = {0} + }) + }) + if(device["child_key"]) then + if(device["child_key"] == "pro") then + test.socket.capability:__expect_send( + mock_child_prod:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + elseif (device["child_key"] == "sald-pro") then + test.socket.capability:__expect_send( + mock_child_sald_prod:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + elseif (device["child_key"] == "sald-con") then + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + end + else + test.socket.capability:__expect_send( + mock_parent:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + end + end + end + end +) + +test.register_coroutine_test( + "Report consumption and power consumption report after 15 minutes", function() + -- set time to trigger power consumption report + local current_time = os.time() - 60 * 20 + --mock_child_sald_con:set_field(LAST_REPORT_TIME, current_time) + mock_parent:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zwave:__queue_receive( + { + --mock_child_sald_con.id, + mock_parent.id, + zw_test_utils.zwave_test_build_receive_command(Meter:Report( + { + scale = Meter.scale.electric_meter.KILOWATT_HOURS, + meter_value = 5 + }, + { + encap = zw.ENCAP.AUTO, + src_channel = 5, + dst_channels = {0} + } + )) + } + ) + + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + + test.socket.capability:__expect_send( + -- mock_parent + mock_parent:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 5000 })) + ) + end +) + +test.register_coroutine_test( + "Handle preference: thresholdCheck (parameter 3) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + thresholdCheck = 0 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 3, + configuration_value = 0, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdTotal (parameter 4) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdTotal = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 4, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdPhaseA (parameter 5) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdPhaseA = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 5, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdTotal (parameter 8) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdTotal = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 8, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdPhaseA (parameter 9) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdPhaseA = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 9, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imtWPctThresholdTotal (parameter 12) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imtWPctThresholdTotal = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 12, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWPctThresholdPhaseA (parameter 13) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWPctThresholdPhaseA = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 13, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdTotal (parameter 16) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdTotal = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 16, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdPhaseA (parameter 17) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdPhaseA = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 17, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: autoRootDeviceReport (parameter 32) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + autoRootDeviceReport = 1 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 32, + configuration_value = 1, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Refresh sends commands to all components including base device", + function() + -- refresh commands for zwave devices do not have guaranteed ordering + test.socket.zwave:__set_channel_ordering("relaxed") + + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.WATTS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + end + end + + test.socket.capability:__queue_receive({ + mock_parent.id, + { capability = "refresh", component = "main", command = "refresh", args = { } } + }) + end +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_2_phase.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_2_phase.lua new file mode 100644 index 0000000000..43ea5cdbe3 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_2_phase.lua @@ -0,0 +1,654 @@ +-- 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 Meter = (require "st.zwave.CommandClass.Meter")({version=4}) +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) +local t_utils = require "integration_test.utils" + +local AEOTEC_MFR_ID = 0x0371 +local AEOTEC_METER_PROD_TYPE = 0x0103 +local AEOTEC_METER_PROD_ID = 0x002E + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local aeotec_meter_endpoints = { + { + command_classes = { + {value = zw.METER} + } + } +} + +local HEM8_DEVICES = { + { + profile = 'aeotec-home-energy-meter-gen8-2-phase-con', + name = 'Aeotec Home Energy Meter 8 Consumption', + endpoints = { 1, 3, 5 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-2-phase-pro', + name = 'Aeotec Home Energy Meter 8 Production', + child_key = 'pro', + endpoints = { 2, 4, 6 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-sald-con', + name = 'Aeotec Home Energy Meter 8 Settled Consumption', + child_key = 'sald-con', + endpoints = { 7 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-sald-pro', + name = 'Aeotec Home Energy Meter 8 Settled Production', + child_key = 'sald-pro', + endpoints = { 8 } + } +} + +local mock_parent = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[1].profile .. '.yml'), + zwave_endpoints = aeotec_meter_endpoints, + zwave_manufacturer_id = AEOTEC_MFR_ID, + zwave_product_type = AEOTEC_METER_PROD_TYPE, + zwave_product_id = AEOTEC_METER_PROD_ID +}) + +local mock_child_prod = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[2].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[2].child_key +}) + +local mock_child_sald_con = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[3].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[3].child_key +}) + +local mock_child_sald_prod = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[4].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[4].child_key +}) + +local function test_init() + test.mock_device.add_test_device(mock_parent) + test.mock_device.add_test_device(mock_child_prod) + test.mock_device.add_test_device(mock_child_sald_con) + test.mock_device.add_test_device(mock_child_sald_prod) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Added lifecycle event should create children for parent device", + function() + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_parent.id, "added" }) + + for _, child in ipairs(HEM8_DEVICES) do + if(child["child_key"]) then + mock_parent:expect_device_create( + { + type = "EDGE_CHILD", + label = child.name, + profile = child.profile, + parent_device_id = mock_parent.id, + parent_assigned_child_key = child.child_key + } + ) + end + end + -- Refresh + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.WATTS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + end + end + end +) + +test.register_coroutine_test( + "Configure should configure all necessary attributes", + function() + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_parent.id, "doConfigure" }) + + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 111, size = 4, configuration_value = 300}) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 112, size = 4, configuration_value = 300}) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 113, size = 4, configuration_value = 300}) + )) + mock_parent:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_coroutine_test( + "Power meter report should be handled", + function() + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + local component = "main" + if endpoint ~= 5 and endpoint ~= 6 and endpoint ~= 7 and endpoint ~= 8 then + component = string.format("clamp%d", endpoint) + end + test.socket.zwave:__queue_receive({ + mock_parent.id, + Meter:Report({ + scale = Meter.scale.electric_meter.WATTS, + meter_value = 27 + }, { + encap = zw.ENCAP.AUTO, + src_channel = endpoint, + dst_channels = {0} + }) + }) + if(device["child_key"]) then + if(device["child_key"] == "pro") then + test.socket.capability:__expect_send( + mock_child_prod:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + elseif (device["child_key"] == "sald-pro") then + test.socket.capability:__expect_send( + mock_child_sald_prod:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + elseif (device["child_key"] == "sald-con") then + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + end + else + test.socket.capability:__expect_send( + mock_parent:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + end + end + end + end +) + +test.register_coroutine_test( + "Energy meter report should be handled", + function() + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + local component = "main" + if endpoint ~= 5 and endpoint ~= 6 and endpoint ~= 7 and endpoint ~= 8 then + component = string.format("clamp%d", endpoint) + end + test.socket.zwave:__queue_receive({ + mock_parent.id, + Meter:Report({ + scale = Meter.scale.electric_meter.KILOWATT_HOURS, + meter_value = 5 + }, { + encap = zw.ENCAP.AUTO, + src_channel = endpoint, + dst_channels = {0} + }) + }) + if(device["child_key"]) then + if(device["child_key"] == "pro") then + test.socket.capability:__expect_send( + mock_child_prod:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + elseif (device["child_key"] == "sald-pro") then + test.socket.capability:__expect_send( + mock_child_sald_prod:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + elseif (device["child_key"] == "sald-con") then + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + end + else + test.socket.capability:__expect_send( + mock_parent:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + end + end + end + end +) + +test.register_coroutine_test( + "Report consumption and power consumption report after 15 minutes", function() + -- set time to trigger power consumption report + local current_time = os.time() - 60 * 20 + --mock_child_sald_con:set_field(LAST_REPORT_TIME, current_time) + mock_parent:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zwave:__queue_receive( + { + mock_parent.id, + zw_test_utils.zwave_test_build_receive_command(Meter:Report( + { + scale = Meter.scale.electric_meter.KILOWATT_HOURS, + meter_value = 5 + }, + { + encap = zw.ENCAP.AUTO, + src_channel = 7, + dst_channels = {0} + } + )) + } + ) + + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + + test.socket.capability:__expect_send( + mock_parent:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 5000 })) + ) + end +) + +test.register_coroutine_test( + "Handle preference: thresholdCheck (parameter 3) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + thresholdCheck = 0 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 3, + configuration_value = 0, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdTotal (parameter 4) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdTotal = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 4, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdPhaseA (parameter 5) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdPhaseA = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 5, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdPhaseB (parameter 6) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdPhaseB = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 6, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdTotal (parameter 8) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdTotal = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 8, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdPhaseA (parameter 9) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdPhaseA = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 9, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdPhaseB (parameter 10) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdPhaseB = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 10, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imtWPctThresholdTotal (parameter 12) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imtWPctThresholdTotal = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 12, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWPctThresholdPhaseA (parameter 13) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWPctThresholdPhaseA = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 13, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWPctThresholdPhaseB (parameter 14) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWPctThresholdPhaseB = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 14, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdTotal (parameter 16) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdTotal = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 16, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdPhaseA (parameter 17) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdPhaseA = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 17, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdPhaseB (parameter 18) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdPhaseB = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 18, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: autoRootDeviceReport (parameter 32) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + autoRootDeviceReport = 1 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 32, + configuration_value = 1, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Refresh sends commands to all components including base device", + function() + -- refresh commands for zwave devices do not have guaranteed ordering + test.socket.zwave:__set_channel_ordering("relaxed") + + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.WATTS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + end + end + + test.socket.capability:__queue_receive({ + mock_parent.id, + { capability = "refresh", component = "main", command = "refresh", args = { } } + }) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_3_phase.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_3_phase.lua new file mode 100644 index 0000000000..465e3add9f --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_3_phase.lua @@ -0,0 +1,749 @@ +-- 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 Meter = (require "st.zwave.CommandClass.Meter")({version=4}) +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=1 }) +local t_utils = require "integration_test.utils" + +local AEOTEC_MFR_ID = 0x0371 +local AEOTEC_METER_PROD_TYPE = 0x0003 +local AEOTEC_METER_PROD_ID = 0x0034 + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" + +local aeotec_meter_endpoints = { + { + command_classes = { + {value = zw.METER} + } + } +} + +local HEM8_DEVICES = { + { + profile = 'aeotec-home-energy-meter-gen8-3-phase-con', + name = 'Aeotec Home Energy Meter 8 Consumption', + endpoints = { 1, 3, 5, 7 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-3-phase-pro', + name = 'Aeotec Home Energy Meter 8 Production', + child_key = 'pro', + endpoints = { 2, 4, 6, 8 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-sald-con', + name = 'Aeotec Home Energy Meter 8 Settled Consumption', + child_key = 'sald-con', + endpoints = { 9 } + }, + { + profile = 'aeotec-home-energy-meter-gen8-sald-pro', + name = 'Aeotec Home Energy Meter 8 Settled Production', + child_key = 'sald-pro', + endpoints = { 10 } + } +} + +local mock_parent = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[1].profile .. '.yml'), + zwave_endpoints = aeotec_meter_endpoints, + zwave_manufacturer_id = AEOTEC_MFR_ID, + zwave_product_type = AEOTEC_METER_PROD_TYPE, + zwave_product_id = AEOTEC_METER_PROD_ID +}) + +local mock_child_prod = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[2].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[2].child_key +}) + +local mock_child_sald_con = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[3].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[3].child_key +}) + +local mock_child_sald_prod = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition(HEM8_DEVICES[4].profile .. '.yml'), + parent_device_id = mock_parent.id, + parent_assigned_child_key = HEM8_DEVICES[4].child_key +}) + +local function test_init() + test.mock_device.add_test_device(mock_parent) + test.mock_device.add_test_device(mock_child_prod) + test.mock_device.add_test_device(mock_child_sald_con) + test.mock_device.add_test_device(mock_child_sald_prod) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Added lifecycle event should create children for parent device", + function() + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_parent.id, "added" }) + + for _, child in ipairs(HEM8_DEVICES) do + if(child["child_key"]) then + mock_parent:expect_device_create( + { + type = "EDGE_CHILD", + label = child.name, + profile = child.profile, + parent_device_id = mock_parent.id, + parent_assigned_child_key = child.child_key + } + ) + end + end + -- Refresh + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.WATTS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + end + end + end +) + +test.register_coroutine_test( + "Configure should configure all necessary attributes", + function() + test.socket.zwave:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_parent.id, "doConfigure" }) + + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 111, size = 4, configuration_value = 300}) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 112, size = 4, configuration_value = 300}) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({parameter_number = 113, size = 4, configuration_value = 300}) + )) + mock_parent:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_coroutine_test( + "Power meter report should be handled", + function() + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + local component = "main" + if endpoint ~= 7 and endpoint ~= 8 and endpoint ~= 9 and endpoint ~= 10 then + component = string.format("clamp%d", endpoint) + end + test.socket.zwave:__queue_receive({ + mock_parent.id, + Meter:Report({ + scale = Meter.scale.electric_meter.WATTS, + meter_value = 27 + }, { + encap = zw.ENCAP.AUTO, + src_channel = endpoint, + dst_channels = {0} + }) + }) + if(device["child_key"]) then + if(device["child_key"] == "pro") then + test.socket.capability:__expect_send( + mock_child_prod:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + elseif (device["child_key"] == "sald-pro") then + test.socket.capability:__expect_send( + mock_child_sald_prod:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + elseif (device["child_key"] == "sald-con") then + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + end + else + test.socket.capability:__expect_send( + mock_parent:generate_test_message(component, capabilities.powerMeter.power({ value = 27, unit = "W" })) + ) + end + end + end + end +) + +test.register_coroutine_test( + "Energy meter report should be handled", + function() + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + local component = "main" + if endpoint ~= 7 and endpoint ~= 8 and endpoint ~= 9 and endpoint ~= 10 then + component = string.format("clamp%d", endpoint) + end + test.socket.zwave:__queue_receive({ + mock_parent.id, + Meter:Report({ + scale = Meter.scale.electric_meter.KILOWATT_HOURS, + meter_value = 5 + }, { + encap = zw.ENCAP.AUTO, + src_channel = endpoint, + dst_channels = {0} + }) + }) + if(device["child_key"]) then + if(device["child_key"] == "pro") then + test.socket.capability:__expect_send( + mock_child_prod:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + elseif (device["child_key"] == "sald-pro") then + test.socket.capability:__expect_send( + mock_child_sald_prod:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + elseif (device["child_key"] == "sald-con") then + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + end + else + test.socket.capability:__expect_send( + mock_parent:generate_test_message(component, capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + end + end + end + end +) + +test.register_coroutine_test( + "Report consumption and power consumption report after 15 minutes", function() + -- set time to trigger power consumption report + local current_time = os.time() - 60 * 20 + -- mock_child_sald_con:set_field(LAST_REPORT_TIME, current_time) + mock_parent:set_field(LAST_REPORT_TIME, current_time) + test.socket.zwave:__queue_receive( + { + mock_parent.id, + zw_test_utils.zwave_test_build_receive_command(Meter:Report( + { + scale = Meter.scale.electric_meter.KILOWATT_HOURS, + meter_value = 5 + }, + { + encap = zw.ENCAP.AUTO, + src_channel = 9, + dst_channels = {0} + } + )) + } + ) + + test.socket.capability:__expect_send( + mock_child_sald_con:generate_test_message("main", capabilities.energyMeter.energy({ value = 5, unit = "kWh" })) + ) + + test.socket.capability:__expect_send( + mock_parent:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 5000 })) + ) + end +) + +test.register_coroutine_test( + "Handle preference: thresholdCheck (parameter 3) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + thresholdCheck = 0 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 3, + configuration_value = 0, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdTotal (parameter 4) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdTotal = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 4, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdPhaseA (parameter 5) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdPhaseA = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 5, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdPhaseB (parameter 6) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdPhaseB = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 6, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWThresholdPhaseC (parameter 7) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWThresholdPhaseC = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 7, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdTotal (parameter 8) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdTotal = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 8, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdPhaseA (parameter 9) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdPhaseA = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 9, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdPhaseB (parameter 10) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdPhaseB = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 10, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWThresholdPhaseC (parameter 11) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWThresholdPhaseC = 3500 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 11, + configuration_value = 3500, + size = 2 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imtWPctThresholdTotal (parameter 12) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imtWPctThresholdTotal = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 12, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWPctThresholdPhaseA (parameter 13) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWPctThresholdPhaseA = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 13, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWPctThresholdPhaseB (parameter 14) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWPctThresholdPhaseB = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 14, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: imWPctThresholdPhaseC (parameter 15) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + imWPctThresholdPhaseC = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 15, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdTotal (parameter 16) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdTotal = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 16, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdPhaseA (parameter 17) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdPhaseA = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 17, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: exWPctThresholdPhaseB (parameter 18) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdPhaseB = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 18, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: thresholdCheck (exWPctThresholdPhaseC 19) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + exWPctThresholdPhaseC = 50 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 19, + configuration_value = 50, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Handle preference: autoRootDeviceReport (parameter 32) in infoChanged", + function() + test.socket.device_lifecycle:__queue_receive( + mock_parent:generate_info_changed({ + preferences = { + autoRootDeviceReport = 1 + } + }) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Configuration:Set({ + parameter_number = 32, + configuration_value = 1, + size = 1 + }) + ) + ) + end +) + +test.register_coroutine_test( + "Refresh sends commands to all components including base device", + function() + -- refresh commands for zwave devices do not have guaranteed ordering + test.socket.zwave:__set_channel_ordering("relaxed") + + for _, device in ipairs(HEM8_DEVICES) do + for _, endpoint in ipairs(device.endpoints) do + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.WATTS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent, + Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}, { + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = { endpoint } + }) + ) + ) + end + end + + test.socket.capability:__queue_receive({ + mock_parent.id, + { capability = "refresh", component = "main", command = "refresh", args = { } } + }) + end +) + +test.run_registered_tests() \ No newline at end of file From cd86c6eb216739428ab017251e1cfd57ccea4fb0 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Thu, 9 Apr 2026 00:08:38 -0500 Subject: [PATCH 237/538] Reinit capabilities upon feature change when profile is unchanged --- .../camera_utils/device_configuration.lua | 4 ++ .../src/sub_drivers/camera/init.lua | 6 +- .../src/test/test_matter_camera.lua | 65 +++++++++++++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) 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 index f709d25420..88b3ccd1b7 100644 --- 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 @@ -42,6 +42,7 @@ function CameraDeviceConfiguration.create_child_devices(driver, device) end function CameraDeviceConfiguration.match_profile(device, status_light_enabled_present, status_light_brightness_present) + local profile_update_requested = false local optional_supported_component_capabilities = {} local main_component_capabilities = {} local status_led_component_capabilities = {} @@ -145,12 +146,15 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr end if camera_utils.optional_capabilities_list_changed(optional_supported_component_capabilities, device.profile.components) then + profile_update_requested = true 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 + + return profile_update_requested end local function init_webrtc(device) diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index 91ebe03ebc..1dbad05698 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -49,12 +49,14 @@ end function CameraLifecycleHandlers.info_changed(driver, device, event, args) local software_version_changed = device.matter_version ~= nil and args.old_st_store.matter_version ~= nil and device.matter_version.software ~= args.old_st_store.matter_version.software + local profile_update_requested = false + local profile_changed = not switch_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) if software_version_changed then - camera_cfg.match_profile(device, false, false) + profile_update_requested = camera_cfg.match_profile(device, false, false) end - if not switch_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then + if profile_changed or (software_version_changed and not profile_update_requested) 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 diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 631060f79b..d248f585b5 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -405,6 +405,71 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Software version change should initialize camera capabilities when profile is unchanged", + function() + local camera_handler = require "sub_drivers.camera" + local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" + local button_cfg = require("switch_utils.device_configuration").ButtonCfg + + local match_profile_called = false + local init_called = false + local subscribe_called = false + local configure_buttons_called = false + + local fake_device = { + matter_version = { hardware = 1, software = 3 }, + profile = { id = "camera" }, + endpoints = { + { + endpoint_id = CAMERA_EP, + device_types = { + {device_type_id = 0x0142, device_type_revision = 1} -- Camera + } + }, + { + endpoint_id = DOORBELL_EP, + device_types = { + {device_type_id = 0x0143, device_type_revision = 1} -- Doorbell + } + } + }, + subscribe = function() subscribe_called = true end, + get_endpoints = function() return { DOORBELL_EP } end, + } + + local original_match_profile = camera_cfg.match_profile + local original_init = camera_cfg.initialize_camera_capabilities + local original_configure_buttons = button_cfg.configure_buttons + + camera_cfg.match_profile = function() + match_profile_called = true + return false + end + camera_cfg.initialize_camera_capabilities = function() init_called = true end + button_cfg.configure_buttons = function() configure_buttons_called = true end + + camera_handler.lifecycle_handlers.infoChanged(nil, fake_device, nil, { + old_st_store = { + matter_version = { hardware = 1, software = 1 }, + profile = fake_device.profile, + } + }) + + camera_cfg.match_profile = original_match_profile + camera_cfg.initialize_camera_capabilities = original_init + button_cfg.configure_buttons = original_configure_buttons + + assert(match_profile_called, "match_profile should be called on software version change") + assert(init_called, "initialize_camera_capabilities should be called") + assert(subscribe_called, "subscribe should be called") + assert(configure_buttons_called, "configure_buttons should be called") + end, + { + min_api_version = 17 + } +) + test.register_coroutine_test( "Reports mapping to EnabledState capability data type should generate appropriate events", function() From 917e7103b7d1dbf7fdf96b5133fce5a35619401f Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Thu, 9 Apr 2026 13:02:41 -0500 Subject: [PATCH 238/538] Handle FeatureMap change, fix profile matching & reinit --- .../camera_handlers/attribute_handlers.lua | 7 +- .../camera_utils/device_configuration.lua | 302 ++++++++++++++---- .../camera/camera_utils/fields.lua | 6 + .../sub_drivers/camera/camera_utils/utils.lua | 25 +- .../src/sub_drivers/camera/init.lua | 27 +- .../src/test/test_matter_camera.lua | 47 ++- 6 files changed, 328 insertions(+), 86 deletions(-) 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 index 140ba6d313..b32b1dd55c 100644 --- 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 @@ -466,7 +466,12 @@ function CameraAttributeHandlers.camera_av_stream_management_attribute_list_hand 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) + camera_cfg.update_status_light_attribute_presence(device, status_light_enabled_present, status_light_brightness_present) + camera_cfg.reconcile_profile_and_capabilities(device) +end + +function CameraAttributeHandlers.camera_feature_map_handler(driver, device, ib, response) + camera_cfg.reconcile_profile_and_capabilities(device) end return CameraAttributeHandlers 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 index 88b3ccd1b7..c2ea259aa1 100644 --- 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 @@ -12,6 +12,152 @@ local switch_utils = require "switch_utils.utils" local CameraDeviceConfiguration = {} +local managed_capability_map = { + { key = "webrtc", capability = capabilities.webrtc }, + { key = "ptz", capability = capabilities.mechanicalPanTiltZoom }, + { key = "zone_management", capability = capabilities.zoneManagement }, + { key = "local_media_storage", capability = capabilities.localMediaStorage }, + { key = "audio_recording", capability = capabilities.audioRecording }, + { key = "video_stream_settings", capability = capabilities.videoStreamSettings }, + { key = "camera_privacy_mode", capability = capabilities.cameraPrivacyMode }, +} + +local function get_status_light_presence(device) + return device:get_field(camera_fields.STATUS_LIGHT_ENABLED_PRESENT), + device:get_field(camera_fields.STATUS_LIGHT_BRIGHTNESS_PRESENT) +end + +local function set_status_light_presence(device, status_light_enabled_present, status_light_brightness_present) + device:set_field(camera_fields.STATUS_LIGHT_ENABLED_PRESENT, status_light_enabled_present == true, { persist = true }) + device:set_field(camera_fields.STATUS_LIGHT_BRIGHTNESS_PRESENT, status_light_brightness_present == true, { persist = true }) +end + +local function build_webrtc_supported_features() + return { + bundle = true, + order = "audio/video", + audio = "sendrecv", + video = "recvonly", + turnSource = "player", + supportTrickleICE = true + } +end + +local function build_ptz_supported_attributes(device) + 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 + return supported_attributes +end + +local function build_zone_management_supported_features(device) + local 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 + return supported_features +end + +local function build_local_media_storage_supported_attributes(device) + 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 + return supported_attributes +end + +local function build_video_stream_settings_supported_features(device) + 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 + return supported_features +end + +local function build_camera_privacy_supported_attributes() + return { "softRecordingPrivacyMode", "softLivestreamPrivacyMode" } +end + +local function build_camera_privacy_supported_commands() + return { "setSoftRecordingPrivacyMode", "setSoftLivestreamPrivacyMode" } +end + +local function capabilities_needing_reinit(device) + local main = camera_fields.profile_components.main + + local capabilities_to_reinit = {} + + local function state_differs(capability, attribute_name, expected) + local current = device:get_latest_state(main, capability.ID, attribute_name) + return not switch_utils.deep_equals(current, expected, { ignore_functions = true }) + end + + if device:supports_capability(capabilities.webrtc) and + state_differs(capabilities.webrtc, capabilities.webrtc.supportedFeatures.NAME, build_webrtc_supported_features()) then + capabilities_to_reinit.webrtc = true + end + + if device:supports_capability(capabilities.mechanicalPanTiltZoom) and + state_differs(capabilities.mechanicalPanTiltZoom, capabilities.mechanicalPanTiltZoom.supportedAttributes.NAME, build_ptz_supported_attributes(device)) then + capabilities_to_reinit.ptz = true + end + + if device:supports_capability(capabilities.zoneManagement) and + state_differs(capabilities.zoneManagement, capabilities.zoneManagement.supportedFeatures.NAME, build_zone_management_supported_features(device)) then + capabilities_to_reinit.zone_management = true + end + + if device:supports_capability(capabilities.localMediaStorage) and + state_differs(capabilities.localMediaStorage, capabilities.localMediaStorage.supportedAttributes.NAME, build_local_media_storage_supported_attributes(device)) then + capabilities_to_reinit.local_media_storage = true + end + + if device:supports_capability(capabilities.audioRecording) then + local audio_enabled_state = device:get_latest_state(main, capabilities.audioRecording.ID, capabilities.audioRecording.audioRecording.NAME) + if audio_enabled_state == nil then + capabilities_to_reinit.audio_recording = true + end + end + + if device:supports_capability(capabilities.videoStreamSettings) and + state_differs(capabilities.videoStreamSettings, capabilities.videoStreamSettings.supportedFeatures.NAME, build_video_stream_settings_supported_features(device)) then + capabilities_to_reinit.video_stream_settings = true + end + + if device:supports_capability(capabilities.cameraPrivacyMode) and + (state_differs(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedAttributes.NAME, build_camera_privacy_supported_attributes()) or + state_differs(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedCommands.NAME, build_camera_privacy_supported_commands())) then + capabilities_to_reinit.camera_privacy_mode = true + end + + return capabilities_to_reinit +end + function CameraDeviceConfiguration.create_child_devices(driver, device) local num_floodlight_eps = 0 local parent_child_device = false @@ -41,7 +187,8 @@ function CameraDeviceConfiguration.create_child_devices(driver, device) end end -function CameraDeviceConfiguration.match_profile(device, status_light_enabled_present, status_light_brightness_present) +function CameraDeviceConfiguration.match_profile(device) + local status_light_enabled_present, status_light_brightness_present = get_status_light_presence(device) local profile_update_requested = false local optional_supported_component_capabilities = {} local main_component_capabilities = {} @@ -159,68 +306,29 @@ 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 - } - })) + device:emit_event_for_endpoint(transport_provider_ep_ids[1], capabilities.webrtc.supportedFeatures(build_webrtc_supported_features())) 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)) + device:emit_event_for_endpoint(av_settings_ep_ids[1], capabilities.mechanicalPanTiltZoom.supportedAttributes(build_ptz_supported_attributes(device))) 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)) + device:emit_event_for_endpoint(zone_management_ep_ids[1], capabilities.zoneManagement.supportedFeatures(build_zone_management_supported_features(device))) 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)) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.localMediaStorage.supportedAttributes(build_local_media_storage_supported_attributes(device))) end end @@ -239,33 +347,16 @@ 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)) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.videoStreamSettings.supportedFeatures(build_video_stream_settings_supported_features(device))) 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)) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedAttributes(build_camera_privacy_supported_attributes())) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedCommands(build_camera_privacy_supported_commands())) end end @@ -279,6 +370,89 @@ function CameraDeviceConfiguration.initialize_camera_capabilities(device) init_camera_privacy_mode(device) end +function CameraDeviceConfiguration.initialize_camera_capabilities_and_subscriptions(device) + CameraDeviceConfiguration.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 + +local function initialize_selected_camera_capabilities(device, capabilities_to_reinit) + local reinit_targets = capabilities_to_reinit or {} + + if reinit_targets.webrtc then + init_webrtc(device) + end + if reinit_targets.ptz then + init_ptz(device) + end + if reinit_targets.zone_management then + init_zone_management(device) + end + if reinit_targets.local_media_storage then + init_local_media_storage(device) + end + if reinit_targets.audio_recording then + init_audio_recording(device) + end + if reinit_targets.video_stream_settings then + init_video_stream_settings(device) + end + if reinit_targets.camera_privacy_mode then + init_camera_privacy_mode(device) + end +end + +local function profile_capability_set(profile) + local capability_set = {} + for _, component in pairs((profile or {}).components or {}) do + for _, capability in ipairs(component.capabilities or {}) do + if capability.id ~= nil then + capability_set[capability.id] = true + end + end + end + return capability_set +end + +local function changed_capabilities_from_profiles(old_profile, new_profile) + local flags = {} + local old_set = profile_capability_set(old_profile) + local new_set = profile_capability_set(new_profile) + + for _, managed in ipairs(managed_capability_map) do + local id = managed.capability.ID + if old_set[id] ~= new_set[id] and new_set[id] == true then + flags[managed.key] = true + end + end + + return flags +end + +function CameraDeviceConfiguration.reconcile_profile_and_capabilities(device) + local profile_update_requested = CameraDeviceConfiguration.match_profile(device) + if not profile_update_requested then + local capabilities_to_reinit = capabilities_needing_reinit(device) + initialize_selected_camera_capabilities(device, capabilities_to_reinit) + end + return profile_update_requested +end + +function CameraDeviceConfiguration.update_status_light_attribute_presence(device, status_light_enabled_present, status_light_brightness_present) + set_status_light_presence(device, status_light_enabled_present, status_light_brightness_present) +end + +function CameraDeviceConfiguration.reinitialize_changed_camera_capabilities_and_subscriptions(device, old_profile, new_profile) + local changed_capabilities = changed_capabilities_from_profiles(old_profile, new_profile) + initialize_selected_camera_capabilities(device, changed_capabilities) + 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 + function CameraDeviceConfiguration.update_doorbell_component_map(device, ep) local component_map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {} component_map.doorbell = ep 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 index 000008fa51..c88f177707 100644 --- 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 @@ -14,6 +14,12 @@ CameraFields.MAX_RESOLUTION = "__max_resolution" CameraFields.MIN_RESOLUTION = "__min_resolution" CameraFields.TRIGGERED_ZONES = "__triggered_zones" CameraFields.DPTZ_VIEWPORTS = "__dptz_viewports" +CameraFields.STATUS_LIGHT_ENABLED_PRESENT = "__status_light_enabled_present" +CameraFields.STATUS_LIGHT_BRIGHTNESS_PRESENT = "__status_light_brightness_present" + +CameraFields.CameraAVSMFeatureMapAttr = { ID = 0xFFFC, cluster = clusters.CameraAvStreamManagement.ID } +CameraFields.CameraAVSULMFeatureMapAttr = { ID = 0xFFFC, cluster = clusters.CameraAvSettingsUserLevelManagement.ID } +CameraFields.ZoneManagementFeatureMapAttr = { ID = 0xFFFC, cluster = clusters.ZoneManagement.ID } CameraFields.PAN_IDX = "PAN" CameraFields.TILT_IDX = "TILT" 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 index 5793c1d9fc..f3b92aa3b6 100644 --- 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 @@ -6,6 +6,7 @@ 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 cluster_base = require "st.matter.cluster_base" local CameraUtils = {} @@ -297,10 +298,19 @@ function CameraUtils.subscribe(device) local subscribe_request = im.InteractionRequest(im.InteractionRequest.RequestType.SUBSCRIBE, {}) local devices_seen, capabilities_seen, attributes_seen, events_seen = {}, {}, {}, {} + local additional_attributes = {} 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) + table.insert(additional_attributes, clusters.CameraAvStreamManagement.attributes.AttributeList) + table.insert(additional_attributes, camera_fields.CameraAVSMFeatureMapAttr) + end + + if #device:get_endpoints(clusters.CameraAvSettingsUserLevelManagement.ID) > 0 then + table.insert(additional_attributes, camera_fields.CameraAVSULMFeatureMapAttr) + end + + if #device:get_endpoints(clusters.ZoneManagement.ID) > 0 then + table.insert(additional_attributes, camera_fields.ZoneManagementFeatureMapAttr) end for _, endpoint_info in ipairs(device.endpoints) do @@ -313,6 +323,17 @@ function CameraUtils.subscribe(device) end end + for _, attr in ipairs(additional_attributes) 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 + if #subscribe_request.info_blocks > 0 then device:send(subscribe_request) 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 index 1dbad05698..a72aa0b234 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -6,7 +6,6 @@ ------------------------------------------------------------------------------------- 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" @@ -33,7 +32,7 @@ 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) + camera_cfg.match_profile(device) end camera_cfg.create_child_devices(driver, device) camera_cfg.initialize_camera_capabilities(device) @@ -42,26 +41,19 @@ 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) + camera_cfg.match_profile(device) end end function CameraLifecycleHandlers.info_changed(driver, device, event, args) local software_version_changed = device.matter_version ~= nil and args.old_st_store.matter_version ~= nil and device.matter_version.software ~= args.old_st_store.matter_version.software - local profile_update_requested = false local profile_changed = not switch_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) if software_version_changed then - profile_update_requested = camera_cfg.match_profile(device, false, false) - end - - if profile_changed or (software_version_changed and not profile_update_requested) 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 + camera_cfg.reconcile_profile_and_capabilities(device) + elseif profile_changed then + camera_cfg.reinitialize_changed_camera_capabilities_and_subscriptions(device, args.old_st_store.profile, device.profile) end end @@ -107,7 +99,8 @@ local camera_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.CameraAvStreamManagement.attributes.AttributeList.ID] = attribute_handlers.camera_av_stream_management_attribute_list_handler, + [camera_fields.CameraAVSMFeatureMapAttr.ID] = attribute_handlers.camera_feature_map_handler }, [clusters.CameraAvSettingsUserLevelManagement.ID] = { [clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition.ID] = attribute_handlers.ptz_position_handler, @@ -118,14 +111,16 @@ local camera_handler = { [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.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams.ID] = attribute_handlers.dptz_streams_handler + [clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams.ID] = attribute_handlers.dptz_streams_handler, + [camera_fields.CameraAVSULMFeatureMapAttr.ID] = attribute_handlers.camera_feature_map_handler }, [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.ZoneManagement.attributes.Sensitivity.ID] = attribute_handlers.sensitivity_handler, + [camera_fields.ZoneManagementFeatureMapAttr.ID] = attribute_handlers.camera_feature_map_handler }, [clusters.Chime.ID] = { [clusters.Chime.attributes.InstalledChimeSounds.ID] = attribute_handlers.installed_chime_sounds_handler, diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index d248f585b5..7f1f72b108 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -2,7 +2,9 @@ -- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" +local cluster_base = require "st.matter.cluster_base" local clusters = require "st.matter.clusters" +local camera_fields = require "sub_drivers.camera.camera_utils.fields" local t_utils = require "integration_test.utils" local test = require "integration_test" local uint32 = require "st.matter.data_types.Uint32" @@ -154,6 +156,9 @@ local function test_init() parent_assigned_child_key = string.format("%d", FLOODLIGHT_EP) }) subscribe_request = subscribed_attributes[1]:subscribe(mock_device) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, camera_fields.CameraAVSMFeatureMapAttr.cluster, camera_fields.CameraAVSMFeatureMapAttr.ID)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, camera_fields.CameraAVSULMFeatureMapAttr.cluster, camera_fields.CameraAVSULMFeatureMapAttr.ID)) + subscribe_request:merge(cluster_base.subscribe(mock_device, nil, camera_fields.ZoneManagementFeatureMapAttr.cluster, camera_fields.ZoneManagementFeatureMapAttr.ID)) for i, attr in ipairs(subscribed_attributes) do if i > 1 then subscribe_request:merge(attr:subscribe(mock_device)) end end @@ -435,6 +440,7 @@ test.register_coroutine_test( } }, subscribe = function() subscribe_called = true end, + supports_capability = function() return false end, get_endpoints = function() return { DOORBELL_EP } end, } @@ -461,9 +467,36 @@ test.register_coroutine_test( button_cfg.configure_buttons = original_configure_buttons assert(match_profile_called, "match_profile should be called on software version change") - assert(init_called, "initialize_camera_capabilities should be called") - assert(subscribe_called, "subscribe should be called") - assert(configure_buttons_called, "configure_buttons should be called") + assert(not init_called, "initialize_camera_capabilities should not be called when capability state is unchanged") + assert(not subscribe_called, "subscribe should not be called when capability state is unchanged") + assert(not configure_buttons_called, "configure_buttons should not be called when capability state is unchanged") + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Camera FeatureMap change should reinitialize capabilities when profile is unchanged", + function() + local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" + + local reconcile_called = false + local original_reconcile = camera_cfg.reconcile_profile_and_capabilities + + camera_cfg.reconcile_profile_and_capabilities = function(_) + reconcile_called = true + return false + end + + test.socket.matter:__queue_receive({ + mock_device.id, + cluster_base.build_test_report_data(mock_device, CAMERA_EP, camera_fields.CameraAVSMFeatureMapAttr.cluster, camera_fields.CameraAVSMFeatureMapAttr.ID, uint32(0)) + }) + test.wait_for_events() + + camera_cfg.reconcile_profile_and_capabilities = original_reconcile + assert(reconcile_called, "reconcile_profile_and_capabilities should be called") end, { min_api_version = 17 @@ -2864,6 +2897,11 @@ test.register_coroutine_test( function() update_device_profile() test.wait_for_events() + + local camera_cfg = require("sub_drivers.camera.camera_utils.device_configuration") + local original_reconcile = camera_cfg.reconcile_profile_and_capabilities + camera_cfg.reconcile_profile_and_capabilities = function(...) return false end + test.socket.matter:__queue_receive({ mock_device.id, clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device, CAMERA_EP, { @@ -2871,6 +2909,9 @@ test.register_coroutine_test( uint32(clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) }) }) + test.wait_for_events() + + camera_cfg.reconcile_profile_and_capabilities = original_reconcile end, { min_api_version = 17 From 387d38483dc65d1f1472587c62d36ad6b994f08e Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Mon, 13 Apr 2026 11:54:26 -0500 Subject: [PATCH 239/538] Deep copy latest state, use `pairs` to handle map-like capability tables keyed by strings Co-authored-by: Harrison Carter --- .../camera_utils/device_configuration.lua | 39 +++++++-------- .../src/test/test_matter_camera.lua | 48 +++++++++++++++++++ 2 files changed, 68 insertions(+), 19 deletions(-) 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 index c2ea259aa1..068bee2344 100644 --- 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 @@ -6,6 +6,7 @@ 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 st_utils = require "st.utils" local device_cfg = require "switch_utils.device_configuration" local fields = require "switch_utils.fields" local switch_utils = require "switch_utils.utils" @@ -108,32 +109,34 @@ local function build_camera_privacy_supported_commands() end local function capabilities_needing_reinit(device) - local main = camera_fields.profile_components.main - local capabilities_to_reinit = {} - local function state_differs(capability, attribute_name, expected) - local current = device:get_latest_state(main, capability.ID, attribute_name) - return not switch_utils.deep_equals(current, expected, { ignore_functions = true }) + local function should_init(capability, attribute, expected) + if device:supports_capability(capability) then + local current = st_utils.deep_copy(device:get_latest_state( + camera_fields.profile_components.main, + capability.ID, + attribute.NAME, + {} + )) + return not switch_utils.deep_equals(current, expected) + end + return false end - if device:supports_capability(capabilities.webrtc) and - state_differs(capabilities.webrtc, capabilities.webrtc.supportedFeatures.NAME, build_webrtc_supported_features()) then + if should_init(capabilities.webrtc, capabilities.webrtc.supportedFeatures, build_webrtc_supported_features()) then capabilities_to_reinit.webrtc = true end - if device:supports_capability(capabilities.mechanicalPanTiltZoom) and - state_differs(capabilities.mechanicalPanTiltZoom, capabilities.mechanicalPanTiltZoom.supportedAttributes.NAME, build_ptz_supported_attributes(device)) then + if should_init(capabilities.mechanicalPanTiltZoom, capabilities.mechanicalPanTiltZoom.supportedAttributes, build_ptz_supported_attributes(device)) then capabilities_to_reinit.ptz = true end - if device:supports_capability(capabilities.zoneManagement) and - state_differs(capabilities.zoneManagement, capabilities.zoneManagement.supportedFeatures.NAME, build_zone_management_supported_features(device)) then + if should_init(capabilities.zoneManagement, capabilities.zoneManagement.supportedFeatures, build_zone_management_supported_features(device)) then capabilities_to_reinit.zone_management = true end - if device:supports_capability(capabilities.localMediaStorage) and - state_differs(capabilities.localMediaStorage, capabilities.localMediaStorage.supportedAttributes.NAME, build_local_media_storage_supported_attributes(device)) then + if should_init(capabilities.localMediaStorage, capabilities.localMediaStorage.supportedAttributes, build_local_media_storage_supported_attributes(device)) then capabilities_to_reinit.local_media_storage = true end @@ -144,14 +147,12 @@ local function capabilities_needing_reinit(device) end end - if device:supports_capability(capabilities.videoStreamSettings) and - state_differs(capabilities.videoStreamSettings, capabilities.videoStreamSettings.supportedFeatures.NAME, build_video_stream_settings_supported_features(device)) then + if should_init(capabilities.videoStreamSettings, capabilities.videoStreamSettings.supportedFeatures, build_video_stream_settings_supported_features(device)) then capabilities_to_reinit.video_stream_settings = true end - if device:supports_capability(capabilities.cameraPrivacyMode) and - (state_differs(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedAttributes.NAME, build_camera_privacy_supported_attributes()) or - state_differs(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedCommands.NAME, build_camera_privacy_supported_commands())) then + if should_init(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedAttributes, build_camera_privacy_supported_attributes()) or + should_init(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedCommands, build_camera_privacy_supported_commands()) then capabilities_to_reinit.camera_privacy_mode = true end @@ -407,7 +408,7 @@ end local function profile_capability_set(profile) local capability_set = {} for _, component in pairs((profile or {}).components or {}) do - for _, capability in ipairs(component.capabilities or {}) do + for _, capability in pairs(component.capabilities or {}) do if capability.id ~= nil then capability_set[capability.id] = true end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 7f1f72b108..fceaa4ccf5 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -503,6 +503,54 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Camera privacy mode state compare should ignore table metatable differences", + function() + local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" + + local init_event_count = 0 + local original_match_profile = camera_cfg.match_profile + + camera_cfg.match_profile = function() + return false + end + + local fake_device = { + supports_capability = function(_, capability) + return capability == capabilities.cameraPrivacyMode + end, + get_latest_state = function(_, _, _, attribute_name) + if attribute_name == capabilities.cameraPrivacyMode.supportedAttributes.NAME then + return { "softRecordingPrivacyMode", "softLivestreamPrivacyMode" } + elseif attribute_name == capabilities.cameraPrivacyMode.supportedCommands.NAME then + local commands = { "setSoftRecordingPrivacyMode", "setSoftLivestreamPrivacyMode" } + setmetatable(commands, { + __index = function() + return nil + end + }) + return commands + end + return nil + end, + get_endpoints = function() + return { CAMERA_EP } + end, + emit_event_for_endpoint = function() + init_event_count = init_event_count + 1 + end + } + + camera_cfg.reconcile_profile_and_capabilities(fake_device) + camera_cfg.match_profile = original_match_profile + + assert(init_event_count == 0, "cameraPrivacyMode should not be reinitialized for equal values with metatable differences") + end, + { + min_api_version = 17 + } +) + test.register_coroutine_test( "Reports mapping to EnabledState capability data type should generate appropriate events", function() From c934a217b6add2697af7362b3ae2a1530f21ce00 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Mon, 13 Apr 2026 12:00:16 -0500 Subject: [PATCH 240/538] Fix luacheck lints --- .../camera/camera_utils/device_configuration.lua | 6 +++++- .../src/sub_drivers/camera/camera_utils/utils.lua | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) 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 index 068bee2344..a31473f993 100644 --- 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 @@ -141,7 +141,11 @@ local function capabilities_needing_reinit(device) end if device:supports_capability(capabilities.audioRecording) then - local audio_enabled_state = device:get_latest_state(main, capabilities.audioRecording.ID, capabilities.audioRecording.audioRecording.NAME) + 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 capabilities_to_reinit.audio_recording = true end 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 index f3b92aa3b6..78792b94b3 100644 --- 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 @@ -6,7 +6,6 @@ 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 cluster_base = require "st.matter.cluster_base" local CameraUtils = {} From cd95815b6c1b007611ac6925bb1b13fce4f3af60 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Tue, 14 Apr 2026 11:43:52 -0500 Subject: [PATCH 241/538] Remove unused init function Co-authored-by: Nick DeBoom --- .../camera/camera_utils/device_configuration.lua | 8 -------- 1 file changed, 8 deletions(-) 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 index a31473f993..398150d063 100644 --- 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 @@ -375,14 +375,6 @@ function CameraDeviceConfiguration.initialize_camera_capabilities(device) init_camera_privacy_mode(device) end -function CameraDeviceConfiguration.initialize_camera_capabilities_and_subscriptions(device) - CameraDeviceConfiguration.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 - local function initialize_selected_camera_capabilities(device, capabilities_to_reinit) local reinit_targets = capabilities_to_reinit or {} From 1c1d98f3a6cc4f83fbb937ea1bdedcab05a9c1d8 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Mon, 13 Apr 2026 14:45:09 -0500 Subject: [PATCH 242/538] add nil check handling for electrical sensor handlers --- .../src/switch_handlers/attribute_handlers.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index b16610cf25..20bd92881e 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -321,6 +321,10 @@ function AttributeHandlers.available_endpoints_handler(driver, device, ib, respo return end local set_topology_eps = device:get_field(fields.ELECTRICAL_SENSOR_EPS) + if set_topology_eps == nil then + device.log.warn("Received an AvailableEndpoints response but no Electrical Sensor endpoints have been identified as supporting the Power Topology cluster with SET feature. Ignoring this response.") + return + end for i, set_ep_info in pairs(set_topology_eps or {}) do if ib.endpoint_id == set_ep_info.endpoint_id then -- since EP response is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table @@ -350,6 +354,10 @@ function AttributeHandlers.parts_list_handler(driver, device, ib, response) return end local tree_topology_eps = device:get_field(fields.ELECTRICAL_SENSOR_EPS) + if tree_topology_eps == nil then + device.log.warn("Received a PartsList response but no Electrical Sensor endpoints have been identified as supporting the Power Topology cluster with TREE feature. Ignoring this response.") + return + end for i, tree_ep_info in pairs(tree_topology_eps or {}) do if ib.endpoint_id == tree_ep_info.endpoint_id then -- since EP response is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table From 24f06e81cc6b848bf7f44ca4693666d59d5e1a66 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Mon, 13 Apr 2026 17:14:00 -0500 Subject: [PATCH 243/538] use fan-modular profile as device fingerprint --- drivers/SmartThings/matter-switch/fingerprints.yml | 2 +- .../matter-switch/profiles/light-color-level-fan.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 852a67432d..73f5cbd3d7 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -155,7 +155,7 @@ matterManufacturer: deviceLabel: OREIN Bath Fan OLO5S vendorId: 0x1396 productId: 0x1001 - deviceProfileName: light-color-level-fan + deviceProfileName: fan-modular - id: "5014/4214" deviceLabel: Linkind Smart Light Bulb vendorId: 0x1396 diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-fan.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-fan.yml index 2f91bcb04e..1b1129e8be 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-color-level-fan.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-fan.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: light-color-level-fan components: - id: main From 4b078efacde5cc72344db37577eb65f6b51c2681 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 16 Apr 2026 11:52:44 -0500 Subject: [PATCH 244/538] WWSTCERT-11013 SMART WIFI MATTER WALL SWITCH 2G (#2905) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 73f5cbd3d7..d92329d604 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -894,6 +894,11 @@ matterManufacturer: vendorId: 0x1189 productId: 0x0633 deviceProfileName: plug-binary + - id: "4489/2193" + deviceLabel: SMART WIFI MATTER WALL SWITCH 2G + vendorId: 0x1189 + productId: 0x0891 + deviceProfileName: switch-binary #Ikea - id: "4476/32768" deviceLabel: BILRESA Scroll Wheel From a9d1692e38829063ebb43e50d0d3b19b84972754 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:38:02 +0800 Subject: [PATCH 245/538] add MultiIR Smoke Detector MIR-SM200 (#2874) Co-authored-by: Carter Swedal --- .../zigbee-smoke-detector/fingerprints.yml | 5 + .../smoke-battery-tamper-no-fw-update.yml | 14 ++ .../src/MultiIR/can_handle.lua | 13 ++ .../src/MultiIR/fingerprints.lua | 6 + .../src/MultiIR/init.lua | 53 +++++ .../zigbee-smoke-detector/src/sub_drivers.lua | 1 + .../src/test/test_multiir_smoke_detector.lua | 195 ++++++++++++++++++ tools/localizations/cn.csv | 1 + 8 files changed, 288 insertions(+) create mode 100644 drivers/SmartThings/zigbee-smoke-detector/profiles/smoke-battery-tamper-no-fw-update.yml create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/init.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/test/test_multiir_smoke_detector.lua diff --git a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml index ddd1135cbd..c1f859037b 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml +++ b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml @@ -54,3 +54,8 @@ zigbeeManufacturer: manufacturer: HEIMAN model: GASSensor-N deviceProfileName: smoke-detector + - id: "MultIR/MIR-SM200" + deviceLabel: MultiIR Smoke Detector MIR-SM200 + manufacturer: MultIR + model: MIR-SM200 + deviceProfileName: smoke-battery-tamper-no-fw-update diff --git a/drivers/SmartThings/zigbee-smoke-detector/profiles/smoke-battery-tamper-no-fw-update.yml b/drivers/SmartThings/zigbee-smoke-detector/profiles/smoke-battery-tamper-no-fw-update.yml new file mode 100644 index 0000000000..eb66ce92c4 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/profiles/smoke-battery-tamper-no-fw-update.yml @@ -0,0 +1,14 @@ +name: smoke-battery-tamper-no-fw-update +components: +- id: main + capabilities: + - id: smokeDetector + version: 1 + - id: tamperAlert + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: SmokeDetector diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/can_handle.lua b/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/can_handle.lua new file mode 100644 index 0000000000..d389619c78 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require "MultiIR.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("MultiIR") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/fingerprints.lua b/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/fingerprints.lua new file mode 100644 index 0000000000..3d845bdf7e --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "MultIR", model = "MIR-SM200" } +} diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/init.lua new file mode 100644 index 0000000000..b382a56cec --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/init.lua @@ -0,0 +1,53 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local zcl_clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" + +local IASZone = zcl_clusters.IASZone + +local function generate_event_from_zone_status(driver, device, zone_status, zb_rx) + if zone_status:is_alarm1_set() then + device:emit_event(capabilities.smokeDetector.smoke.detected()) + elseif zone_status:is_alarm2_set() then + device:emit_event(capabilities.smokeDetector.smoke.tested()) + else + device:emit_event(capabilities.smokeDetector.smoke.clear()) + end + if device:supports_capability(capabilities.tamperAlert) then + device:emit_event(zone_status:is_tamper_set() and capabilities.tamperAlert.tamper.detected() or capabilities.tamperAlert.tamper.clear()) + end +end + +local function ias_zone_status_change_handler(driver, device, zb_rx) + local zone_status = zb_rx.body.zcl_body.zone_status + generate_event_from_zone_status(driver, device, zone_status, zb_rx) +end + +local function added_handler(self, device) + device:emit_event(capabilities.battery.battery(100)) + device:emit_event(capabilities.smokeDetector.smoke.clear()) + device:emit_event(capabilities.tamperAlert.tamper.clear()) +end + +local MultiIR_smoke_detector_handler = { + NAME = "MultiIR Smoke Detector Handler", + lifecycle_handlers = { + added = added_handler + }, + zigbee_handlers = { + cluster = { + [IASZone.ID] = { + [IASZone.client.commands.ZoneStatusChangeNotification.ID] = ias_zone_status_change_handler + } + }, + attr = { + [IASZone.ID] = { + [IASZone.attributes.ZoneStatus.ID] = generate_event_from_zone_status + } + } + }, + can_handle = require("MultiIR.can_handle") +} + +return MultiIR_smoke_detector_handler diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua b/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua index e6c5e004f3..2b917b85dc 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua @@ -6,5 +6,6 @@ local sub_drivers = { lazy_load_if_possible("frient"), lazy_load_if_possible("aqara-gas"), lazy_load_if_possible("aqara"), + lazy_load_if_possible("MultiIR"), } return sub_drivers diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_multiir_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_multiir_smoke_detector.lua new file mode 100644 index 0000000000..e79df95f01 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_multiir_smoke_detector.lua @@ -0,0 +1,195 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local IASZone = clusters.IASZone + +local mock_device = test.mock_device.build_test_zigbee_device( + { profile = t_utils.get_profile_definition("smoke-battery-tamper-no-fw-update.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "MultIR", + model = "MIR-SM200", + server_clusters = { 0x0001,0x0020, 0x0500, 0x0502 } + } + } + } +) + +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( + "Handle added lifecycle", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__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", + capabilities.battery.battery(100))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.smokeDetector.smoke.clear())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tamperAlert.tamper.clear())) + end, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported ZoneStatus should be handled: smoke/clear tamper/clear", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported ZoneStatus should be handled: smoke/detected tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0005) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported ZoneStatus should be handled: smoke/tested tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0006) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.tested()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: smoke/detected tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0005, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: smoke/tested tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0006, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.tested()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: smoke/clear tamper/clear", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + }, + { + min_api_version = 19 + } +) + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index c03098c37f..faeee2a3f7 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -134,3 +134,4 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ "HAOJAI Smart Switch 3-key",好家智能三键开关 "HAOJAI Smart Switch 6-key",好家智能六键开关 +"MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 From a910034561184990c5769e32c01e45000490584c Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:55:52 +0800 Subject: [PATCH 246/538] add MultiIR Smart button MIR-SO100 (#2862) Co-authored-by: Carter Swedal Co-authored-by: Chris Baumler --- .../zigbee-button/fingerprints.yml | 5 + .../one-button-battery-no-fw-update.yml | 12 ++ .../zigbee-button/src/MultiIR/can_handle.lua | 13 ++ .../src/MultiIR/fingerprints.lua | 6 + .../zigbee-button/src/MultiIR/init.lua | 37 +++++ .../zigbee-button/src/sub_drivers.lua | 1 + .../src/test/test_multiir_smart_button.lua | 131 ++++++++++++++++++ tools/localizations/cn.csv | 1 + 8 files changed, 206 insertions(+) create mode 100644 drivers/SmartThings/zigbee-button/profiles/one-button-battery-no-fw-update.yml create mode 100644 drivers/SmartThings/zigbee-button/src/MultiIR/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/MultiIR/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/MultiIR/init.lua create mode 100644 drivers/SmartThings/zigbee-button/src/test/test_multiir_smart_button.lua diff --git a/drivers/SmartThings/zigbee-button/fingerprints.yml b/drivers/SmartThings/zigbee-button/fingerprints.yml index f256e0584c..09e915321b 100644 --- a/drivers/SmartThings/zigbee-button/fingerprints.yml +++ b/drivers/SmartThings/zigbee-button/fingerprints.yml @@ -267,6 +267,11 @@ zigbeeManufacturer: manufacturer: WALL HERO model: ACL-401SCA4 deviceProfileName: thirty-buttons + - id: "MultIR/MIR-SO100" + deviceLabel: MultiIR Smart button MIR-SO100 + manufacturer: MultIR + model: MIR-SO100 + deviceProfileName: one-button-battery-no-fw-update zigbeeGeneric: - id: "generic-button-sensor" deviceLabel: "Zigbee Generic Button" diff --git a/drivers/SmartThings/zigbee-button/profiles/one-button-battery-no-fw-update.yml b/drivers/SmartThings/zigbee-button/profiles/one-button-battery-no-fw-update.yml new file mode 100644 index 0000000000..98175a88ea --- /dev/null +++ b/drivers/SmartThings/zigbee-button/profiles/one-button-battery-no-fw-update.yml @@ -0,0 +1,12 @@ +name: one-button-battery-no-fw-update +components: + - id: main + capabilities: + - id: button + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: Button diff --git a/drivers/SmartThings/zigbee-button/src/MultiIR/can_handle.lua b/drivers/SmartThings/zigbee-button/src/MultiIR/can_handle.lua new file mode 100644 index 0000000000..d389619c78 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/MultiIR/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require "MultiIR.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("MultiIR") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-button/src/MultiIR/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/MultiIR/fingerprints.lua new file mode 100644 index 0000000000..f9e21e49b9 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/MultiIR/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "MultIR", model = "MIR-SO100" } +} diff --git a/drivers/SmartThings/zigbee-button/src/MultiIR/init.lua b/drivers/SmartThings/zigbee-button/src/MultiIR/init.lua new file mode 100644 index 0000000000..06d6d98f7e --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/MultiIR/init.lua @@ -0,0 +1,37 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local zcl_clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local log = require "log" + +local IASZone = zcl_clusters.IASZone +local PRIVATE_CMD_ID = 0xF1 + +local function ias_zone_private_cmd_handler(self, device, zb_rx) + local cmd_data = zb_rx.body.zcl_body.body_bytes:byte(1) + if cmd_data == 0 then + device:emit_event(capabilities.button.button.pushed({state_change = true})) + elseif cmd_data == 1 then + device:emit_event(capabilities.button.button.double({state_change = true})) + elseif cmd_data == 0x80 then + device:emit_event(capabilities.button.button.held({state_change = true})) + else + log.info("ias_zone_private_cmd Unknown value",zb_rx.body.zcl_body.body_bytes:byte(1)) + end +end + +local MultiIR_Emergency_Button = { + NAME = "MultiIR Emergency Button", + zigbee_handlers = { + cluster = { + [IASZone.ID] = { + [PRIVATE_CMD_ID] = ias_zone_private_cmd_handler + } + } + }, + can_handle = require("MultiIR.can_handle") +} + +return MultiIR_Emergency_Button diff --git a/drivers/SmartThings/zigbee-button/src/sub_drivers.lua b/drivers/SmartThings/zigbee-button/src/sub_drivers.lua index bec1f76ac1..8aa36d9ba0 100644 --- a/drivers/SmartThings/zigbee-button/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-button/src/sub_drivers.lua @@ -14,5 +14,6 @@ local sub_drivers = { lazy_load_if_possible("ewelink"), lazy_load_if_possible("thirdreality"), lazy_load_if_possible("ezviz"), + lazy_load_if_possible("MultiIR"), } return sub_drivers diff --git a/drivers/SmartThings/zigbee-button/src/test/test_multiir_smart_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_multiir_smart_button.lua new file mode 100644 index 0000000000..84b974e988 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/test/test_multiir_smart_button.lua @@ -0,0 +1,131 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Mock out globals +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local t_utils = require "integration_test.utils" +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local IASZone = clusters.IASZone +local PRIVATE_CMD_ID = 0xF1 + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("one-button-battery-no-fw-update.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "MultIR", + model = "MIR-SO100", + server_clusters = {0x0000, 0x0001, 0x0003, 0x0020, 0x0500, 0x0B05} + } + } + } +) + +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( + "added lifecycle event", + function() + -- The initial button pushed event should be send during the device's first time onboarding + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.button.supportedButtonValues({ "pushed","held","double" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send({ + mock_device.id, + { + capability_id = "button", component_id = "main", + attribute_id = "button", state = { value = "pushed" } + } + }) + -- 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.id, "added" }) + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.button.supportedButtonValues({ "pushed","held","double" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }) + ) + ) + end, + { + min_api_version = 19 + } +) + +test.register_message_test( + "IASZone cmd 0xF1 0x00 are handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, IASZone.ID, PRIVATE_CMD_ID, 0x0000, "\x00", 0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) + } + } +) + +test.register_message_test( + "IASZone cmd 0xF1 0x01 are handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, IASZone.ID, PRIVATE_CMD_ID, 0x0000, "\x01", 0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.button.button.double({state_change = true})) + } + } +) + +test.register_message_test( + "IASZone cmd 0xF1 0x80 are handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, IASZone.ID, PRIVATE_CMD_ID, 0x0000, "\x80", 0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.button.button.held({state_change = true})) + } + } +) + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index faeee2a3f7..8b15479879 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -134,4 +134,5 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ "HAOJAI Smart Switch 3-key",好家智能三键开关 "HAOJAI Smart Switch 6-key",好家智能六键开关 +"MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 From 123bfc69d8125c4f4a0896d8b214f9754bf83e6a Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:44:34 +0800 Subject: [PATCH 247/538] add MultiIR contact sensor MIR_MC100 (#2867) Co-authored-by: Chris Baumler --- .../zigbee-contact/fingerprints.yml | 5 + .../contact-battery-tamper-no-fw-update.yml | 14 ++ .../zigbee-contact/src/MultiIR/can_handle.lua | 13 ++ .../src/MultiIR/fingerprints.lua | 6 + .../zigbee-contact/src/MultiIR/init.lua | 52 +++++++ .../zigbee-contact/src/sub_drivers.lua | 1 + .../src/test/test_multiir_contact_tamper.lua | 147 ++++++++++++++++++ tools/localizations/cn.csv | 1 + 8 files changed, 239 insertions(+) create mode 100644 drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper-no-fw-update.yml create mode 100644 drivers/SmartThings/zigbee-contact/src/MultiIR/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/MultiIR/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/test/test_multiir_contact_tamper.lua diff --git a/drivers/SmartThings/zigbee-contact/fingerprints.yml b/drivers/SmartThings/zigbee-contact/fingerprints.yml index dd4bb9175c..b3eaa9ca95 100644 --- a/drivers/SmartThings/zigbee-contact/fingerprints.yml +++ b/drivers/SmartThings/zigbee-contact/fingerprints.yml @@ -219,6 +219,11 @@ zigbeeManufacturer: manufacturer: Aug. Winkhaus SE model: FM.V.ZB deviceProfileName: contact-battery-profile + - id: "MultIR/MIR-MC100" + deviceLabel: MultiIR Contact Sensor MIR-MC100 + manufacturer: MultIR + model: MIR-MC100 + deviceProfileName: contact-battery-tamper-no-fw-update zigbeeGeneric: - id: "contact-generic" deviceLabel: "Zigbee Contact Sensor" diff --git a/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper-no-fw-update.yml b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper-no-fw-update.yml new file mode 100644 index 0000000000..528e6c0021 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper-no-fw-update.yml @@ -0,0 +1,14 @@ +name: contact-battery-tamper-no-fw-update +components: +- id: main + capabilities: + - id: contactSensor + version: 1 + - id: battery + version: 1 + - id: tamperAlert + version: 1 + - id: refresh + version: 1 + categories: + - name: ContactSensor diff --git a/drivers/SmartThings/zigbee-contact/src/MultiIR/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/MultiIR/can_handle.lua new file mode 100644 index 0000000000..d389619c78 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/MultiIR/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require "MultiIR.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("MultiIR") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-contact/src/MultiIR/fingerprints.lua b/drivers/SmartThings/zigbee-contact/src/MultiIR/fingerprints.lua new file mode 100644 index 0000000000..4bda1794f0 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/MultiIR/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "MultIR", model = "MIR-MC100" } +} diff --git a/drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua b/drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua new file mode 100644 index 0000000000..1481bb01fc --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua @@ -0,0 +1,52 @@ +-- Copyright 2026 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 + +local function generate_event_from_zone_status(driver, device, zone_status, zb_rx) + device:emit_event(zone_status:is_alarm1_set() and capabilities.contactSensor.contact.open() or capabilities.contactSensor.contact.closed()) + if device:supports_capability_by_id(capabilities.tamperAlert.ID) then + device:emit_event(zone_status:is_tamper_set() and capabilities.tamperAlert.tamper.detected() or capabilities.tamperAlert.tamper.clear()) + end +end + +local function ias_zone_status_attr_handler(driver, device, attr_val, zb_rx) + generate_event_from_zone_status(driver, device, attr_val, zb_rx) +end + +local function ias_zone_status_change_handler(driver, device, zb_rx) + generate_event_from_zone_status(driver, device, zb_rx.body.zcl_body.zone_status, zb_rx) +end + +local function added_handler(driver, device) + device:emit_event(capabilities.battery.battery(100)) + device:emit_event(capabilities.contactSensor.contact.closed()) + if device:supports_capability_by_id(capabilities.tamperAlert.ID) then + device:emit_event(capabilities.tamperAlert.tamper.clear()) + end +end + +local MultiIR_sensor = { + NAME = "MultiIR Contact Sensor", + lifecycle_handlers = { + added = added_handler + }, + zigbee_handlers = { + cluster = { + [IASZone.ID] = { + [IASZone.client.commands.ZoneStatusChangeNotification.ID] = ias_zone_status_change_handler, + } + }, + attr = { + [IASZone.ID] = { + [IASZone.attributes.ZoneStatus.ID] = ias_zone_status_attr_handler, + } + } + }, + can_handle = require("MultiIR.can_handle") +} + +return MultiIR_sensor diff --git a/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua b/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua index 394de5a72d..8a4977ca5e 100644 --- a/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua @@ -10,5 +10,6 @@ local sub_drivers = { lazy_load_if_possible("smartsense-multi"), lazy_load_if_possible("sengled"), lazy_load_if_possible("frient"), + lazy_load_if_possible("MultiIR"), } return sub_drivers diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_multiir_contact_tamper.lua b/drivers/SmartThings/zigbee-contact/src/test/test_multiir_contact_tamper.lua new file mode 100644 index 0000000000..123b9df58c --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/test/test_multiir_contact_tamper.lua @@ -0,0 +1,147 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local IASZone = clusters.IASZone + +local mock_device = test.mock_device.build_test_zigbee_device( + { profile = t_utils.get_profile_definition("contact-battery-tamper-no-fw-update.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "MultIR", + model = "MIR-MC100", + server_clusters = { 0x0001, 0x0500 } + } + } + } +) + +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( + "Handle added lifecycle", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__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", + capabilities.battery.battery(100))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.contactSensor.contact.closed())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tamperAlert.tamper.clear())) + end, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported ZoneStatus should be handled: contact/closed tamper/clear", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported ZoneStatus should be handled: contact/open tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0005) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: contact/open tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0005, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: contact/closed tamper/clear", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + }, + { + min_api_version = 19 + } +) + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 8b15479879..04b3bb6f51 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -134,5 +134,6 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ "HAOJAI Smart Switch 3-key",好家智能三键开关 "HAOJAI Smart Switch 6-key",好家智能六键开关 +"MultiIR Contact Sensor MIR-MC100",麦乐克门窗开关传感器MIR-MC100 "MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 From f865fd9699bbcb2f24bae4e1ed6b4544aeb08454 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:48:51 +0800 Subject: [PATCH 248/538] add MultiIR Water Leak MIR-WA100 (#2896) Co-authored-by: Chris Baumler --- .../zigbee-water-leak-sensor/fingerprints.yml | 7 ++++++- .../profiles/water-battery-no-fw-update.yml | 12 ++++++++++++ tools/localizations/cn.csv | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/profiles/water-battery-no-fw-update.yml diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/fingerprints.yml b/drivers/SmartThings/zigbee-water-leak-sensor/fingerprints.yml index 5b1c1e4977..e7007de4f7 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/fingerprints.yml +++ b/drivers/SmartThings/zigbee-water-leak-sensor/fingerprints.yml @@ -113,4 +113,9 @@ zigbeeManufacturer: deviceLabel: NEO Water Leak Sensor manufacturer: NEO model: NAS_WS11 - deviceProfileName: water-battery \ No newline at end of file + deviceProfileName: water-battery + - id: MultIR/MIR-WA100 + deviceLabel: MultiIR Water Leak Sensor MIR-WA100 + manufacturer: MultIR + model: MIR-WA100 + deviceProfileName: water-battery-no-fw-update diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/profiles/water-battery-no-fw-update.yml b/drivers/SmartThings/zigbee-water-leak-sensor/profiles/water-battery-no-fw-update.yml new file mode 100644 index 0000000000..03a891c23b --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/profiles/water-battery-no-fw-update.yml @@ -0,0 +1,12 @@ +name: water-battery-no-fw-update +components: +- id: main + capabilities: + - id: waterSensor + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: LeakSensor diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 04b3bb6f51..22a1512d50 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -134,6 +134,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ "HAOJAI Smart Switch 3-key",好家智能三键开关 "HAOJAI Smart Switch 6-key",好家智能六键开关 +"MultiIR Water Leak Sensor MIR-WA100",麦乐克水浸传感器MIR-WA100 "MultiIR Contact Sensor MIR-MC100",麦乐克门窗开关传感器MIR-MC100 "MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 From ce9777bcdb85839a2685ee467954ac0779a3f01b Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 21 Apr 2026 10:49:50 -0500 Subject: [PATCH 249/538] WWSTCERT-10993 Linkind Smart Ceiling Light (#2901) --- drivers/SmartThings/matter-switch/fingerprints.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index d92329d604..a913b477aa 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -161,6 +161,11 @@ matterManufacturer: vendorId: 0x1396 productId: 0x1076 deviceProfileName: light-color-level + - id: "5014/4245" + deviceLabel: OREiN Matter smart Bathroom Fan + vendorId: 0x1396 + productId: 0x1095 + deviceProfileName: fan-modular - id: "5014/4246" deviceLabel: OREiN Matter smart Bathroom Fan vendorId: 0x1396 @@ -176,6 +181,11 @@ matterManufacturer: vendorId: 0x1396 productId: 0x1077 deviceProfileName: light-color-level + - id: "5014/4273" + deviceLabel: Linkind Smart Ceiling Light + vendorId: 0x1396 + productId: 0x10B1 + deviceProfileName: light-level-colorTemperature #Bosch Smart Home - id: "4617/12310" deviceLabel: Plug Compact [M] From a1104ecd99505e21eaf7d4988c9bd100c506e53d Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:57:03 +0800 Subject: [PATCH 250/538] add multiir_motion_MIR-IR100 (#2861) Co-authored-by: Carter Swedal Co-authored-by: Chris Baumler --- .../zigbee-motion-sensor/fingerprints.yml | 5 + ...nce-sensitivity-frequency-no-fw-update.yml | 26 ++ .../src/MultiIR/can_handle.lua | 13 + .../src/MultiIR/fingerprints.lua | 6 + .../zigbee-motion-sensor/src/MultiIR/init.lua | 117 +++++++++ .../zigbee-motion-sensor/src/init.lua | 1 + .../src/test/test_multiir_motion_pir.lua | 224 ++++++++++++++++++ tools/localizations/cn.csv | 1 + 8 files changed, 393 insertions(+) create mode 100644 drivers/SmartThings/zigbee-motion-sensor/profiles/motion-battery-illuminance-sensitivity-frequency-no-fw-update.yml create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/init.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/test/test_multiir_motion_pir.lua diff --git a/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml b/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml index e71ab0c5ac..89d3df1586 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml +++ b/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml @@ -209,6 +209,11 @@ zigbeeManufacturer: manufacturer: sengled model: E1M-G7H deviceProfileName: motion-battery + - id: "MultIR/MIR-IR100" + deviceLabel: MultiIR Motion Detector MIR-IR100 + manufacturer: MultIR + model: MIR-IR100 + deviceProfileName: motion-battery-illuminance-sensitivity-frequency-no-fw-update zigbeeGeneric: - id: kickstarter/motion/1 deviceLabel: SmartThings Motion Sensor diff --git a/drivers/SmartThings/zigbee-motion-sensor/profiles/motion-battery-illuminance-sensitivity-frequency-no-fw-update.yml b/drivers/SmartThings/zigbee-motion-sensor/profiles/motion-battery-illuminance-sensitivity-frequency-no-fw-update.yml new file mode 100644 index 0000000000..5fc7d7ea8b --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/profiles/motion-battery-illuminance-sensitivity-frequency-no-fw-update.yml @@ -0,0 +1,26 @@ +name: motion-battery-illuminance-sensitivity-frequency-no-fw-update +components: + - id: main + capabilities: + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + - id: stse.sensitivityAdjustment + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: MotionSensor +preferences: + - title: "检测频率/秒(detection frequency/sec)" + name: detectionfrequency + description: "传感器检测频率(Sensor detects frequency unit: seconds)" + required: false + preferenceType: integer + definition: + minimum: 10 + maximum: 1800 + default: 60 diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/can_handle.lua new file mode 100644 index 0000000000..d389619c78 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require "MultiIR.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("MultiIR") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/fingerprints.lua b/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/fingerprints.lua new file mode 100644 index 0000000000..139d3aa054 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "MultIR", model = "MIR-IR100" } +} diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/init.lua new file mode 100644 index 0000000000..c3c47753e1 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/init.lua @@ -0,0 +1,117 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local zcl_clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local data_types = require "st.zigbee.data_types" +local FrameCtrl = require "st.zigbee.zcl.frame_ctrl" +local zcl_messages = require "st.zigbee.zcl" +local messages = require "st.zigbee.messages" +local zb_const = require "st.zigbee.constants" + +local sensitivityAdjustment = capabilities["stse.sensitivityAdjustment"] +local sensitivityAdjustmentCommandName = "setSensitivityAdjustment" +local IASZone = zcl_clusters.IASZone +local IASZone_PRIVATE_COMMAND_ID = 0xF4 + +local PREF_SENSITIVITY_VALUE_HIGH = 3 +local PREF_SENSITIVITY_VALUE_MEDIUM = 2 +local PREF_SENSITIVITY_VALUE_LOW = 1 + +local function send_iaszone_private_cmd(device, priv_cmd, data) + local frame_ctrl = FrameCtrl(0x00) + frame_ctrl:set_cluster_specific() + + local zclh = zcl_messages.ZclHeader({ + frame_ctrl = frame_ctrl, + cmd = data_types.ZCLCommandId(priv_cmd) + }) + + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zclh, + zcl_body = data_types.Uint16(data) + }) + + local addr_header = messages.AddressHeader( + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + device:get_short_address(), + device:get_endpoint(IASZone.ID), + zb_const.HA_PROFILE_ID, + IASZone.ID + ) + + local zigbee_msg = messages.ZigbeeMessageTx({ + address_header = addr_header, + body = message_body + }) + + device:send(zigbee_msg) +end + +local function iaszone_attr_sen_handler(driver, device, value, zb_rx) + if value.value == PREF_SENSITIVITY_VALUE_HIGH then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.High()) + elseif value.value == PREF_SENSITIVITY_VALUE_MEDIUM then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.Medium()) + elseif value.value == PREF_SENSITIVITY_VALUE_LOW then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.Low()) + end +end + +local function send_sensitivity_adjustment_value(device, value) + device:send(IASZone.attributes.CurrentZoneSensitivityLevel:write(device, value)) +end + +local function sensitivity_adjustment_capability_handler(driver, device, command) + local sensitivity = command.args.sensitivity + if sensitivity == 'High' then + send_sensitivity_adjustment_value(device, PREF_SENSITIVITY_VALUE_HIGH) + elseif sensitivity == 'Medium' then + send_sensitivity_adjustment_value(device, PREF_SENSITIVITY_VALUE_MEDIUM) + elseif sensitivity == 'Low' then + send_sensitivity_adjustment_value(device, PREF_SENSITIVITY_VALUE_LOW) + end + device:send(IASZone.attributes.CurrentZoneSensitivityLevel:read(device)) +end + +local function added_handler(self, device) + device:emit_event(capabilities.motionSensor.motion.inactive()) + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.High()) + device:emit_event(capabilities.battery.battery(100)) + device:send(IASZone.attributes.CurrentZoneSensitivityLevel:read(device)) +end + +local function info_changed(driver, device, event, args) + for name, value in pairs(device.preferences) do + if (device.preferences[name] ~= nil and args.old_st_store.preferences[name] ~= device.preferences[name]) then + if (name == "detectionfrequency") then + local detectionfrequency = tonumber(device.preferences.detectionfrequency) + send_iaszone_private_cmd(device, IASZone_PRIVATE_COMMAND_ID, detectionfrequency) + end + end + end +end + +local MultiIR_motion_handler = { + NAME = "MultiIR motion handler", + lifecycle_handlers = { + added = added_handler, + infoChanged = info_changed + }, + capability_handlers = { + [sensitivityAdjustment.ID] = { + [sensitivityAdjustmentCommandName] = sensitivity_adjustment_capability_handler, + } + }, + zigbee_handlers = { + attr = { + [IASZone.ID] = { + [IASZone.attributes.CurrentZoneSensitivityLevel.ID] = iaszone_attr_sen_handler + } + } + }, + can_handle = require("MultiIR.can_handle") +} + +return MultiIR_motion_handler diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/init.lua index 11e9d94a85..660948720b 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/init.lua @@ -118,6 +118,7 @@ local zigbee_motion_driver = { lazy_load_if_possible("smartsense"), lazy_load_if_possible("thirdreality"), lazy_load_if_possible("sengled"), + lazy_load_if_possible("MultiIR"), }, additional_zcl_profiles = { [0xFC01] = true diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_multiir_motion_pir.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_multiir_motion_pir.lua new file mode 100644 index 0000000000..357190c730 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_multiir_motion_pir.lua @@ -0,0 +1,224 @@ +-- Copyright 2026 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 data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local FrameCtrl = require "st.zigbee.zcl.frame_ctrl" + +--If this line is removed, an error will occur. +test.add_package_capability("sensitivityAdjustment.yaml") + +local sensitivityAdjustment = capabilities["stse.sensitivityAdjustment"] +local IASZone = clusters.IASZone +local PowerConfiguration = clusters.PowerConfiguration + +local PREF_SENSITIVITY_VALUE_HIGH = 3 +local PREF_SENSITIVITY_VALUE_MEDIUM = 2 +local PREF_SENSITIVITY_VALUE_LOW = 1 +local IASZone_PRIVATE_COMMAND_ID = 0xF4 + +-- Needed for building iaszone_private_cmd msg +local messages = require "st.zigbee.messages" +local zb_const = require "st.zigbee.constants" + +local zcl_messages = require "st.zigbee.zcl" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("motion-battery-illuminance-sensitivity-frequency-no-fw-update.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "MultIR", + model = "MIR-IR100", + server_clusters = { PowerConfiguration.ID ,IASZone.ID} + } + } + } +) + +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( + "Handle added lifecycle", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__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", + capabilities.motionSensor.motion.inactive())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + sensitivityAdjustment.sensitivityAdjustment.High())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.battery.battery(100))) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:read(mock_device)}) + end, + { + min_api_version = 19 + } +) + +local function build_iaszone_private_cmd(device, priv_cmd, data) + local frame_ctrl = FrameCtrl(0x00) + frame_ctrl:set_cluster_specific() + + local zclh = zcl_messages.ZclHeader({ + frame_ctrl = frame_ctrl, + cmd = data_types.ZCLCommandId(priv_cmd) + }) + + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zclh, + zcl_body = data_types.Uint16(data) + }) + + local addr_header = messages.AddressHeader( + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + device:get_short_address(), + device:get_endpoint(IASZone.ID), + zb_const.HA_PROFILE_ID, + IASZone.ID + ) + + local msg = messages.ZigbeeMessageTx({ + address_header = addr_header, + body = message_body + }) + + return msg +end + +test.register_coroutine_test( + "Handle detectionFrequency preference in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({preferences = {detectionfrequency = 63}})) + test.socket.zigbee:__expect_send( + { + mock_device.id, + build_iaszone_private_cmd(mock_device,IASZone_PRIVATE_COMMAND_ID, 63) + } + ) + end, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported CurrentZoneSensitivityLevel 1 should be Low", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.CurrentZoneSensitivityLevel:build_test_attr_report(mock_device, + 1) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.Low()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported CurrentZoneSensitivityLevel 2 should be Medium", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.CurrentZoneSensitivityLevel:build_test_attr_report(mock_device, + 2) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.Medium()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported CurrentZoneSensitivityLevel 3 should be High", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.CurrentZoneSensitivityLevel:build_test_attr_report(mock_device, + 3) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.High()) + } + }, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability sensitivityAdjustment High should be handled", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "stse.sensitivityAdjustment", component = "main", command = "setSensitivityAdjustment", args = {"High"} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:write(mock_device, PREF_SENSITIVITY_VALUE_HIGH) }) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:read(mock_device) }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability sensitivityAdjustment Medium should be handled", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "stse.sensitivityAdjustment", component = "main", command = "setSensitivityAdjustment", args = {"Medium"} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:write(mock_device, PREF_SENSITIVITY_VALUE_MEDIUM) }) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:read(mock_device) }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability sensitivityAdjustment Low should be handled", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "stse.sensitivityAdjustment", component = "main", command = "setSensitivityAdjustment", args = {"Low"} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:write(mock_device, PREF_SENSITIVITY_VALUE_LOW) }) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:read(mock_device) }) + end, + { + min_api_version = 19 + } +) + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 22a1512d50..63f5f93a42 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -134,6 +134,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ "HAOJAI Smart Switch 3-key",好家智能三键开关 "HAOJAI Smart Switch 6-key",好家智能六键开关 +"MultiIR Motion Detector MIR-IR100",麦乐克人体移动传感器MIR-IR100 "MultiIR Water Leak Sensor MIR-WA100",麦乐克水浸传感器MIR-WA100 "MultiIR Contact Sensor MIR-MC100",麦乐克门窗开关传感器MIR-MC100 "MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100 From c3d05cfeb725ed858c06ca4c226f3863fd93a59d Mon Sep 17 00:00:00 2001 From: LQ107 Date: Wed, 22 Apr 2026 00:05:30 +0800 Subject: [PATCH 251/538] WWSTCERT-10189 Ledvance zigbee meter plug (#2729) --- .../zigbee-switch/fingerprints.yml | 5 ++ .../src/ledvance-metering-plug/can_handle.lua | 13 +++++ .../ledvance-metering-plug/fingerprints.lua | 6 ++ .../src/ledvance-metering-plug/init.lua | 23 ++++++++ .../zigbee-switch/src/sub_drivers.lua | 1 + .../src/test/test_ledvance_metering_plug.lua | 55 +++++++++++++++++++ 6 files changed, 103 insertions(+) create mode 100644 drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/init.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 8765be8aeb..db44b09caa 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -1730,6 +1730,11 @@ zigbeeManufacturer: manufacturer: LEDVANCE model: RT TW deviceProfileName: color-temp-bulb + - id: "LEDVANCE/PLUG COMPACT EU EM T" + deviceLabel: SMART ZIGBEE COMPACT OUTDOOR PLUG EU + manufacturer: LEDVANCE + model: PLUG COMPACT EU EM T + deviceProfileName: switch-power-energy - id: "OSRAM/LIGHTIFY Edge-lit flushmount" deviceLabel: SYLVANIA Light manufacturer: OSRAM diff --git a/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/can_handle.lua new file mode 100644 index 0000000000..2d7f42bac8 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require("ledvance-metering-plug.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("ledvance-metering-plug") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/fingerprints.lua new file mode 100644 index 0000000000..e514269f82 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "LEDVANCE", model = "PLUG COMPACT EU EM T" } +} diff --git a/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/init.lua b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/init.lua new file mode 100644 index 0000000000..e907f25a28 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/init.lua @@ -0,0 +1,23 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local zigbee_constants = require "st.zigbee.constants" + +local function device_init(driver, device) + if device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == nil then + device:set_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY, 1, {persist = true}) + end + if device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == nil then + device:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, 100, {persist = true}) + end +end + +local ledvance_metering_plug = { + NAME = "LEDVANCE Metering Plug", + lifecycle_handlers = { + init = device_init + }, + can_handle = require("ledvance-metering-plug.can_handle") +} + +return ledvance_metering_plug diff --git a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua index 69be094da4..736c2a0464 100644 --- a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua @@ -14,6 +14,7 @@ return { lazy_load_if_possible("sinope"), lazy_load_if_possible("sinope-dimmer"), lazy_load_if_possible("zigbee-dimmer-power-energy"), + lazy_load_if_possible("ledvance-metering-plug"), lazy_load_if_possible("zigbee-metering-plug-power-consumption-report"), lazy_load_if_possible("jasco"), lazy_load_if_possible("multi-switch-no-master"), diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua b/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua new file mode 100644 index 0000000000..a2087797d4 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua @@ -0,0 +1,55 @@ +-- Copyright 2026 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" +local t_utils = require "integration_test.utils" +local zigbee_constants = require "st.zigbee.constants" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("switch-power-energy.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LEDVANCE", + model = "PLUG COMPACT EU EM T", + server_clusters = { 0x0006, 0x0702 } + } + } + } +) + +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( + "Device init should set default multiplier and divisor only when not already set", + function() + assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == nil) + assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == nil) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.wait_for_events() + assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == 1) + assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 100) + end +) + +test.register_coroutine_test( + "Device init should preserve device-reported multiplier and divisor", + function() + mock_device:set_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY, 5, {persist = true}) + mock_device:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, 1000, {persist = true}) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.wait_for_events() + assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == 5) + assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 1000) + end +) + +test.run_registered_tests() From b913a44a3e140d4e3b35d251ac4d3af3c41da6fb Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 22 Apr 2026 01:46:26 +0900 Subject: [PATCH 252/538] Delete guest user when SetYearDaySchedule is failed (#2876) Signed-off-by: Hunsup Jung --- .../matter-lock/src/new-matter-lock/init.lua | 59 +++++--- .../src/test/test_new_matter_lock.lua | 136 +++++++++++++++++- 2 files changed, 173 insertions(+), 22 deletions(-) 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 1e7d461eda..1497dedc4e 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -1554,15 +1554,29 @@ local function clear_user_response_handler(driver, device, ib, response) device.log.warn(string.format("Failed to clear user: %s", status)) end - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - statusCode = status - } - device:emit_event(capabilities.lockUsers.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) + -- This occurs in the "defaultSchedule" command failure path, when a guest user's credentials are set but + -- the scheduling fails during default setup. In this case, those set credentials should be removed, and we + -- wait to log lock credentials (note: as a "failure", though it technically succeeded) until here. + if cmdName == "defaultSchedule" then + local command_result_info = { + commandName = "addCredential", + userIndex = userIdx, + statusCode = "failure" + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + else + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + statusCode = status + } + device:emit_event(capabilities.lockUsers.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + end device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) end @@ -2418,17 +2432,22 @@ local function set_year_day_schedule_handler(driver, device, ib, response) local cmdName = "addCredential" local credIdx = device:get_field(lock_utils.CRED_INDEX) - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - credentialIndex = credIdx, - statusCode = status - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + if status == "success" then + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + credentialIndex = credIdx, + statusCode = status + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + else + local ep = find_default_endpoint(device, clusters.DoorLock.ID) + device:send(DoorLock.server.commands.ClearUser(device, ep, userIdx)) + end return end diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 3154e18cbd..bc9f5751c6 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -35,7 +35,7 @@ local mock_device = test.mock_device.build_test_matter_device({ cluster_id = DoorLock.ID, cluster_type = "SERVER", cluster_revision = 1, - feature_map = 0x0181, -- PIN & USR & COTA + feature_map = 0x0591, -- PIN & WDSCH & USR & COTA & YDSCH } }, device_types = { @@ -76,7 +76,7 @@ local function test_init() test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) ) - mock_device:expect_metadata_update({ profile = "lock-user-pin" }) + mock_device:expect_metadata_update({ profile = "lock-user-pin-schedule" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -2100,4 +2100,136 @@ test.register_coroutine_test( } ) +-- mock_device:set_field(lock_utils.COTA_CRED, "654123", {persist = true}) --overwrite random cred for test expectation +test.register_coroutine_test( + "Add Guest User and failure response ", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockCredentials.ID, + command = "addCredential", + args = {0, "guest", "pin", "654123"} + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + {credential_type = DoorLock.types.CredentialTypeEnum.PIN, credential_index = 1} + ), -- credential + "654123", -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({{userIndex = 1, userType = "guest"}}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials( + {{credentialIndex=1, credentialType="pin", userIndex=1}}, {visibility={displayed=false}} + ) + ) + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetYearDaySchedule( + mock_device, 1, -- endpoint + 1, -- year_day_index + 1, -- user_index + 0, -- local_start_time + 0xffffffff -- local_end_time + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.SetYearDaySchedule:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.FAILURE -- status + ), + } + ) + test.socket.matter:__expect_send({ + mock_device.id, + DoorLock.server.commands.ClearUser( + mock_device, 1, + 1 + ) + }) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.ClearUser:build_test_command_response( + mock_device, 1 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.weekDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.yearDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.commandResult( + {commandName="addCredential", statusCode="failure", userIndex=1}, {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + test.run_registered_tests() From 974773e3cad2753dcd0dccaffe2fd33838b3556a Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 21 Apr 2026 12:53:10 -0500 Subject: [PATCH 253/538] WWSTCERT-11096 Sombra Automated Shades and Blinds (#2912) --- drivers/SmartThings/zigbee-window-treatment/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml b/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml index c59d96f93f..b2e918746b 100644 --- a/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml +++ b/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml @@ -143,6 +143,11 @@ zigbeeManufacturer: manufacturer: Sombra Shades model: WM25/L-Z deviceProfileName: window-treatment-battery + - id: "Sombra Shades/SS25/L-Z" + deviceLabel: Sombra Automated Shades and Blinds + manufacturer: Sombra Shades + model: SS25/L-Z + deviceProfileName: window-treatment-battery zigbeeGeneric: - id: "genericShade" From 6dde90da877e9535e84e9ced627b757e43f4794e Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:40:45 -0500 Subject: [PATCH 254/538] use clear user status for default schedule lock credential result (#2913) --- .../matter-lock/src/new-matter-lock/init.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 1497dedc4e..fbea3ebfb5 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -1554,14 +1554,16 @@ local function clear_user_response_handler(driver, device, ib, response) device.log.warn(string.format("Failed to clear user: %s", status)) end - -- This occurs in the "defaultSchedule" command failure path, when a guest user's credentials are set but - -- the scheduling fails during default setup. In this case, those set credentials should be removed, and we - -- wait to log lock credentials (note: as a "failure", though it technically succeeded) until here. + -- In the "defaultSchedule" cmd failure path, when a guest user's credentials are set but the scheduling + -- fails during default setup, those credentials should be removed, so we wait to log lock credentials until here. if cmdName == "defaultSchedule" then + -- note: if clear user succeeds, we'd log credential settings as a "failure" since it's effectively a no-op. + -- If clear user fails, log "success" since the credentials would still be present. + local lock_credential_status = status == "success" and "failure" or "success" local command_result_info = { commandName = "addCredential", userIndex = userIdx, - statusCode = "failure" + statusCode = lock_credential_status } device:emit_event(capabilities.lockCredentials.commandResult( command_result_info, {state_change = true, visibility = {displayed = false}} From 8b4df1ad626528201458361c752eec6386c8ea34 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Thu, 9 Apr 2026 14:00:54 -0500 Subject: [PATCH 255/538] Fixing zigbee-switch tests for ColorTempPhysMireds - Splitting tests based off api_version --- .github/workflows/jenkins-driver-tests.yml | 6 +- .../test/test_all_capability_zigbee_bulb.lua | 147 +++++++++- .../src/test/test_aqara_led_bulb.lua | 70 ++++- .../src/test/test_aqara_light.lua | 72 ++++- .../test/test_duragreen_color_temp_bulb.lua | 126 ++++++++- .../zigbee-switch/src/test/test_rgbw_bulb.lua | 78 +++++- .../src/test/test_sengled_color_temp_bulb.lua | 125 ++++++++- .../src/test/test_white_color_temp_bulb.lua | 127 ++++++++- .../src/test/test_zll_color_temp_bulb.lua | 143 +++++++++- .../src/test/test_zll_rgbw_bulb.lua | 251 +++++++++++++++++- 10 files changed, 1121 insertions(+), 24 deletions(-) diff --git a/.github/workflows/jenkins-driver-tests.yml b/.github/workflows/jenkins-driver-tests.yml index 1e29ea8afc..153b8f6913 100644 --- a/.github/workflows/jenkins-driver-tests.yml +++ b/.github/workflows/jenkins-driver-tests.yml @@ -4,6 +4,10 @@ on: paths: - 'drivers/**' + pull_request_target: + paths: + - 'drivers/**' + permissions: statuses: write @@ -12,7 +16,7 @@ jobs: strategy: matrix: version: - [ 59, 60 ] + [ 59, 60, dev] runs-on: ubuntu-latest steps: 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 cf4afb92c2..5e27983718 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 @@ -423,10 +423,155 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) +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, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + OnOff.attributes.OnOff:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Level.attributes.CurrentLevel:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.CurrentHue:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.CurrentSaturation: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, + OnOff.ID) + }) + 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) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePower:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, + zigbee_test_utils.mock_hub_eui, + Level.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, + zigbee_test_utils.mock_hub_eui, + ColorControl.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 0x0010) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.CurrentHue:configure_reporting(mock_device, 1, 3600, 0x0010) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.CurrentSaturation:configure_reporting(mock_device, 1, 3600, 0x0010) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + }) + 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, + 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, + zigbee_test_utils.build_bind_request(mock_device, + zigbee_test_utils.mock_hub_eui, + ElectricalMeasurement.ID) + }) + 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, + 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.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.Multiplier:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.Divisor:read(mock_device) + }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 20 + } +) -- test.register_coroutine_test( -- "health check coroutine", -- function() 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 b9914ff4af..cc3e86d218 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 @@ -109,7 +109,75 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 + } +) + +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, + ColorControl.commands.MoveToColorTemperature(mock_device, 200, 0) + } + ) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Level.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + 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, + OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 20 } ) 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 f0b57744d3..a80bf1dcfc 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua @@ -106,15 +106,85 @@ test.register_coroutine_test( OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300, 1) } ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 20 + } +) + + +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, Level.attributes.OnTransitionTime:write(mock_device, 0) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OffTransitionTime:write(mock_device, 0) }) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.commands.MoveToColorTemperature(mock_device, 200, 0) + } + ) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Level.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + 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, + OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300, 1) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) 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 be8bc457e5..d1bebb68d5 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 @@ -38,6 +38,9 @@ test.register_coroutine_test( 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, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds: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, OnOff.ID) @@ -69,6 +72,18 @@ test.register_coroutine_test( ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) } ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) @@ -78,7 +93,113 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +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, Level.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.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, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17, + max_api_version = 19 + } +) + +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, + OnOff.attributes.OnOff:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + Level.attributes.CurrentLevel:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) + } + } + }, + { + inner_block_ordering = "relaxed", + min_api_version = 20 } ) @@ -117,7 +238,8 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) 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 f025655b26..4a83d8b920 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_rgbw_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_rgbw_bulb.lua @@ -81,6 +81,18 @@ test.register_coroutine_test( ColorControl.attributes.CurrentSaturation:configure_reporting(mock_device, 1, 3600, 16) } ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) @@ -90,7 +102,71 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + + +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, Level.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.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, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.CurrentHue:configure_reporting(mock_device, 1, 3600, 16) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.CurrentSaturation:configure_reporting(mock_device, 1, 3600, 16) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17, + max_api_version = 19 } ) 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 eae21e8ed7..356d7dbe31 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 @@ -69,16 +69,136 @@ test.register_coroutine_test( ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) } ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +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, Level.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.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, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17, + max_api_version = 19 + } +) + +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, + OnOff.attributes.OnOff:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + Level.attributes.CurrentLevel:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) + } + } + }, + { + inner_block_ordering = "relaxed", + min_api_version = 20 } ) @@ -117,7 +237,8 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) 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 bdcd61d03a..8a0db9d65c 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 @@ -38,6 +38,9 @@ test.register_coroutine_test( 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, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds: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, OnOff.ID) @@ -69,6 +72,18 @@ test.register_coroutine_test( ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) } ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) @@ -78,7 +93,114 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +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, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds: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, OnOff.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Level.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.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, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17, + max_api_version = 19 + } +) + +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, + OnOff.attributes.OnOff:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + Level.attributes.CurrentLevel:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) + } + } + }, + { + inner_block_ordering = "relaxed", + min_api_version = 20 } ) @@ -117,7 +239,8 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) 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 2ada61a3e6..a02e3978f5 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 @@ -42,12 +42,54 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 20 } ) +test.register_coroutine_test( + "Refresh necessary attributes", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + end, + { + min_api_version = 17, + max_api_version = 19 + } +) + +test.register_coroutine_test( + "ZLL periodic poll should occur", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + test.wait_for_events() + + test.mock_time.advance_time(50000) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.wait_for_events() + end, + { + test_init = function() + test.mock_device.add_test_device(mock_device) + test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "polling") + end, + min_api_version = 20 + } +) + test.register_coroutine_test( "ZLL periodic poll should occur", function() @@ -67,7 +109,8 @@ test.register_coroutine_test( test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "polling") end, - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) @@ -84,9 +127,31 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +test.register_coroutine_test( + "Switch command on should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "on", args = {} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "on") end + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.On(mock_device)}) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + end, + { + min_api_version = 17, + max_api_version = 19 } ) @@ -103,9 +168,31 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +test.register_coroutine_test( + "Switch command off should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "off", args = {} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "off") end + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.Off(mock_device)}) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + end, + { + min_api_version = 17, + max_api_version = 19 } ) @@ -122,9 +209,31 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +test.register_coroutine_test( + "SwitchLevel command setLevel should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switchLevel", component = "main", command = "setLevel", args = {50} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switchLevel", "setLevel") end + test.socket.zigbee:__expect_send({ mock_device.id, Level.commands.MoveToLevelWithOnOff(mock_device, math.floor(50 / 100.0 * 254), 0xFFFF)}) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + end, + { + min_api_version = 17, + max_api_version = 19 } ) @@ -142,10 +251,32 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 20 } ) +test.register_coroutine_test( + "ColorTemperature command setColorTemperature should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {200} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("colorTemperature", "setColorTemperature") end + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.On(mock_device)}) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.commands.MoveToColorTemperature(mock_device, 5000, 0x0000)}) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + end, + { + min_api_version = 17, + max_api_version = 19 + } +) test.run_registered_tests() 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 e05344d6a8..4e2ad43b06 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 @@ -34,6 +34,78 @@ end test.set_test_init_function(test_init) +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, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds: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, OnOff.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Level.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.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, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.CurrentHue:configure_reporting(mock_device, 1, 3600, 16) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.CurrentSaturation:configure_reporting(mock_device, 1, 3600, 16) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMaxMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTempPhysicalMinMireds:configure_reporting(mock_device, 1, 43200, 1) + } + ) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 20 + } +) + + test.register_coroutine_test( "Configure should configure all necessary attributes and refresh device", function() @@ -88,7 +160,8 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) @@ -102,12 +175,58 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 20 + } +) + +test.register_coroutine_test( + "Refresh necessary attributes", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + end, + { + min_api_version = 17, + max_api_version = 19 } ) +test.register_coroutine_test( + "ZLL periodic poll should occur", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + test.wait_for_events() + + test.mock_time.advance_time(5*60) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.wait_for_events() + end, + { + test_init = function() + test.mock_device.add_test_device(mock_device) + test.timer.__create_and_queue_test_time_advance_timer(5*60, "interval", "polling") + end, + min_api_version = 20 + } +) + test.register_coroutine_test( "ZLL periodic poll should occur", function() @@ -129,7 +248,8 @@ test.register_coroutine_test( test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(5*60, "interval", "polling") end, - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) @@ -151,9 +271,65 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + end, { - min_api_version = 17 + min_api_version = 20 + } +) + +test.register_coroutine_test( + "Capability 'switch' command 'on' should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "on", args = {} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "on") end + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.On(mock_device) }) + + test.wait_for_events() + test.mock_time.advance_time(2) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + + end, + { + min_api_version = 17, + max_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability 'switch' command 'off' should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "off", args = {} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "off") end + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.Off(mock_device) }) + + test.wait_for_events() + test.mock_time.advance_time(2) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + + end, + { + min_api_version = 20 } ) @@ -175,9 +351,38 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability 'switchLevel' command 'setLevel' on should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 57 } } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switchLevel", "setLevel") end + + test.socket.zigbee:__expect_send({ mock_device.id, Level.server.commands.MoveToLevelWithOnOff(mock_device, 144, 0xFFFF) }) + + test.wait_for_events() + test.mock_time.advance_time(2) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + + end, + { + min_api_version = 20 } ) @@ -199,9 +404,39 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 + } +) + +test.register_coroutine_test( + "ColorTemperature command setColorTemperature should be handled", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {200} } }) + if version.api > 15 then mock_device:expect_native_cmd_handler_registration("colorTemperature", "setColorTemperature") end + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.On(mock_device)}) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.commands.MoveToColorTemperature(mock_device, 5000, 0x0000)}) + + test.wait_for_events() + test.mock_time.advance_time(2) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + + end, + { + min_api_version = 20 } ) @@ -224,9 +459,11 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) + end, { - min_api_version = 17 + min_api_version = 17, + max_api_version = 19 } ) From ef453803fc1edc5685ec60c8d74ed006bcdeb74d Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 22 Apr 2026 01:46:26 +0900 Subject: [PATCH 256/538] Delete guest user when SetYearDaySchedule is failed (#2876) Signed-off-by: Hunsup Jung --- .../matter-lock/src/new-matter-lock/init.lua | 59 +++++--- .../src/test/test_new_matter_lock.lua | 136 +++++++++++++++++- 2 files changed, 173 insertions(+), 22 deletions(-) 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 1e7d461eda..1497dedc4e 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -1554,15 +1554,29 @@ local function clear_user_response_handler(driver, device, ib, response) device.log.warn(string.format("Failed to clear user: %s", status)) end - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - statusCode = status - } - device:emit_event(capabilities.lockUsers.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) + -- This occurs in the "defaultSchedule" command failure path, when a guest user's credentials are set but + -- the scheduling fails during default setup. In this case, those set credentials should be removed, and we + -- wait to log lock credentials (note: as a "failure", though it technically succeeded) until here. + if cmdName == "defaultSchedule" then + local command_result_info = { + commandName = "addCredential", + userIndex = userIdx, + statusCode = "failure" + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + else + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + statusCode = status + } + device:emit_event(capabilities.lockUsers.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + end device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) end @@ -2418,17 +2432,22 @@ local function set_year_day_schedule_handler(driver, device, ib, response) local cmdName = "addCredential" local credIdx = device:get_field(lock_utils.CRED_INDEX) - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - credentialIndex = credIdx, - statusCode = status - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + if status == "success" then + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + credentialIndex = credIdx, + statusCode = status + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + else + local ep = find_default_endpoint(device, clusters.DoorLock.ID) + device:send(DoorLock.server.commands.ClearUser(device, ep, userIdx)) + end return end diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 3154e18cbd..bc9f5751c6 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -35,7 +35,7 @@ local mock_device = test.mock_device.build_test_matter_device({ cluster_id = DoorLock.ID, cluster_type = "SERVER", cluster_revision = 1, - feature_map = 0x0181, -- PIN & USR & COTA + feature_map = 0x0591, -- PIN & WDSCH & USR & COTA & YDSCH } }, device_types = { @@ -76,7 +76,7 @@ local function test_init() test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) ) - mock_device:expect_metadata_update({ profile = "lock-user-pin" }) + mock_device:expect_metadata_update({ profile = "lock-user-pin-schedule" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -2100,4 +2100,136 @@ test.register_coroutine_test( } ) +-- mock_device:set_field(lock_utils.COTA_CRED, "654123", {persist = true}) --overwrite random cred for test expectation +test.register_coroutine_test( + "Add Guest User and failure response ", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockCredentials.ID, + command = "addCredential", + args = {0, "guest", "pin", "654123"} + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + {credential_type = DoorLock.types.CredentialTypeEnum.PIN, credential_index = 1} + ), -- credential + "654123", -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({{userIndex = 1, userType = "guest"}}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials( + {{credentialIndex=1, credentialType="pin", userIndex=1}}, {visibility={displayed=false}} + ) + ) + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetYearDaySchedule( + mock_device, 1, -- endpoint + 1, -- year_day_index + 1, -- user_index + 0, -- local_start_time + 0xffffffff -- local_end_time + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.SetYearDaySchedule:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.FAILURE -- status + ), + } + ) + test.socket.matter:__expect_send({ + mock_device.id, + DoorLock.server.commands.ClearUser( + mock_device, 1, + 1 + ) + }) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.ClearUser:build_test_command_response( + mock_device, 1 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.weekDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.yearDaySchedules({}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.commandResult( + {commandName="addCredential", statusCode="failure", userIndex=1}, {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + test.run_registered_tests() From 5b1ef5502e2fc42f36c8580b89b7a52de95060fb Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:40:45 -0500 Subject: [PATCH 257/538] use clear user status for default schedule lock credential result (#2913) --- .../matter-lock/src/new-matter-lock/init.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 1497dedc4e..fbea3ebfb5 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -1554,14 +1554,16 @@ local function clear_user_response_handler(driver, device, ib, response) device.log.warn(string.format("Failed to clear user: %s", status)) end - -- This occurs in the "defaultSchedule" command failure path, when a guest user's credentials are set but - -- the scheduling fails during default setup. In this case, those set credentials should be removed, and we - -- wait to log lock credentials (note: as a "failure", though it technically succeeded) until here. + -- In the "defaultSchedule" cmd failure path, when a guest user's credentials are set but the scheduling + -- fails during default setup, those credentials should be removed, so we wait to log lock credentials until here. if cmdName == "defaultSchedule" then + -- note: if clear user succeeds, we'd log credential settings as a "failure" since it's effectively a no-op. + -- If clear user fails, log "success" since the credentials would still be present. + local lock_credential_status = status == "success" and "failure" or "success" local command_result_info = { commandName = "addCredential", userIndex = userIdx, - statusCode = "failure" + statusCode = lock_credential_status } device:emit_event(capabilities.lockCredentials.commandResult( command_result_info, {state_change = true, visibility = {displayed = false}} From 973bde84d3f3bd87faa44a07f0876a8cd1b99ec2 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:38:02 +0800 Subject: [PATCH 258/538] add MultiIR Smoke Detector MIR-SM200 (#2874) Co-authored-by: Carter Swedal --- .../zigbee-smoke-detector/fingerprints.yml | 5 + .../smoke-battery-tamper-no-fw-update.yml | 14 ++ .../src/MultiIR/can_handle.lua | 13 ++ .../src/MultiIR/fingerprints.lua | 6 + .../src/MultiIR/init.lua | 53 +++++ .../zigbee-smoke-detector/src/sub_drivers.lua | 1 + .../src/test/test_multiir_smoke_detector.lua | 195 ++++++++++++++++++ tools/localizations/cn.csv | 1 + 8 files changed, 288 insertions(+) create mode 100644 drivers/SmartThings/zigbee-smoke-detector/profiles/smoke-battery-tamper-no-fw-update.yml create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/init.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/test/test_multiir_smoke_detector.lua diff --git a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml index ddd1135cbd..c1f859037b 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml +++ b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml @@ -54,3 +54,8 @@ zigbeeManufacturer: manufacturer: HEIMAN model: GASSensor-N deviceProfileName: smoke-detector + - id: "MultIR/MIR-SM200" + deviceLabel: MultiIR Smoke Detector MIR-SM200 + manufacturer: MultIR + model: MIR-SM200 + deviceProfileName: smoke-battery-tamper-no-fw-update diff --git a/drivers/SmartThings/zigbee-smoke-detector/profiles/smoke-battery-tamper-no-fw-update.yml b/drivers/SmartThings/zigbee-smoke-detector/profiles/smoke-battery-tamper-no-fw-update.yml new file mode 100644 index 0000000000..eb66ce92c4 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/profiles/smoke-battery-tamper-no-fw-update.yml @@ -0,0 +1,14 @@ +name: smoke-battery-tamper-no-fw-update +components: +- id: main + capabilities: + - id: smokeDetector + version: 1 + - id: tamperAlert + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: SmokeDetector diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/can_handle.lua b/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/can_handle.lua new file mode 100644 index 0000000000..d389619c78 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require "MultiIR.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("MultiIR") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/fingerprints.lua b/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/fingerprints.lua new file mode 100644 index 0000000000..3d845bdf7e --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "MultIR", model = "MIR-SM200" } +} diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/init.lua new file mode 100644 index 0000000000..b382a56cec --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/init.lua @@ -0,0 +1,53 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local zcl_clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" + +local IASZone = zcl_clusters.IASZone + +local function generate_event_from_zone_status(driver, device, zone_status, zb_rx) + if zone_status:is_alarm1_set() then + device:emit_event(capabilities.smokeDetector.smoke.detected()) + elseif zone_status:is_alarm2_set() then + device:emit_event(capabilities.smokeDetector.smoke.tested()) + else + device:emit_event(capabilities.smokeDetector.smoke.clear()) + end + if device:supports_capability(capabilities.tamperAlert) then + device:emit_event(zone_status:is_tamper_set() and capabilities.tamperAlert.tamper.detected() or capabilities.tamperAlert.tamper.clear()) + end +end + +local function ias_zone_status_change_handler(driver, device, zb_rx) + local zone_status = zb_rx.body.zcl_body.zone_status + generate_event_from_zone_status(driver, device, zone_status, zb_rx) +end + +local function added_handler(self, device) + device:emit_event(capabilities.battery.battery(100)) + device:emit_event(capabilities.smokeDetector.smoke.clear()) + device:emit_event(capabilities.tamperAlert.tamper.clear()) +end + +local MultiIR_smoke_detector_handler = { + NAME = "MultiIR Smoke Detector Handler", + lifecycle_handlers = { + added = added_handler + }, + zigbee_handlers = { + cluster = { + [IASZone.ID] = { + [IASZone.client.commands.ZoneStatusChangeNotification.ID] = ias_zone_status_change_handler + } + }, + attr = { + [IASZone.ID] = { + [IASZone.attributes.ZoneStatus.ID] = generate_event_from_zone_status + } + } + }, + can_handle = require("MultiIR.can_handle") +} + +return MultiIR_smoke_detector_handler diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua b/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua index e6c5e004f3..2b917b85dc 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua @@ -6,5 +6,6 @@ local sub_drivers = { lazy_load_if_possible("frient"), lazy_load_if_possible("aqara-gas"), lazy_load_if_possible("aqara"), + lazy_load_if_possible("MultiIR"), } return sub_drivers diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_multiir_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_multiir_smoke_detector.lua new file mode 100644 index 0000000000..e79df95f01 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_multiir_smoke_detector.lua @@ -0,0 +1,195 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local IASZone = clusters.IASZone + +local mock_device = test.mock_device.build_test_zigbee_device( + { profile = t_utils.get_profile_definition("smoke-battery-tamper-no-fw-update.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "MultIR", + model = "MIR-SM200", + server_clusters = { 0x0001,0x0020, 0x0500, 0x0502 } + } + } + } +) + +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( + "Handle added lifecycle", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__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", + capabilities.battery.battery(100))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.smokeDetector.smoke.clear())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tamperAlert.tamper.clear())) + end, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported ZoneStatus should be handled: smoke/clear tamper/clear", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported ZoneStatus should be handled: smoke/detected tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0005) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported ZoneStatus should be handled: smoke/tested tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0006) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.tested()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: smoke/detected tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0005, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: smoke/tested tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0006, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.tested()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: smoke/clear tamper/clear", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + }, + { + min_api_version = 19 + } +) + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index c03098c37f..faeee2a3f7 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -134,3 +134,4 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ "HAOJAI Smart Switch 3-key",好家智能三键开关 "HAOJAI Smart Switch 6-key",好家智能六键开关 +"MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 From aa5c66a3963b39612425187cf6f20d7b04f09d23 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:55:52 +0800 Subject: [PATCH 259/538] add MultiIR Smart button MIR-SO100 (#2862) Co-authored-by: Carter Swedal Co-authored-by: Chris Baumler --- .../zigbee-button/fingerprints.yml | 5 + .../one-button-battery-no-fw-update.yml | 12 ++ .../zigbee-button/src/MultiIR/can_handle.lua | 13 ++ .../src/MultiIR/fingerprints.lua | 6 + .../zigbee-button/src/MultiIR/init.lua | 37 +++++ .../zigbee-button/src/sub_drivers.lua | 1 + .../src/test/test_multiir_smart_button.lua | 131 ++++++++++++++++++ tools/localizations/cn.csv | 1 + 8 files changed, 206 insertions(+) create mode 100644 drivers/SmartThings/zigbee-button/profiles/one-button-battery-no-fw-update.yml create mode 100644 drivers/SmartThings/zigbee-button/src/MultiIR/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/MultiIR/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/MultiIR/init.lua create mode 100644 drivers/SmartThings/zigbee-button/src/test/test_multiir_smart_button.lua diff --git a/drivers/SmartThings/zigbee-button/fingerprints.yml b/drivers/SmartThings/zigbee-button/fingerprints.yml index f256e0584c..09e915321b 100644 --- a/drivers/SmartThings/zigbee-button/fingerprints.yml +++ b/drivers/SmartThings/zigbee-button/fingerprints.yml @@ -267,6 +267,11 @@ zigbeeManufacturer: manufacturer: WALL HERO model: ACL-401SCA4 deviceProfileName: thirty-buttons + - id: "MultIR/MIR-SO100" + deviceLabel: MultiIR Smart button MIR-SO100 + manufacturer: MultIR + model: MIR-SO100 + deviceProfileName: one-button-battery-no-fw-update zigbeeGeneric: - id: "generic-button-sensor" deviceLabel: "Zigbee Generic Button" diff --git a/drivers/SmartThings/zigbee-button/profiles/one-button-battery-no-fw-update.yml b/drivers/SmartThings/zigbee-button/profiles/one-button-battery-no-fw-update.yml new file mode 100644 index 0000000000..98175a88ea --- /dev/null +++ b/drivers/SmartThings/zigbee-button/profiles/one-button-battery-no-fw-update.yml @@ -0,0 +1,12 @@ +name: one-button-battery-no-fw-update +components: + - id: main + capabilities: + - id: button + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: Button diff --git a/drivers/SmartThings/zigbee-button/src/MultiIR/can_handle.lua b/drivers/SmartThings/zigbee-button/src/MultiIR/can_handle.lua new file mode 100644 index 0000000000..d389619c78 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/MultiIR/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require "MultiIR.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("MultiIR") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-button/src/MultiIR/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/MultiIR/fingerprints.lua new file mode 100644 index 0000000000..f9e21e49b9 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/MultiIR/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "MultIR", model = "MIR-SO100" } +} diff --git a/drivers/SmartThings/zigbee-button/src/MultiIR/init.lua b/drivers/SmartThings/zigbee-button/src/MultiIR/init.lua new file mode 100644 index 0000000000..06d6d98f7e --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/MultiIR/init.lua @@ -0,0 +1,37 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local zcl_clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local log = require "log" + +local IASZone = zcl_clusters.IASZone +local PRIVATE_CMD_ID = 0xF1 + +local function ias_zone_private_cmd_handler(self, device, zb_rx) + local cmd_data = zb_rx.body.zcl_body.body_bytes:byte(1) + if cmd_data == 0 then + device:emit_event(capabilities.button.button.pushed({state_change = true})) + elseif cmd_data == 1 then + device:emit_event(capabilities.button.button.double({state_change = true})) + elseif cmd_data == 0x80 then + device:emit_event(capabilities.button.button.held({state_change = true})) + else + log.info("ias_zone_private_cmd Unknown value",zb_rx.body.zcl_body.body_bytes:byte(1)) + end +end + +local MultiIR_Emergency_Button = { + NAME = "MultiIR Emergency Button", + zigbee_handlers = { + cluster = { + [IASZone.ID] = { + [PRIVATE_CMD_ID] = ias_zone_private_cmd_handler + } + } + }, + can_handle = require("MultiIR.can_handle") +} + +return MultiIR_Emergency_Button diff --git a/drivers/SmartThings/zigbee-button/src/sub_drivers.lua b/drivers/SmartThings/zigbee-button/src/sub_drivers.lua index bec1f76ac1..8aa36d9ba0 100644 --- a/drivers/SmartThings/zigbee-button/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-button/src/sub_drivers.lua @@ -14,5 +14,6 @@ local sub_drivers = { lazy_load_if_possible("ewelink"), lazy_load_if_possible("thirdreality"), lazy_load_if_possible("ezviz"), + lazy_load_if_possible("MultiIR"), } return sub_drivers diff --git a/drivers/SmartThings/zigbee-button/src/test/test_multiir_smart_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_multiir_smart_button.lua new file mode 100644 index 0000000000..84b974e988 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/test/test_multiir_smart_button.lua @@ -0,0 +1,131 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Mock out globals +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local t_utils = require "integration_test.utils" +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local IASZone = clusters.IASZone +local PRIVATE_CMD_ID = 0xF1 + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("one-button-battery-no-fw-update.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "MultIR", + model = "MIR-SO100", + server_clusters = {0x0000, 0x0001, 0x0003, 0x0020, 0x0500, 0x0B05} + } + } + } +) + +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( + "added lifecycle event", + function() + -- The initial button pushed event should be send during the device's first time onboarding + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.button.supportedButtonValues({ "pushed","held","double" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send({ + mock_device.id, + { + capability_id = "button", component_id = "main", + attribute_id = "button", state = { value = "pushed" } + } + }) + -- 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.id, "added" }) + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.button.supportedButtonValues({ "pushed","held","double" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }) + ) + ) + end, + { + min_api_version = 19 + } +) + +test.register_message_test( + "IASZone cmd 0xF1 0x00 are handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, IASZone.ID, PRIVATE_CMD_ID, 0x0000, "\x00", 0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.button.button.pushed({state_change = true})) + } + } +) + +test.register_message_test( + "IASZone cmd 0xF1 0x01 are handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, IASZone.ID, PRIVATE_CMD_ID, 0x0000, "\x01", 0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.button.button.double({state_change = true})) + } + } +) + +test.register_message_test( + "IASZone cmd 0xF1 0x80 are handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, IASZone.ID, PRIVATE_CMD_ID, 0x0000, "\x80", 0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.button.button.held({state_change = true})) + } + } +) + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index faeee2a3f7..8b15479879 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -134,4 +134,5 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ "HAOJAI Smart Switch 3-key",好家智能三键开关 "HAOJAI Smart Switch 6-key",好家智能六键开关 +"MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 From d1c2bef55829367cba7a6f425d494814adacf816 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:44:34 +0800 Subject: [PATCH 260/538] add MultiIR contact sensor MIR_MC100 (#2867) Co-authored-by: Chris Baumler --- .../zigbee-contact/fingerprints.yml | 5 + .../contact-battery-tamper-no-fw-update.yml | 14 ++ .../zigbee-contact/src/MultiIR/can_handle.lua | 13 ++ .../src/MultiIR/fingerprints.lua | 6 + .../zigbee-contact/src/MultiIR/init.lua | 52 +++++++ .../zigbee-contact/src/sub_drivers.lua | 1 + .../src/test/test_multiir_contact_tamper.lua | 147 ++++++++++++++++++ tools/localizations/cn.csv | 1 + 8 files changed, 239 insertions(+) create mode 100644 drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper-no-fw-update.yml create mode 100644 drivers/SmartThings/zigbee-contact/src/MultiIR/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/MultiIR/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/test/test_multiir_contact_tamper.lua diff --git a/drivers/SmartThings/zigbee-contact/fingerprints.yml b/drivers/SmartThings/zigbee-contact/fingerprints.yml index c30c3296a2..3dba88a8e9 100644 --- a/drivers/SmartThings/zigbee-contact/fingerprints.yml +++ b/drivers/SmartThings/zigbee-contact/fingerprints.yml @@ -209,6 +209,11 @@ zigbeeManufacturer: manufacturer: Aug. Winkhaus SE model: FM.V.ZB deviceProfileName: contact-battery-profile + - id: "MultIR/MIR-MC100" + deviceLabel: MultiIR Contact Sensor MIR-MC100 + manufacturer: MultIR + model: MIR-MC100 + deviceProfileName: contact-battery-tamper-no-fw-update zigbeeGeneric: - id: "contact-generic" deviceLabel: "Zigbee Contact Sensor" diff --git a/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper-no-fw-update.yml b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper-no-fw-update.yml new file mode 100644 index 0000000000..528e6c0021 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper-no-fw-update.yml @@ -0,0 +1,14 @@ +name: contact-battery-tamper-no-fw-update +components: +- id: main + capabilities: + - id: contactSensor + version: 1 + - id: battery + version: 1 + - id: tamperAlert + version: 1 + - id: refresh + version: 1 + categories: + - name: ContactSensor diff --git a/drivers/SmartThings/zigbee-contact/src/MultiIR/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/MultiIR/can_handle.lua new file mode 100644 index 0000000000..d389619c78 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/MultiIR/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require "MultiIR.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("MultiIR") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-contact/src/MultiIR/fingerprints.lua b/drivers/SmartThings/zigbee-contact/src/MultiIR/fingerprints.lua new file mode 100644 index 0000000000..4bda1794f0 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/MultiIR/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "MultIR", model = "MIR-MC100" } +} diff --git a/drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua b/drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua new file mode 100644 index 0000000000..1481bb01fc --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua @@ -0,0 +1,52 @@ +-- Copyright 2026 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 + +local function generate_event_from_zone_status(driver, device, zone_status, zb_rx) + device:emit_event(zone_status:is_alarm1_set() and capabilities.contactSensor.contact.open() or capabilities.contactSensor.contact.closed()) + if device:supports_capability_by_id(capabilities.tamperAlert.ID) then + device:emit_event(zone_status:is_tamper_set() and capabilities.tamperAlert.tamper.detected() or capabilities.tamperAlert.tamper.clear()) + end +end + +local function ias_zone_status_attr_handler(driver, device, attr_val, zb_rx) + generate_event_from_zone_status(driver, device, attr_val, zb_rx) +end + +local function ias_zone_status_change_handler(driver, device, zb_rx) + generate_event_from_zone_status(driver, device, zb_rx.body.zcl_body.zone_status, zb_rx) +end + +local function added_handler(driver, device) + device:emit_event(capabilities.battery.battery(100)) + device:emit_event(capabilities.contactSensor.contact.closed()) + if device:supports_capability_by_id(capabilities.tamperAlert.ID) then + device:emit_event(capabilities.tamperAlert.tamper.clear()) + end +end + +local MultiIR_sensor = { + NAME = "MultiIR Contact Sensor", + lifecycle_handlers = { + added = added_handler + }, + zigbee_handlers = { + cluster = { + [IASZone.ID] = { + [IASZone.client.commands.ZoneStatusChangeNotification.ID] = ias_zone_status_change_handler, + } + }, + attr = { + [IASZone.ID] = { + [IASZone.attributes.ZoneStatus.ID] = ias_zone_status_attr_handler, + } + } + }, + can_handle = require("MultiIR.can_handle") +} + +return MultiIR_sensor diff --git a/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua b/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua index 394de5a72d..8a4977ca5e 100644 --- a/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua @@ -10,5 +10,6 @@ local sub_drivers = { lazy_load_if_possible("smartsense-multi"), lazy_load_if_possible("sengled"), lazy_load_if_possible("frient"), + lazy_load_if_possible("MultiIR"), } return sub_drivers diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_multiir_contact_tamper.lua b/drivers/SmartThings/zigbee-contact/src/test/test_multiir_contact_tamper.lua new file mode 100644 index 0000000000..123b9df58c --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/test/test_multiir_contact_tamper.lua @@ -0,0 +1,147 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local IASZone = clusters.IASZone + +local mock_device = test.mock_device.build_test_zigbee_device( + { profile = t_utils.get_profile_definition("contact-battery-tamper-no-fw-update.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "MultIR", + model = "MIR-MC100", + server_clusters = { 0x0001, 0x0500 } + } + } + } +) + +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( + "Handle added lifecycle", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__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", + capabilities.battery.battery(100))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.contactSensor.contact.closed())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tamperAlert.tamper.clear())) + end, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported ZoneStatus should be handled: contact/closed tamper/clear", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported ZoneStatus should be handled: contact/open tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0005) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: contact/open tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0005, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: contact/closed tamper/clear", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + }, + { + min_api_version = 19 + } +) + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 8b15479879..04b3bb6f51 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -134,5 +134,6 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ "HAOJAI Smart Switch 3-key",好家智能三键开关 "HAOJAI Smart Switch 6-key",好家智能六键开关 +"MultiIR Contact Sensor MIR-MC100",麦乐克门窗开关传感器MIR-MC100 "MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 From dcbe713e5e80214ca6ec993d3bffe4740eea7949 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:48:51 +0800 Subject: [PATCH 261/538] add MultiIR Water Leak MIR-WA100 (#2896) Co-authored-by: Chris Baumler --- .../zigbee-water-leak-sensor/fingerprints.yml | 7 ++++++- .../profiles/water-battery-no-fw-update.yml | 12 ++++++++++++ tools/localizations/cn.csv | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/profiles/water-battery-no-fw-update.yml diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/fingerprints.yml b/drivers/SmartThings/zigbee-water-leak-sensor/fingerprints.yml index 5b1c1e4977..e7007de4f7 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/fingerprints.yml +++ b/drivers/SmartThings/zigbee-water-leak-sensor/fingerprints.yml @@ -113,4 +113,9 @@ zigbeeManufacturer: deviceLabel: NEO Water Leak Sensor manufacturer: NEO model: NAS_WS11 - deviceProfileName: water-battery \ No newline at end of file + deviceProfileName: water-battery + - id: MultIR/MIR-WA100 + deviceLabel: MultiIR Water Leak Sensor MIR-WA100 + manufacturer: MultIR + model: MIR-WA100 + deviceProfileName: water-battery-no-fw-update diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/profiles/water-battery-no-fw-update.yml b/drivers/SmartThings/zigbee-water-leak-sensor/profiles/water-battery-no-fw-update.yml new file mode 100644 index 0000000000..03a891c23b --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/profiles/water-battery-no-fw-update.yml @@ -0,0 +1,12 @@ +name: water-battery-no-fw-update +components: +- id: main + capabilities: + - id: waterSensor + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: LeakSensor diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 04b3bb6f51..22a1512d50 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -134,6 +134,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ "HAOJAI Smart Switch 3-key",好家智能三键开关 "HAOJAI Smart Switch 6-key",好家智能六键开关 +"MultiIR Water Leak Sensor MIR-WA100",麦乐克水浸传感器MIR-WA100 "MultiIR Contact Sensor MIR-MC100",麦乐克门窗开关传感器MIR-MC100 "MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 From fb40ebeb32b0965229695be956f423b737bd9907 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:57:03 +0800 Subject: [PATCH 262/538] add multiir_motion_MIR-IR100 (#2861) Co-authored-by: Carter Swedal Co-authored-by: Chris Baumler --- .../zigbee-motion-sensor/fingerprints.yml | 5 + ...nce-sensitivity-frequency-no-fw-update.yml | 26 ++ .../src/MultiIR/can_handle.lua | 13 + .../src/MultiIR/fingerprints.lua | 6 + .../zigbee-motion-sensor/src/MultiIR/init.lua | 117 +++++++++ .../zigbee-motion-sensor/src/init.lua | 1 + .../src/test/test_multiir_motion_pir.lua | 224 ++++++++++++++++++ tools/localizations/cn.csv | 1 + 8 files changed, 393 insertions(+) create mode 100644 drivers/SmartThings/zigbee-motion-sensor/profiles/motion-battery-illuminance-sensitivity-frequency-no-fw-update.yml create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/init.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/test/test_multiir_motion_pir.lua diff --git a/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml b/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml index e71ab0c5ac..89d3df1586 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml +++ b/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml @@ -209,6 +209,11 @@ zigbeeManufacturer: manufacturer: sengled model: E1M-G7H deviceProfileName: motion-battery + - id: "MultIR/MIR-IR100" + deviceLabel: MultiIR Motion Detector MIR-IR100 + manufacturer: MultIR + model: MIR-IR100 + deviceProfileName: motion-battery-illuminance-sensitivity-frequency-no-fw-update zigbeeGeneric: - id: kickstarter/motion/1 deviceLabel: SmartThings Motion Sensor diff --git a/drivers/SmartThings/zigbee-motion-sensor/profiles/motion-battery-illuminance-sensitivity-frequency-no-fw-update.yml b/drivers/SmartThings/zigbee-motion-sensor/profiles/motion-battery-illuminance-sensitivity-frequency-no-fw-update.yml new file mode 100644 index 0000000000..5fc7d7ea8b --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/profiles/motion-battery-illuminance-sensitivity-frequency-no-fw-update.yml @@ -0,0 +1,26 @@ +name: motion-battery-illuminance-sensitivity-frequency-no-fw-update +components: + - id: main + capabilities: + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + - id: stse.sensitivityAdjustment + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: MotionSensor +preferences: + - title: "检测频率/秒(detection frequency/sec)" + name: detectionfrequency + description: "传感器检测频率(Sensor detects frequency unit: seconds)" + required: false + preferenceType: integer + definition: + minimum: 10 + maximum: 1800 + default: 60 diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/can_handle.lua new file mode 100644 index 0000000000..d389619c78 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require "MultiIR.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("MultiIR") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/fingerprints.lua b/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/fingerprints.lua new file mode 100644 index 0000000000..139d3aa054 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "MultIR", model = "MIR-IR100" } +} diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/init.lua new file mode 100644 index 0000000000..c3c47753e1 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/MultiIR/init.lua @@ -0,0 +1,117 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local zcl_clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local data_types = require "st.zigbee.data_types" +local FrameCtrl = require "st.zigbee.zcl.frame_ctrl" +local zcl_messages = require "st.zigbee.zcl" +local messages = require "st.zigbee.messages" +local zb_const = require "st.zigbee.constants" + +local sensitivityAdjustment = capabilities["stse.sensitivityAdjustment"] +local sensitivityAdjustmentCommandName = "setSensitivityAdjustment" +local IASZone = zcl_clusters.IASZone +local IASZone_PRIVATE_COMMAND_ID = 0xF4 + +local PREF_SENSITIVITY_VALUE_HIGH = 3 +local PREF_SENSITIVITY_VALUE_MEDIUM = 2 +local PREF_SENSITIVITY_VALUE_LOW = 1 + +local function send_iaszone_private_cmd(device, priv_cmd, data) + local frame_ctrl = FrameCtrl(0x00) + frame_ctrl:set_cluster_specific() + + local zclh = zcl_messages.ZclHeader({ + frame_ctrl = frame_ctrl, + cmd = data_types.ZCLCommandId(priv_cmd) + }) + + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zclh, + zcl_body = data_types.Uint16(data) + }) + + local addr_header = messages.AddressHeader( + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + device:get_short_address(), + device:get_endpoint(IASZone.ID), + zb_const.HA_PROFILE_ID, + IASZone.ID + ) + + local zigbee_msg = messages.ZigbeeMessageTx({ + address_header = addr_header, + body = message_body + }) + + device:send(zigbee_msg) +end + +local function iaszone_attr_sen_handler(driver, device, value, zb_rx) + if value.value == PREF_SENSITIVITY_VALUE_HIGH then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.High()) + elseif value.value == PREF_SENSITIVITY_VALUE_MEDIUM then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.Medium()) + elseif value.value == PREF_SENSITIVITY_VALUE_LOW then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.Low()) + end +end + +local function send_sensitivity_adjustment_value(device, value) + device:send(IASZone.attributes.CurrentZoneSensitivityLevel:write(device, value)) +end + +local function sensitivity_adjustment_capability_handler(driver, device, command) + local sensitivity = command.args.sensitivity + if sensitivity == 'High' then + send_sensitivity_adjustment_value(device, PREF_SENSITIVITY_VALUE_HIGH) + elseif sensitivity == 'Medium' then + send_sensitivity_adjustment_value(device, PREF_SENSITIVITY_VALUE_MEDIUM) + elseif sensitivity == 'Low' then + send_sensitivity_adjustment_value(device, PREF_SENSITIVITY_VALUE_LOW) + end + device:send(IASZone.attributes.CurrentZoneSensitivityLevel:read(device)) +end + +local function added_handler(self, device) + device:emit_event(capabilities.motionSensor.motion.inactive()) + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.High()) + device:emit_event(capabilities.battery.battery(100)) + device:send(IASZone.attributes.CurrentZoneSensitivityLevel:read(device)) +end + +local function info_changed(driver, device, event, args) + for name, value in pairs(device.preferences) do + if (device.preferences[name] ~= nil and args.old_st_store.preferences[name] ~= device.preferences[name]) then + if (name == "detectionfrequency") then + local detectionfrequency = tonumber(device.preferences.detectionfrequency) + send_iaszone_private_cmd(device, IASZone_PRIVATE_COMMAND_ID, detectionfrequency) + end + end + end +end + +local MultiIR_motion_handler = { + NAME = "MultiIR motion handler", + lifecycle_handlers = { + added = added_handler, + infoChanged = info_changed + }, + capability_handlers = { + [sensitivityAdjustment.ID] = { + [sensitivityAdjustmentCommandName] = sensitivity_adjustment_capability_handler, + } + }, + zigbee_handlers = { + attr = { + [IASZone.ID] = { + [IASZone.attributes.CurrentZoneSensitivityLevel.ID] = iaszone_attr_sen_handler + } + } + }, + can_handle = require("MultiIR.can_handle") +} + +return MultiIR_motion_handler diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/init.lua index 11e9d94a85..660948720b 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/init.lua @@ -118,6 +118,7 @@ local zigbee_motion_driver = { lazy_load_if_possible("smartsense"), lazy_load_if_possible("thirdreality"), lazy_load_if_possible("sengled"), + lazy_load_if_possible("MultiIR"), }, additional_zcl_profiles = { [0xFC01] = true diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_multiir_motion_pir.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_multiir_motion_pir.lua new file mode 100644 index 0000000000..357190c730 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_multiir_motion_pir.lua @@ -0,0 +1,224 @@ +-- Copyright 2026 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 data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local FrameCtrl = require "st.zigbee.zcl.frame_ctrl" + +--If this line is removed, an error will occur. +test.add_package_capability("sensitivityAdjustment.yaml") + +local sensitivityAdjustment = capabilities["stse.sensitivityAdjustment"] +local IASZone = clusters.IASZone +local PowerConfiguration = clusters.PowerConfiguration + +local PREF_SENSITIVITY_VALUE_HIGH = 3 +local PREF_SENSITIVITY_VALUE_MEDIUM = 2 +local PREF_SENSITIVITY_VALUE_LOW = 1 +local IASZone_PRIVATE_COMMAND_ID = 0xF4 + +-- Needed for building iaszone_private_cmd msg +local messages = require "st.zigbee.messages" +local zb_const = require "st.zigbee.constants" + +local zcl_messages = require "st.zigbee.zcl" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("motion-battery-illuminance-sensitivity-frequency-no-fw-update.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "MultIR", + model = "MIR-IR100", + server_clusters = { PowerConfiguration.ID ,IASZone.ID} + } + } + } +) + +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( + "Handle added lifecycle", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__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", + capabilities.motionSensor.motion.inactive())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + sensitivityAdjustment.sensitivityAdjustment.High())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.battery.battery(100))) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:read(mock_device)}) + end, + { + min_api_version = 19 + } +) + +local function build_iaszone_private_cmd(device, priv_cmd, data) + local frame_ctrl = FrameCtrl(0x00) + frame_ctrl:set_cluster_specific() + + local zclh = zcl_messages.ZclHeader({ + frame_ctrl = frame_ctrl, + cmd = data_types.ZCLCommandId(priv_cmd) + }) + + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zclh, + zcl_body = data_types.Uint16(data) + }) + + local addr_header = messages.AddressHeader( + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + device:get_short_address(), + device:get_endpoint(IASZone.ID), + zb_const.HA_PROFILE_ID, + IASZone.ID + ) + + local msg = messages.ZigbeeMessageTx({ + address_header = addr_header, + body = message_body + }) + + return msg +end + +test.register_coroutine_test( + "Handle detectionFrequency preference in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({preferences = {detectionfrequency = 63}})) + test.socket.zigbee:__expect_send( + { + mock_device.id, + build_iaszone_private_cmd(mock_device,IASZone_PRIVATE_COMMAND_ID, 63) + } + ) + end, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported CurrentZoneSensitivityLevel 1 should be Low", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.CurrentZoneSensitivityLevel:build_test_attr_report(mock_device, + 1) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.Low()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported CurrentZoneSensitivityLevel 2 should be Medium", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.CurrentZoneSensitivityLevel:build_test_attr_report(mock_device, + 2) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.Medium()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported CurrentZoneSensitivityLevel 3 should be High", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.CurrentZoneSensitivityLevel:build_test_attr_report(mock_device, + 3) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.High()) + } + }, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability sensitivityAdjustment High should be handled", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "stse.sensitivityAdjustment", component = "main", command = "setSensitivityAdjustment", args = {"High"} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:write(mock_device, PREF_SENSITIVITY_VALUE_HIGH) }) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:read(mock_device) }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability sensitivityAdjustment Medium should be handled", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "stse.sensitivityAdjustment", component = "main", command = "setSensitivityAdjustment", args = {"Medium"} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:write(mock_device, PREF_SENSITIVITY_VALUE_MEDIUM) }) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:read(mock_device) }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability sensitivityAdjustment Low should be handled", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "stse.sensitivityAdjustment", component = "main", command = "setSensitivityAdjustment", args = {"Low"} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:write(mock_device, PREF_SENSITIVITY_VALUE_LOW) }) + test.socket.zigbee:__expect_send({ mock_device.id, + IASZone.attributes.CurrentZoneSensitivityLevel:read(mock_device) }) + end, + { + min_api_version = 19 + } +) + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 22a1512d50..63f5f93a42 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -134,6 +134,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ "HAOJAI Smart Switch 3-key",好家智能三键开关 "HAOJAI Smart Switch 6-key",好家智能六键开关 +"MultiIR Motion Detector MIR-IR100",麦乐克人体移动传感器MIR-IR100 "MultiIR Water Leak Sensor MIR-WA100",麦乐克水浸传感器MIR-WA100 "MultiIR Contact Sensor MIR-MC100",麦乐克门窗开关传感器MIR-MC100 "MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100 From d1a0489fa249ca2774c123ec3c609777778ffcca Mon Sep 17 00:00:00 2001 From: LQ107 Date: Wed, 22 Apr 2026 00:05:30 +0800 Subject: [PATCH 263/538] WWSTCERT-10189 Ledvance zigbee meter plug (#2729) --- .../zigbee-switch/fingerprints.yml | 5 ++ .../src/ledvance-metering-plug/can_handle.lua | 13 +++++ .../ledvance-metering-plug/fingerprints.lua | 6 ++ .../src/ledvance-metering-plug/init.lua | 23 ++++++++ .../zigbee-switch/src/sub_drivers.lua | 1 + .../src/test/test_ledvance_metering_plug.lua | 55 +++++++++++++++++++ 6 files changed, 103 insertions(+) create mode 100644 drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/init.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 8765be8aeb..db44b09caa 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -1730,6 +1730,11 @@ zigbeeManufacturer: manufacturer: LEDVANCE model: RT TW deviceProfileName: color-temp-bulb + - id: "LEDVANCE/PLUG COMPACT EU EM T" + deviceLabel: SMART ZIGBEE COMPACT OUTDOOR PLUG EU + manufacturer: LEDVANCE + model: PLUG COMPACT EU EM T + deviceProfileName: switch-power-energy - id: "OSRAM/LIGHTIFY Edge-lit flushmount" deviceLabel: SYLVANIA Light manufacturer: OSRAM diff --git a/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/can_handle.lua new file mode 100644 index 0000000000..2d7f42bac8 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require("ledvance-metering-plug.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("ledvance-metering-plug") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/fingerprints.lua new file mode 100644 index 0000000000..e514269f82 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "LEDVANCE", model = "PLUG COMPACT EU EM T" } +} diff --git a/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/init.lua b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/init.lua new file mode 100644 index 0000000000..e907f25a28 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/init.lua @@ -0,0 +1,23 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local zigbee_constants = require "st.zigbee.constants" + +local function device_init(driver, device) + if device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == nil then + device:set_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY, 1, {persist = true}) + end + if device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == nil then + device:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, 100, {persist = true}) + end +end + +local ledvance_metering_plug = { + NAME = "LEDVANCE Metering Plug", + lifecycle_handlers = { + init = device_init + }, + can_handle = require("ledvance-metering-plug.can_handle") +} + +return ledvance_metering_plug diff --git a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua index 5dcf24ca74..07d1fd9c7a 100644 --- a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua @@ -14,6 +14,7 @@ return { lazy_load_if_possible("sinope"), lazy_load_if_possible("sinope-dimmer"), lazy_load_if_possible("zigbee-dimmer-power-energy"), + lazy_load_if_possible("ledvance-metering-plug"), lazy_load_if_possible("zigbee-metering-plug-power-consumption-report"), lazy_load_if_possible("jasco"), lazy_load_if_possible("multi-switch-no-master"), diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua b/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua new file mode 100644 index 0000000000..a2087797d4 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua @@ -0,0 +1,55 @@ +-- Copyright 2026 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" +local t_utils = require "integration_test.utils" +local zigbee_constants = require "st.zigbee.constants" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("switch-power-energy.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LEDVANCE", + model = "PLUG COMPACT EU EM T", + server_clusters = { 0x0006, 0x0702 } + } + } + } +) + +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( + "Device init should set default multiplier and divisor only when not already set", + function() + assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == nil) + assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == nil) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.wait_for_events() + assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == 1) + assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 100) + end +) + +test.register_coroutine_test( + "Device init should preserve device-reported multiplier and divisor", + function() + mock_device:set_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY, 5, {persist = true}) + mock_device:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, 1000, {persist = true}) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.wait_for_events() + assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == 5) + assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 1000) + end +) + +test.run_registered_tests() From cd25371e56e3e0d26a022c2ad823f6966a7d8e47 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 16 Apr 2026 11:52:44 -0500 Subject: [PATCH 264/538] WWSTCERT-11013 SMART WIFI MATTER WALL SWITCH 2G (#2905) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 852a67432d..b764c79f2c 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -894,6 +894,11 @@ matterManufacturer: vendorId: 0x1189 productId: 0x0633 deviceProfileName: plug-binary + - id: "4489/2193" + deviceLabel: SMART WIFI MATTER WALL SWITCH 2G + vendorId: 0x1189 + productId: 0x0891 + deviceProfileName: switch-binary #Ikea - id: "4476/32768" deviceLabel: BILRESA Scroll Wheel From 14d06f745c90d5ef5d95ce3384f6ba4befc7c969 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Fri, 10 Apr 2026 11:48:55 -0500 Subject: [PATCH 265/538] WWSTCERT-10652 ULTRALOQ Matter Door Lock (#2889) --- drivers/SmartThings/matter-lock/fingerprints.yml | 10 ++++++++++ .../matter-lock/src/new-matter-lock/fingerprints.lua | 2 ++ 2 files changed, 12 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 29c37dd69c..0c12932a2b 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -140,6 +140,16 @@ matterManufacturer: vendorId: 0x147F productId: 0x0008 deviceProfileName: lock-user-pin-battery + - id: "5247/7" + deviceLabel: ULTRALOQ Bolt Pro Smart Matter Door Lock + vendorId: 0x147F + productId: 0x0007 + deviceProfileName: lock-modular + - id: "5247/16" + deviceLabel: ULTRALOQ Latch 5 Pro Smart Matter Door Lock + vendorId: 0x147F + productId: 0x0010 + deviceProfileName: lock-modular #Yale - id: "4125/33040" deviceLabel: Yale Lock with Matter diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index ac0352c75a..901c0ea39c 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -8,7 +8,9 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x115f, 0x2804}, -- AQARA, U400 {0x115f, 0x286A}, -- AQARA, U200 US {0x147F, 0x0001}, -- U-tec + {0x147F, 0x0007}, -- ULTRALOQ Bolt Pro Smart Matter Door Lock {0x147F, 0x0008}, -- Ultraloq, Bolt Smart Matter Door Lock + {0x147F, 0x0010}, -- ULTRALOQ Latch 5 Pro Smart Matter Door Lock {0x144F, 0x4002}, -- Yale, Linus Smart Lock L2 {0x101D, 0x8110}, -- Yale, New Lock {0x1533, 0x0001}, -- eufy, E31 From db1a7d44f4d30cf12b41ca74a3fb687f7874ab6a Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 21 Apr 2026 10:49:50 -0500 Subject: [PATCH 266/538] WWSTCERT-10993 Linkind Smart Ceiling Light (#2901) --- drivers/SmartThings/matter-switch/fingerprints.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index b764c79f2c..cc975d0138 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -161,6 +161,11 @@ matterManufacturer: vendorId: 0x1396 productId: 0x1076 deviceProfileName: light-color-level + - id: "5014/4245" + deviceLabel: OREiN Matter smart Bathroom Fan + vendorId: 0x1396 + productId: 0x1095 + deviceProfileName: fan-modular - id: "5014/4246" deviceLabel: OREiN Matter smart Bathroom Fan vendorId: 0x1396 @@ -176,6 +181,11 @@ matterManufacturer: vendorId: 0x1396 productId: 0x1077 deviceProfileName: light-color-level + - id: "5014/4273" + deviceLabel: Linkind Smart Ceiling Light + vendorId: 0x1396 + productId: 0x10B1 + deviceProfileName: light-level-colorTemperature #Bosch Smart Home - id: "4617/12310" deviceLabel: Plug Compact [M] From 3d1287dab060800b4435b8a0e38e3f99ee413378 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 21 Apr 2026 15:08:29 -0500 Subject: [PATCH 267/538] WWSTCERT-10842 ThirdReality Smart Night Light -T (#2910) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index a913b477aa..55f3fc945c 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -204,6 +204,11 @@ matterManufacturer: vendorId: 0x1407 productId: 0x1088 deviceProfileName: light-color-level-illuminance-motion-1000K-15000K + - id: "5127/5121" + deviceLabel: ThirdReality Smart Night Light -T + vendorId: 0x1407 + productId: 0x1401 + deviceProfileName: light-color-level-illuminance-motion - id: "5127/4744" deviceLabel: Smart Bridge MZ1 vendorId: 0x1407 From a0df76db04363a70a1cd4ada8bafedeff17cc5a8 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 21 Apr 2026 15:08:29 -0500 Subject: [PATCH 268/538] WWSTCERT-10842 ThirdReality Smart Night Light -T (#2910) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index cc975d0138..132f792085 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -204,6 +204,11 @@ matterManufacturer: vendorId: 0x1407 productId: 0x1088 deviceProfileName: light-color-level-illuminance-motion-1000K-15000K + - id: "5127/5121" + deviceLabel: ThirdReality Smart Night Light -T + vendorId: 0x1407 + productId: 0x1401 + deviceProfileName: light-color-level-illuminance-motion - id: "5127/4744" deviceLabel: Smart Bridge MZ1 vendorId: 0x1407 From 84a60d4f05e5aa05c56e6be02dfbe3732fd9e83b Mon Sep 17 00:00:00 2001 From: cjswedes Date: Wed, 22 Apr 2026 09:24:42 -0500 Subject: [PATCH 269/538] Fix ledvance unit tests Device init messages must be disabled before adding the mock device to the test framework for these tests. --- .../src/test/test_ledvance_metering_plug.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua b/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua index a2087797d4..6e63bba1a9 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua @@ -23,6 +23,7 @@ local mock_device = test.mock_device.build_test_zigbee_device( zigbee_test_utils.prepare_zigbee_env_info() local function test_init() + test.disable_startup_messages() test.mock_device.add_test_device(mock_device) end @@ -37,7 +38,10 @@ test.register_coroutine_test( test.wait_for_events() assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == 1) assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 100) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -49,7 +53,10 @@ test.register_coroutine_test( test.wait_for_events() assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == 5) assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 1000) - end + end, + { + min_api_version = 15 + } ) test.run_registered_tests() From bf0e08b5714a4a171b0f0d926c21f2ffe83497f6 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:06:19 -0500 Subject: [PATCH 270/538] Add Stateless Native Handler Registration (#2914) --- .../switch_handlers/capability_handlers.lua | 6 +++ .../src/test/test_stateless_step.lua | 48 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua index eec323a335..9bf402c8ad 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua @@ -50,6 +50,9 @@ end -- [[ STATELESS SWITCH LEVEL STEP CAPABILITY COMMANDS ]] -- function CapabilityHandlers.handle_step_level(driver, device, cmd) + if type(device.register_native_capability_cmd_handler) == "function" then + device:register_native_capability_cmd_handler(cmd.capability, cmd.command) + end local step_size = st_utils.round((cmd.args and cmd.args.stepSize or 0)/100.0 * 254) if step_size == 0 then return end local endpoint_id = device:component_to_endpoint(cmd.component) @@ -123,6 +126,9 @@ end -- [[ STATELESS COLOR TEMPERATURE STEP CAPABILITY COMMANDS ]] -- function CapabilityHandlers.handle_step_color_temperature_by_percent(driver, device, cmd) + if type(device.register_native_capability_cmd_handler) == "function" then + device:register_native_capability_cmd_handler(cmd.capability, cmd.command) + end local step_percent_change = cmd.args and cmd.args.stepSize or 0 if step_percent_change == 0 then return end step_percent_change = st_utils.clamp_value(step_percent_change, -100, 100) diff --git a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua index 8d05070efc..1022acd795 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua @@ -62,6 +62,14 @@ test.register_message_test( { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { 20 } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device_color_temp.id, capability_id = "statelessColorTemperatureStep", capability_cmd_id = "stepColorTemperatureByPercent" } + } + }, { channel = "matter", direction = "send", @@ -78,6 +86,14 @@ test.register_message_test( { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { 90 } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device_color_temp.id, capability_id = "statelessColorTemperatureStep", capability_cmd_id = "stepColorTemperatureByPercent" } + } + }, { channel = "matter", direction = "send", @@ -94,6 +110,14 @@ test.register_message_test( { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { -50 } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device_color_temp.id, capability_id = "statelessColorTemperatureStep", capability_cmd_id = "stepColorTemperatureByPercent" } + } + }, { channel = "matter", direction = "send", @@ -120,6 +144,14 @@ test.register_message_test( { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 25 } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device_color_temp.id, capability_id = "statelessSwitchLevelStep", capability_cmd_id = "stepLevel" } + } + }, { channel = "matter", direction = "send", @@ -136,6 +168,14 @@ test.register_message_test( { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { -50 } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device_color_temp.id, capability_id = "statelessSwitchLevelStep", capability_cmd_id = "stepLevel" } + } + }, { channel = "matter", direction = "send", @@ -152,6 +192,14 @@ test.register_message_test( { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 100 } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device_color_temp.id, capability_id = "statelessSwitchLevelStep", capability_cmd_id = "stepLevel" } + } + }, { channel = "matter", direction = "send", From 56083499d5f1591fc7d945a5487da1edb39e8d10 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:27 -0600 Subject: [PATCH 271/538] CHAD-17092: zwave-sensor lazy loading of sub-drivers --- .../src/aeotec-multisensor/can_handle.lua | 15 ++++++ .../src/aeotec-multisensor/fingerprints.lua | 9 ++++ .../src/aeotec-multisensor/init.lua | 37 ++------------ .../multisensor-6/can_handle.lua | 11 ++++ .../aeotec-multisensor/multisensor-6/init.lua | 23 ++------- .../multisensor-7/can_handle.lua | 12 +++++ .../aeotec-multisensor/multisensor-7/init.lua | 24 ++------- .../src/aeotec-multisensor/sub_drivers.lua | 9 ++++ .../src/aeotec-water-sensor/can_handle.lua | 15 ++++++ .../src/aeotec-water-sensor/fingerprints.lua | 11 ++++ .../src/aeotec-water-sensor/init.lua | 34 ++----------- .../src/apiv6_bugfix/can_handle.lua | 35 +++++++++++++ .../zwave-sensor/src/apiv6_bugfix/init.lua | 33 ++---------- .../zwave-sensor/src/configurations.lua | 16 ++---- .../src/enerwave-motion-sensor/can_handle.lua | 12 +++++ .../src/enerwave-motion-sensor/init.lua | 27 ++-------- .../can_handle.lua | 17 +++++++ .../everspring-motion-light-sensor/init.lua | 32 ++---------- .../can_handle.lua | 15 ++++++ .../ezmultipli-multipurpose-sensor/init.lua | 32 +++--------- .../fibaro-door-window-sensor/can_handle.lua | 15 ++++++ .../can_handle.lua | 14 ++++++ .../fingerprints.lua | 8 +++ .../fibaro-door-window-sensor-1/init.lua | 30 ++--------- .../can_handle.lua | 14 ++++++ .../fingerprints.lua | 10 ++++ .../fibaro-door-window-sensor-2/init.lua | 32 ++---------- .../fingerprints.lua | 15 ++++++ .../src/fibaro-door-window-sensor/init.lua | 43 ++-------------- .../fibaro-door-window-sensor/sub_drivers.lua | 9 ++++ .../src/fibaro-flood-sensor/can_handle.lua | 14 ++++++ .../src/fibaro-flood-sensor/init.lua | 30 ++--------- .../src/fibaro-motion-sensor/can_handle.lua | 15 ++++++ .../src/fibaro-motion-sensor/init.lua | 29 ++--------- .../src/firmware-version/can_handle.lua | 21 ++++++++ .../src/firmware-version/init.lua | 34 ++----------- .../can_handle.lua | 20 ++++++++ .../glentronics-water-leak-sensor/init.lua | 36 ++----------- .../src/homeseer-multi-sensor/can_handle.lua | 20 ++++++++ .../src/homeseer-multi-sensor/init.lua | 36 ++----------- drivers/SmartThings/zwave-sensor/src/init.lua | 50 ++----------------- .../zwave-sensor/src/lazy_load_subdriver.lua | 18 +++++++ .../zwave-sensor/src/preferences.lua | 16 ++---- .../src/sensative-strip/can_handle.lua | 14 ++++++ .../zwave-sensor/src/sensative-strip/init.lua | 27 ++-------- .../zwave-sensor/src/sub_drivers.lua | 26 ++++++++++ .../src/test/test_aeon_multisensor.lua | 16 ++---- .../src/test/test_aeotec_multisensor_6.lua | 16 ++---- .../src/test/test_aeotec_multisensor_7.lua | 16 ++---- .../src/test/test_aeotec_multisensor_gen5.lua | 16 ++---- .../src/test/test_aeotec_water_sensor.lua | 16 ++---- .../src/test/test_aeotec_water_sensor_7.lua | 16 ++---- .../src/test/test_enerwave_motion_sensor.lua | 16 ++---- .../src/test/test_everpsring_sp817.lua | 16 ++---- .../src/test/test_everspring_PIR_sensor.lua | 16 ++---- .../src/test/test_everspring_ST814.lua | 16 ++---- .../test_everspring_illuminance_sensor.lua | 16 ++---- .../test_everspring_motion_light_sensor.lua | 16 ++---- .../test_ezmultipli_multipurpose_sensor.lua | 16 ++---- .../test/test_fibaro_door_window_sensor.lua | 16 ++---- .../test/test_fibaro_door_window_sensor_1.lua | 16 ++---- .../test/test_fibaro_door_window_sensor_2.lua | 16 ++---- ...ro_door_window_sensor_with_temperature.lua | 16 ++---- .../src/test/test_fibaro_flood_sensor.lua | 16 ++---- .../src/test/test_fibaro_flood_sensor_zw5.lua | 16 ++---- .../src/test/test_fibaro_motion_sensor.lua | 16 ++---- .../test/test_fibaro_motion_sensor_zw5.lua | 16 ++---- .../src/test/test_generic_sensor.lua | 16 ++---- .../test_glentronics_water_leak_sensor.lua | 16 ++---- .../src/test/test_homeseer_multi_sensor.lua | 16 ++---- .../src/test/test_no_wakeup_poll.lua | 17 ++----- .../src/test/test_sensative_strip.lua | 16 ++---- .../test_smartthings_water_leak_sensor.lua | 16 ++---- .../src/test/test_v1_contact_event.lua | 16 ++---- .../src/test/test_vision_motion_detector.lua | 16 ++---- .../src/test/test_zooz_4_in_1_sensor.lua | 16 ++---- .../test/test_zwave_motion_light_sensor.lua | 16 ++---- .../test_zwave_motion_temp_light_sensor.lua | 16 ++---- .../src/test/test_zwave_sensor.lua | 16 ++---- .../src/test/test_zwave_water_sensor.lua | 16 ++---- .../src/timed-tamper-clear/can_handle.lua | 23 +++++++++ .../src/timed-tamper-clear/init.lua | 36 ++----------- .../src/v1-contact-event/can_handle.lua | 22 ++++++++ .../src/v1-contact-event/init.lua | 31 ++---------- .../src/vision-motion-detector/can_handle.lua | 17 +++++++ .../src/vision-motion-detector/init.lua | 33 ++---------- .../src/wakeup-no-poll/can_handle.lua | 12 +++++ .../zwave-sensor/src/wakeup-no-poll/init.lua | 32 +++--------- .../src/zooz-4-in-1-sensor/can_handle.lua | 15 ++++++ .../src/zooz-4-in-1-sensor/fingerprints.lua | 10 ++++ .../src/zooz-4-in-1-sensor/init.lua | 33 ++---------- .../zwave-water-leak-sensor/can_handle.lua | 15 ++++++ .../zwave-water-leak-sensor/fingerprints.lua | 19 +++++++ .../src/zwave-water-leak-sensor/init.lua | 43 ++-------------- 94 files changed, 741 insertions(+), 1160 deletions(-) create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua new file mode 100644 index 0000000000..d9956517dd --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_aeotec_multisensor(opts, self, device, ...) + local FINGERPRINTS = require("aeotec-multisensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("aeotec-multisensor") + return true, subdriver + end + end + return false +end + +return can_handle_aeotec_multisensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua new file mode 100644 index 0000000000..9436a85979 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_MULTISENSOR_FINGERPRINTS = { + { manufacturerId = 0x0086, productId = 0x0064 }, -- MultiSensor 6 + { manufacturerId = 0x0371, productId = 0x0018 }, -- MultiSensor 7 +} + +return AEOTEC_MULTISENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua index edd01c7553..d1759a41e0 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -18,21 +7,6 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) -local AEOTEC_MULTISENSOR_FINGERPRINTS = { - { manufacturerId = 0x0086, productId = 0x0064 }, -- MultiSensor 6 - { manufacturerId = 0x0371, productId = 0x0018 }, -- MultiSensor 7 -} - -local function can_handle_aeotec_multisensor(opts, self, device, ...) - for _, fingerprint in ipairs(AEOTEC_MULTISENSOR_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("aeotec-multisensor") - return true, subdriver - end - end - return false -end - local function notification_report_handler(self, device, cmd) local event if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then @@ -61,12 +35,9 @@ local aeotec_multisensor = { [Notification.REPORT] = notification_report_handler } }, - sub_drivers = { - require("aeotec-multisensor/multisensor-6"), - require("aeotec-multisensor/multisensor-7") - }, + sub_drivers = require("aeotec-multisensor.sub_drivers"), NAME = "aeotec multisensor", - can_handle = can_handle_aeotec_multisensor + can_handle = require("aeotec-multisensor.can_handle"), } return aeotec_multisensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua new file mode 100644 index 0000000000..d86e9c8b3a --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_multisensor_6(opts, self, device, ...) +local MULTISENSOR_6_PRODUCT_ID = 0x0064 + if device.zwave_product_id == MULTISENSOR_6_PRODUCT_ID then + return true, require("aeotec-multisensor.multisensor-6") + end + return false +end +return can_handle_multisensor_6 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua index 1b9d4d6b97..4174b3b14e 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -19,12 +10,8 @@ local cc = require "st.zwave.CommandClass" local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) local WakeUp = (require "st.zwave.CommandClass.WakeUp")({version = 2}) -local MULTISENSOR_6_PRODUCT_ID = 0x0064 local PREFERENCE_NUM = 9 -local function can_handle_multisensor_6(opts, self, device, ...) - return device.zwave_product_id == MULTISENSOR_6_PRODUCT_ID -end local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher @@ -62,7 +49,7 @@ local multisensor_6 = { } }, NAME = "aeotec multisensor 6", - can_handle = can_handle_multisensor_6 + can_handle = require("aeotec-multisensor.multisensor-6.can_handle"), } return multisensor_6 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua new file mode 100644 index 0000000000..f109d0e31c --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_multisensor_7(opts, self, device, ...) + local MULTISENSOR_7_PRODUCT_ID = 0x0018 + if device.zwave_product_id == MULTISENSOR_7_PRODUCT_ID then + return true, require("aeotec-multisensor.multisensor-7") + end + return false +end + +return can_handle_multisensor_7 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua index 2d2bf4e36e..c3dc69178f 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -19,13 +10,8 @@ local cc = require "st.zwave.CommandClass" local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 2 }) -local MULTISENSOR_7_PRODUCT_ID = 0x0018 local PREFERENCE_NUM = 10 -local function can_handle_multisensor_7(opts, self, device, ...) - return device.zwave_product_id == MULTISENSOR_7_PRODUCT_ID -end - local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher --This is done to help the hub correctly set the checkInterval for migrated devices. @@ -62,7 +48,7 @@ local multisensor_7 = { } }, NAME = "aeotec multisensor 7", - can_handle = can_handle_multisensor_7 + can_handle = require("aeotec-multisensor.multisensor-7.can_handle"), } return multisensor_7 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/sub_drivers.lua new file mode 100644 index 0000000000..396f53fe86 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/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("aeotec-multisensor/multisensor-6"), + lazy_load_if_possible("aeotec-multisensor/multisensor-7"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua new file mode 100644 index 0000000000..1b87febb74 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_zwave_water_temp_humidity_sensor(opts, driver, device, ...) + local FINGERPRINTS = require("aeotec-water-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("aeotec-water-sensor") + return true, subdriver + end + end + return false +end + +return can_handle_zwave_water_temp_humidity_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua new file mode 100644 index 0000000000..423d87754e --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS = { + { manufacturerId = 0x0371, productType = 0x0002, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro EU + { manufacturerId = 0x0371, productType = 0x0102, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro US + { manufacturerId = 0x0371, productType = 0x0202, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro AU + { manufacturerId = 0x0371, productId = 0x0012 } -- Aeotec Water Sensor 7 +} + +return ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua index 9d883ea3c2..4c7a86e708 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -18,23 +9,8 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) -local ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS = { - { manufacturerId = 0x0371, productType = 0x0002, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro EU - { manufacturerId = 0x0371, productType = 0x0102, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro US - { manufacturerId = 0x0371, productType = 0x0202, productId = 0x0013 }, -- Aeotec Water Sensor 7 Pro AU - { manufacturerId = 0x0371, productId = 0x0012 } -- Aeotec Water Sensor 7 -} --- Determine whether the passed device is zwave water temperature humidiry sensor -local function can_handle_zwave_water_temp_humidity_sensor(opts, driver, device, ...) - for _, fingerprint in ipairs(ZWAVE_WATER_TEMP_HUMIDITY_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("aeotec-water-sensor") - return true, subdriver - end - end - return false -end --- Default handler for notification command class reports --- @@ -68,7 +44,7 @@ local zwave_water_temp_humidity_sensor = { }, }, NAME = "zwave water temp humidity sensor", - can_handle = can_handle_zwave_water_temp_humidity_sensor + can_handle = require("aeotec-water-sensor.can_handle"), } return zwave_water_temp_humidity_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua new file mode 100644 index 0000000000..4913e9a25e --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua @@ -0,0 +1,35 @@ +-- 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 }) + +-- doing refresh would cause incorrect state for device, see comments in wakeup-no-poll +local NORTEK_FP = {mfr = 0x014F, prod = 0x2001, model = 0x0102} -- NorTek open/close sensor +local POPP_THERMOSTAT_FP = {mfr = 0x0002, prod = 0x0115, model = 0xA010} --Popp thermostat +local AEOTEC_MULTISENSOR_6_FP = {mfr = 0x0086, model = 0x0064} --Aeotec multisensor 6 +local AEOTEC_MULTISENSOR_7_FP = {mfr = 0x0371, model = 0x0018} --Aeotec multisensor 7 +local ENERWAVE_MOTION_FP = {mfr = 0x011A} --Enerwave motion sensor +local HOMESEER_MULTI_SENSOR_FP = {mfr = 0x001E, prod = 0x0002, model = 0x0001} -- Homeseer multi sensor HSM100 +local SENSATIVE_STRIP_FP = {mfr = 0x019A, model = 0x000A} +local FPS = {NORTEK_FP, POPP_THERMOSTAT_FP, + AEOTEC_MULTISENSOR_6_FP, AEOTEC_MULTISENSOR_7_FP, + ENERWAVE_MOTION_FP, HOMESEER_MULTI_SENSOR_FP, SENSATIVE_STRIP_FP} + +local function can_handle(opts, driver, device, cmd, ...) + local version = require "version" + if version.api == 6 and + cmd.cmd_class == cc.WAKE_UP and + cmd.cmd_id == WakeUp.NOTIFICATION then + + for _, fp in ipairs(FPS) do + if device:id_match(fp.mfr, fp.prod, fp.model) then return false end + end + local subdriver = require("apiv6_bugfix") + return true, subdriver + else + return false + end +end + +return can_handle diff --git a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua index 322333d565..94dc5975ab 100644 --- a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua @@ -1,34 +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 }) --- doing refresh would cause incorrect state for device, see comments in wakeup-no-poll -local NORTEK_FP = {mfr = 0x014F, prod = 0x2001, model = 0x0102} -- NorTek open/close sensor -local POPP_THERMOSTAT_FP = {mfr = 0x0002, prod = 0x0115, model = 0xA010} --Popp thermostat -local AEOTEC_MULTISENSOR_6_FP = {mfr = 0x0086, model = 0x0064} --Aeotec multisensor 6 -local AEOTEC_MULTISENSOR_7_FP = {mfr = 0x0371, model = 0x0018} --Aeotec multisensor 7 -local ENERWAVE_MOTION_FP = {mfr = 0x011A} --Enerwave motion sensor -local HOMESEER_MULTI_SENSOR_FP = {mfr = 0x001E, prod = 0x0002, model = 0x0001} -- Homeseer multi sensor HSM100 -local SENSATIVE_STRIP_FP = {mfr = 0x019A, model = 0x000A} -local FPS = {NORTEK_FP, POPP_THERMOSTAT_FP, - AEOTEC_MULTISENSOR_6_FP, AEOTEC_MULTISENSOR_7_FP, - ENERWAVE_MOTION_FP, HOMESEER_MULTI_SENSOR_FP, SENSATIVE_STRIP_FP} - -local function can_handle(opts, driver, device, cmd, ...) - local version = require "version" - if version.api == 6 and - cmd.cmd_class == cc.WAKE_UP and - cmd.cmd_id == WakeUp.NOTIFICATION then - - for _, fp in ipairs(FPS) do - if device:id_match(fp.mfr, fp.prod, fp.model) then return false end - end - local subdriver = require("apiv6_bugfix") - return true, subdriver - else - return false - end -end - local function wakeup_notification(driver, device, cmd) device:refresh() end @@ -40,7 +15,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-sensor/src/configurations.lua b/drivers/SmartThings/zwave-sensor/src/configurations.lua index 2883e70384..0a3c62ead8 100644 --- a/drivers/SmartThings/zwave-sensor/src/configurations.lua +++ b/drivers/SmartThings/zwave-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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass.Configuration diff --git a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua new file mode 100644 index 0000000000..8ab0bac6bc --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_enerwave_motion_sensor(opts, driver, device, cmd, ...) + local ENERWAVE_MFR = 0x011A + if device.zwave_manufacturer_id == ENERWAVE_MFR then + local subdriver = require("enerwave-motion-sensor") + return true, subdriver + else return false end +end + +return can_handle_enerwave_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua index 6fb712e3b0..012a8884a3 100644 --- a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-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 @@ -20,15 +10,6 @@ local Association = (require "st.zwave.CommandClass.Association")({version=2}) --- @type st.zwave.CommandClass.WakeUp local WakeUp = (require "st.zwave.CommandClass.WakeUp")({version=1}) -local ENERWAVE_MFR = 0x011A - -local function can_handle_enerwave_motion_sensor(opts, driver, device, cmd, ...) - if device.zwave_manufacturer_id == ENERWAVE_MFR then - local subdriver = require("enerwave-motion-sensor") - return true, subdriver - else return false end -end - local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher --This is done to help the hub correctly set the checkInterval for migrated devices. @@ -58,7 +39,7 @@ local enerwave_motion_sensor = { doConfigure = do_configure }, NAME = "enerwave_motion_sensor", - can_handle = can_handle_enerwave_motion_sensor + can_handle = require("enerwave-motion-sensor.can_handle") } return enerwave_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua new file mode 100644 index 0000000000..c9fd2eafd1 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_everspring_motion_light(opts, driver, device, ...) + local EVERSPRING_MOTION_LIGHT_FINGERPRINT = { mfr = 0x0060, prod = 0x0012, model = 0x0001 } + if device:id_match( + EVERSPRING_MOTION_LIGHT_FINGERPRINT.mfr, + EVERSPRING_MOTION_LIGHT_FINGERPRINT.prod, + EVERSPRING_MOTION_LIGHT_FINGERPRINT.model + ) then + local subdriver = require("everspring-motion-light-sensor") + return true, subdriver + end + return false +end + +return can_handle_everspring_motion_light diff --git a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua index 1b11aadabe..8baa3756d6 100644 --- a/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/everspring-motion-light-sensor/init.lua @@ -1,34 +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 capabilities = require "st.capabilities" local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=2,strict=true}) local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({version=2}) -local EVERSPRING_MOTION_LIGHT_FINGERPRINT = { mfr = 0x0060, prod = 0x0012, model = 0x0001 } - -local function can_handle_everspring_motion_light(opts, driver, device, ...) - if device:id_match( - EVERSPRING_MOTION_LIGHT_FINGERPRINT.mfr, - EVERSPRING_MOTION_LIGHT_FINGERPRINT.prod, - EVERSPRING_MOTION_LIGHT_FINGERPRINT.model - ) then - local subdriver = require("everspring-motion-light-sensor") - return true, subdriver - else return false end -end - local function device_added(driver, device) device:emit_event(capabilities.motionSensor.motion.inactive()) device:send(SwitchBinary:Get({})) @@ -40,7 +18,7 @@ local everspring_motion_light = { lifecycle_handlers = { added = device_added }, - can_handle = can_handle_everspring_motion_light + can_handle = require("everspring-motion-light-sensor.can_handle"), } return everspring_motion_light diff --git a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua new file mode 100644 index 0000000000..5596894fbb --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_ezmultipli_multipurpose_sensor(opts, driver, device, ...) + local EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0004, productId = 0x0001 } + if device:id_match(EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.manufacturerId, + EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productType, + EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productId) then + local subdriver = require("ezmultipli-multipurpose-sensor") + return true, subdriver + end + return false +end + +return can_handle_ezmultipli_multipurpose_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua index 1e4b3bf0ce..f4cac30faa 100644 --- a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/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 capabilities = require "st.capabilities" --- @type st.utils @@ -28,17 +19,6 @@ local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=2}) local CAP_CACHE_KEY = "st.capabilities." .. capabilities.colorControl.ID -local EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0004, productId = 0x0001 } - -local function can_handle_ezmultipli_multipurpose_sensor(opts, driver, device, ...) - if device:id_match(EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.manufacturerId, - EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productType, - EZMULTIPLI_MULTIPURPOSE_SENSOR_FINGERPRINTS.productId) then - local subdriver = require("ezmultipli-multipurpose-sensor") - return true, subdriver - else return false end -end - local function basic_report_handler(driver, device, cmd) local event local value = (cmd.args.target_value ~= nil) and cmd.args.target_value or cmd.args.value @@ -102,7 +82,7 @@ local ezmultipli_multipurpose_sensor = { [capabilities.colorControl.commands.setColor.NAME] = set_color } }, - can_handle = can_handle_ezmultipli_multipurpose_sensor + can_handle = require("ezmultipli-multipurpose-sensor.can_handle"), } -return ezmultipli_multipurpose_sensor \ No newline at end of file +return ezmultipli_multipurpose_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua new file mode 100644 index 0000000000..6cf9ebba98 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_door_window_sensor(opts, driver, device, ...) + local FINGERPRINTS = require("fibaro-door-window-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.prod, fingerprint.productId) then + local subdriver = require("fibaro-door-window-sensor") + return true, subdriver + end + end + return false +end + +return can_handle_fibaro_door_window_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua new file mode 100644 index 0000000000..992ea8fd9d --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_door_window_sensor_1(opts, driver, device, cmd, ...) + local FINGERPRINTS = require("fibaro-door-window-sensor.fibaro-door-window-sensor-1.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("fibaro-door-window-sensor.fibaro-door-window-sensor-1") + end + end + return false +end + +return can_handle_fibaro_door_window_sensor_1 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua new file mode 100644 index 0000000000..50727133bb --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS = { + { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } +} + +return FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua index 698fffcceb..4dbca58919 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/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 capabilities = require "st.capabilities" local cc = require "st.zwave.CommandClass" @@ -21,19 +10,6 @@ local SensorAlarm = (require "st.zwave.CommandClass.SensorAlarm")({ version = 1 local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 1 }) local configurationsMap = require "configurations" -local FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS = { - { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } -} - -local function can_handle_fibaro_door_window_sensor_1(opts, driver, device, cmd, ...) - for _, fingerprint in ipairs(FIBARO_DOOR_WINDOW_SENSOR_1_FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -end - local function sensor_alarm_report_handler(driver, device, cmd) if (cmd.args.sensor_state == SensorAlarm.sensor_state.ALARM) then device:emit_event(capabilities.tamperAlert.tamper.detected()) @@ -92,7 +68,7 @@ local fibaro_door_window_sensor_1 = { [capabilities.refresh.ID] = { [capabilities.refresh.commands.refresh.NAME] = do_refresh }, - can_handle = can_handle_fibaro_door_window_sensor_1 + can_handle = require("fibaro-door-window-sensor.fibaro-door-window-sensor-1.can_handle"), } return fibaro_door_window_sensor_1 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua new file mode 100644 index 0000000000..4493496f94 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_door_window_sensor_2(opts, driver, device, cmd, ...) + local FINGERPRINTS = require("fibaro-door-window-sensor.fibaro-door-window-sensor-2.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("fibaro-door-window-sensor.fibaro-door-window-sensor-2") + end + end + return false +end + +return can_handle_fibaro_door_window_sensor_2 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua new file mode 100644 index 0000000000..6103c107d1 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS = { + { manufacturerId = 0x010F, productType = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe + { manufacturerId = 0x010F, productType = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA + { manufacturerId = 0x010F, productType = 0x0702, productId = 0x3000 } -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ +} + +return FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua index 16c5ec2017..250203b0cd 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -18,21 +7,6 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Alarm local Alarm = (require "st.zwave.CommandClass.Alarm")({ version = 2 }) -local FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS = { - { manufacturerId = 0x010F, productType = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe - { manufacturerId = 0x010F, productType = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA - { manufacturerId = 0x010F, productType = 0x0702, productId = 0x3000 } -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ -} - -local function can_handle_fibaro_door_window_sensor_2(opts, driver, device, cmd, ...) - for _, fingerprint in ipairs(FIBARO_DOOR_WINDOW_SENSOR_2_FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -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) @@ -83,7 +57,7 @@ local fibaro_door_window_sensor_2 = { lifecycle_handlers = { added = device_added }, - can_handle = can_handle_fibaro_door_window_sensor_2, + can_handle = require("fibaro-door-window-sensor.fibaro-door-window-sensor-2.can_handle"), } return fibaro_door_window_sensor_2 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua new file mode 100644 index 0000000000..699df3f623 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fingerprints.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS = { + { manufacturerId = 0x010F, prod = 0x0700, productId = 0x1000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / Europe + { manufacturerId = 0x010F, prod = 0x0700, productId = 0x2000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / NA + { manufacturerId = 0x010F, prod = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe + { manufacturerId = 0x010F, prod = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA + { manufacturerId = 0x010F, prod = 0x0702, productId = 0x3000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ + { manufacturerId = 0x010F, prod = 0x0701, productId = 0x2001 }, -- Fibaro Open/Closed Sensor with temperature (FGK-10X) / NA + { manufacturerId = 0x010F, prod = 0x0701, productId = 0x1001 }, -- Fibaro Open/Closed Sensor + { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } -- Fibaro Open/Closed Sensor +} + +return FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua index 86cf865348..6c30508fd1 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/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 cc = require "st.zwave.CommandClass" local capabilities = require "st.capabilities" @@ -24,27 +13,6 @@ local preferencesMap = require "preferences" local FIBARO_DOOR_WINDOW_SENSOR_WAKEUP_INTERVAL = 21600 --seconds -local FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS = { - { manufacturerId = 0x010F, prod = 0x0700, productId = 0x1000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / Europe - { manufacturerId = 0x010F, prod = 0x0700, productId = 0x2000 }, -- Fibaro Open/Closed Sensor (FGK-10x) / NA - { manufacturerId = 0x010F, prod = 0x0702, productId = 0x1000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / Europe - { manufacturerId = 0x010F, prod = 0x0702, productId = 0x2000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / NA - { manufacturerId = 0x010F, prod = 0x0702, productId = 0x3000 }, -- Fibaro Open/Closed Sensor 2 (FGDW-002) / ANZ - { manufacturerId = 0x010F, prod = 0x0701, productId = 0x2001 }, -- Fibaro Open/Closed Sensor with temperature (FGK-10X) / NA - { manufacturerId = 0x010F, prod = 0x0701, productId = 0x1001 }, -- Fibaro Open/Closed Sensor - { manufacturerId = 0x010F, prod = 0x0501, productId = 0x1002 } -- Fibaro Open/Closed Sensor -} - -local function can_handle_fibaro_door_window_sensor(opts, driver, device, ...) - for _, fingerprint in ipairs(FIBARO_DOOR_WINDOW_SENSOR_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.prod, fingerprint.productId) then - local subdriver = require("fibaro-door-window-sensor") - return true, subdriver - end - end - return false -end - local function parameterNumberToParameterName(preferences,parameterNumber) for id, parameter in pairs(preferences) do if parameter.parameter_number == parameterNumber then @@ -154,11 +122,8 @@ local fibaro_door_window_sensor = { [capabilities.refresh.commands.refresh.NAME] = do_refresh } }, - sub_drivers = { - require("fibaro-door-window-sensor/fibaro-door-window-sensor-1"), - require("fibaro-door-window-sensor/fibaro-door-window-sensor-2") - }, - can_handle = can_handle_fibaro_door_window_sensor + sub_drivers = require("fibaro-door-window-sensor.sub_drivers"), + can_handle = require("fibaro-door-window-sensor.can_handle"), } return fibaro_door_window_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/sub_drivers.lua new file mode 100644 index 0000000000..0c4ddd4e43 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-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("fibaro-door-window-sensor/fibaro-door-window-sensor-1"), + lazy_load_if_possible("fibaro-door-window-sensor/fibaro-door-window-sensor-2"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua new file mode 100644 index 0000000000..341fbcd6f9 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_flood_sensor(opts, driver, device, ...) + local FIBARO_MFR_ID = 0x010F + local FIBARO_FLOOD_PROD_TYPES = { 0x0000, 0x0B00 } + if device:id_match(FIBARO_MFR_ID, FIBARO_FLOOD_PROD_TYPES, nil) then + local subdriver = require("fibaro-flood-sensor") + return true, subdriver + end + return false +end + +return can_handle_fibaro_flood_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua index 144be985ae..9a0a8e7eaa 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -26,17 +17,6 @@ local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = local preferences = require "preferences" local configurations = require "configurations" -local FIBARO_MFR_ID = 0x010F -local FIBARO_FLOOD_PROD_TYPES = { 0x0000, 0x0B00 } - -local function can_handle_fibaro_flood_sensor(opts, driver, device, ...) - if device:id_match(FIBARO_MFR_ID, FIBARO_FLOOD_PROD_TYPES, nil) then - local subdriver = require("fibaro-flood-sensor") - return true, subdriver - else return false end -end - - local function basic_set_handler(self, device, cmd) local value = cmd.args.target_value and cmd.args.target_value or cmd.args.value device:emit_event(value == 0xFF and capabilities.waterSensor.water.wet() or capabilities.waterSensor.water.dry()) @@ -96,7 +76,7 @@ local fibaro_flood_sensor = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_fibaro_flood_sensor + can_handle = require("fibaro-flood-sensor.can_handle"), } return fibaro_flood_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua new file mode 100644 index 0000000000..b184001935 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_motion_sensor(opts, driver, device, ...) + + local FIBARO_MOTION_MFR = 0x010F + local FIBARO_MOTION_PROD = 0x0800 + if device:id_match(FIBARO_MOTION_MFR, FIBARO_MOTION_PROD) then + local subdriver = require("fibaro-motion-sensor") + return true, subdriver + end + return false +end + +return can_handle_fibaro_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua index ae45f5a27b..ed035bde18 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-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 + --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -18,15 +8,6 @@ local cc = require "st.zwave.CommandClass" local SensorAlarm = (require "st.zwave.CommandClass.SensorAlarm")({ version = 1 }) local capabilities = require "st.capabilities" -local FIBARO_MOTION_MFR = 0x010F -local FIBARO_MOTION_PROD = 0x0800 - -local function can_handle_fibaro_motion_sensor(opts, driver, device, ...) - if device:id_match(FIBARO_MOTION_MFR, FIBARO_MOTION_PROD) then - local subdriver = require("fibaro-motion-sensor") - return true, subdriver - else return false end -end local function sensor_alarm_report(driver, device, cmd) if (cmd.args.sensor_state ~= SensorAlarm.sensor_state.NO_ALARM) then @@ -43,7 +24,7 @@ local fibaro_motion_sensor = { [SensorAlarm.REPORT] = sensor_alarm_report } }, - can_handle = can_handle_fibaro_motion_sensor + can_handle = require("fibaro-motion-sensor.can_handle") } -return fibaro_motion_sensor \ No newline at end of file +return fibaro_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua new file mode 100644 index 0000000000..3ecdc2baf0 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/firmware-version/can_handle.lua @@ -0,0 +1,21 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" + +--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 +} + +return function(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 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua b/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua index 058a7f955c..528cf7da45 100644 --- a/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/firmware-version/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" --- @type st.zwave.CommandClass @@ -20,22 +10,6 @@ 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) @@ -73,7 +47,7 @@ end local firmware_version = { NAME = "firmware_version", - can_handle = can_handle_fw, + can_handle = require("firmware-version.can_handle"), lifecycle_handlers = { added = added_handler, diff --git a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua new file mode 100644 index 0000000000..e24d7b9cf2 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/can_handle.lua @@ -0,0 +1,20 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +--- Determine whether the passed device is glentronics water leak sensor +--- +--- @param driver Driver driver instance +--- @param device Device device isntance +--- @return boolean true if the device proper, else false +local function can_handle_glentronics_water_leak_sensor(opts, driver, device, ...) + local GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS = { manufacturerId = 0x0084, productType = 0x0093, productId = 0x0114 } -- glentronics water leak sensor + if device:id_match( + GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.manufacturerId, + GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productType, + GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productId) then + return true, require("glentronics-water-leak-sensor") + end + return false +end + +return can_handle_glentronics_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua index 3dba7351d6..7400d889b7 100644 --- a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -18,23 +9,6 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) -local GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS = { manufacturerId = 0x0084, productType = 0x0093, productId = 0x0114 } -- glentronics water leak sensor - ---- Determine whether the passed device is glentronics water leak sensor ---- ---- @param driver Driver driver instance ---- @param device Device device isntance ---- @return boolean true if the device proper, else false -local function can_handle_glentronics_water_leak_sensor(opts, driver, device, ...) - if device:id_match( - GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.manufacturerId, - GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productType, - GLENTRONICS_WATER_LEAK_SENSOR_FINGERPRINTS.productId) then - local subdriver = require("glentronics-water-leak-sensor") - return true, subdriver - else return false end -end - local function notification_report_handler(self, device, cmd) local event if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then @@ -78,7 +52,7 @@ local glentronics_water_leak_sensor = { added = device_added }, NAME = "glentronics water leak sensor", - can_handle = can_handle_glentronics_water_leak_sensor + can_handle = require("glentronics-water-leak-sensor.can_handle"), } return glentronics_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua new file mode 100644 index 0000000000..992c1f7c7f --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/can_handle.lua @@ -0,0 +1,20 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +--- Determine whether the passed device is homeseer multi sensor +--- +--- @param driver Driver driver instance +--- @param device Device device instance +--- @return boolean true if the device proper, else false +local function can_handle_homeseer_multi_sensor(opts, driver, device, ...) + local HOMESEER_MULTI_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0002, productId = 0x0001 } -- Homeseer multi sensor HSM100 + if device:id_match( + HOMESEER_MULTI_SENSOR_FINGERPRINTS.manufacturerId, + HOMESEER_MULTI_SENSOR_FINGERPRINTS.productType, + HOMESEER_MULTI_SENSOR_FINGERPRINTS.productId) then + return true, require("homeseer-multi-sensor") + end + return false +end + +return can_handle_homeseer_multi_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua index 2330f28106..f89e6a7870 100644 --- a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -22,23 +13,6 @@ local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({version = 5}) local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1}) -local HOMESEER_MULTI_SENSOR_FINGERPRINTS = { manufacturerId = 0x001E, productType = 0x0002, productId = 0x0001 } -- Homeseer multi sensor HSM100 - ---- Determine whether the passed device is homeseer multi sensor ---- ---- @param driver Driver driver instance ---- @param device Device device instance ---- @return boolean true if the device proper, else false -local function can_handle_homeseer_multi_sensor(opts, driver, device, ...) - if device:id_match( - HOMESEER_MULTI_SENSOR_FINGERPRINTS.manufacturerId, - HOMESEER_MULTI_SENSOR_FINGERPRINTS.productType, - HOMESEER_MULTI_SENSOR_FINGERPRINTS.productId) then - local subdriver = require("homeseer-multi-sensor") - return true, subdriver - else return false end -end - local function basic_set_handler(self, device, cmd) if cmd.args.value ~= nil then device:emit_event(cmd.args.value == 0xFF and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) @@ -87,7 +61,7 @@ local homeseer_multi_sensor = { init = device_init, }, NAME = "homeseer multi sensor", - can_handle = can_handle_homeseer_multi_sensor + can_handle = require("homeseer-multi-sensor.can_handle"), } return homeseer_multi_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/init.lua b/drivers/SmartThings/zwave-sensor/src/init.lua index 213aa8c389..2c18e4c2ed 100644 --- a/drivers/SmartThings/zwave-sensor/src/init.lua +++ b/drivers/SmartThings/zwave-sensor/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 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -27,19 +16,6 @@ local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) local preferences = require "preferences" local configurations = require "configurations" -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 - --- Handle preference changes --- --- @param driver st.zwave.Driver @@ -134,27 +110,7 @@ local driver_template = { capabilities.powerMeter, capabilities.smokeDetector }, - sub_drivers = { - lazy_load_if_possible("zooz-4-in-1-sensor"), - lazy_load_if_possible("vision-motion-detector"), - lazy_load_if_possible("fibaro-flood-sensor"), - lazy_load_if_possible("aeotec-water-sensor"), - lazy_load_if_possible("glentronics-water-leak-sensor"), - lazy_load_if_possible("homeseer-multi-sensor"), - lazy_load_if_possible("fibaro-door-window-sensor"), - lazy_load_if_possible("sensative-strip"), - lazy_load_if_possible("enerwave-motion-sensor"), - lazy_load_if_possible("aeotec-multisensor"), - lazy_load_if_possible("zwave-water-leak-sensor"), - lazy_load_if_possible("everspring-motion-light-sensor"), - lazy_load_if_possible("ezmultipli-multipurpose-sensor"), - lazy_load_if_possible("fibaro-motion-sensor"), - 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"), - }, + sub_drivers = require("sub_drivers"), lifecycle_handlers = { added = added_handler, init = device_init, diff --git a/drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-sensor/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/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-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index 9585b6ffe9..70293b10fa 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/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 + --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) diff --git a/drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua new file mode 100644 index 0000000000..9b515bae9f --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/sensative-strip/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_sensative_strip(opts, driver, device, cmd, ...) + local SENSATIVE_MFR = 0x019A + local SENSATIVE_MODEL = 0x000A + if device:id_match(SENSATIVE_MFR, nil, SENSATIVE_MODEL) then + local subdriver = require("sensative-strip") + return true, subdriver + end + return false +end + +return can_handle_sensative_strip diff --git a/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua b/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua index 73f1cb8459..7fd9fb2258 100644 --- a/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/sensative-strip/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 + --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -19,20 +9,11 @@ local Configuration = (require "st.zwave.CommandClass.Configuration")({ version --- @type st.zwave.CommandClass.WakeUp local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) -local SENSATIVE_MFR = 0x019A -local SENSATIVE_MODEL = 0x000A local LEAKAGE_ALARM_PARAM = 12 local LEAKAGE_ALARM_OFF = 0 local SENSATIVE_COMFORT_PROFILE = "illuminance-temperature" local CONFIG_REPORT_RECEIVED = "configReportReceived" -local function can_handle_sensative_strip(opts, driver, device, cmd, ...) - if device:id_match(SENSATIVE_MFR, nil, SENSATIVE_MODEL) then - local subdriver = require("sensative-strip") - return true, subdriver - else return false end -end - local function configuration_report(driver, device, cmd) local parameter_number = cmd.args.parameter_number local configuration_value = cmd.args.configuration_value @@ -75,7 +56,7 @@ local sensative_strip = { doConfigure = do_configure }, NAME = "sensative_strip", - can_handle = can_handle_sensative_strip + can_handle = require("sensative-strip.can_handle") } return sensative_strip diff --git a/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua new file mode 100644 index 0000000000..9504479304 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua @@ -0,0 +1,26 @@ +-- 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("zooz-4-in-1-sensor"), + lazy_load_if_possible("vision-motion-detector"), + lazy_load_if_possible("fibaro-flood-sensor"), + lazy_load_if_possible("aeotec-water-sensor"), + lazy_load_if_possible("glentronics-water-leak-sensor"), + lazy_load_if_possible("homeseer-multi-sensor"), + lazy_load_if_possible("fibaro-door-window-sensor"), + lazy_load_if_possible("sensative-strip"), + lazy_load_if_possible("enerwave-motion-sensor"), + lazy_load_if_possible("aeotec-multisensor"), + lazy_load_if_possible("zwave-water-leak-sensor"), + lazy_load_if_possible("everspring-motion-light-sensor"), + lazy_load_if_possible("ezmultipli-multipurpose-sensor"), + lazy_load_if_possible("fibaro-motion-sensor"), + 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"), +} diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua index 934235ae24..472932fdfb 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.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-sensor/src/test/test_aeotec_multisensor_6.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua index 29938eb5ce..323c0d4807 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_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-sensor/src/test/test_aeotec_multisensor_7.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua index ed0e6312b5..13442ec635 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.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/src/test/test_aeotec_multisensor_gen5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua index 63671f073b..b6ede32bd5 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.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-sensor/src/test/test_aeotec_water_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua index 836b5a9480..e38ce96f37 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_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-sensor/src/test/test_aeotec_water_sensor_7.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua index bdb0f60308..0f3dc972de 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.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/src/test/test_enerwave_motion_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua index a1e9f5691e..3559656e3a 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_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 zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua index 853729bdd5..a33f9b97ec 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.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-sensor/src/test/test_everspring_PIR_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua index 428a4883b0..8479a5f74f 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_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-sensor/src/test/test_everspring_ST814.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua index 0e537bf123..3c288c0be4 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.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-sensor/src/test/test_everspring_illuminance_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua index 48c3d2e609..ab124e8f80 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_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 zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua index 5f9adaf70c..0a31f97de5 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_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 zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua index 6e5f1d25ed..fb2138c0d5 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_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 zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua index 1b4ba1cd9c..05e306b6c7 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_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-sensor/src/test/test_fibaro_door_window_sensor_1.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua index 16f46f0756..6707dd7ae0 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.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/src/test/test_fibaro_door_window_sensor_2.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua index c784afb875..8fd23b208b 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.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/src/test/test_fibaro_door_window_sensor_with_temperature.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua index cd986cdc94..4d3e6e0660 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.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/src/test/test_fibaro_flood_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua index 51533e76da..0ac7e57418 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_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-sensor/src/test/test_fibaro_flood_sensor_zw5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua index d9fd3c08c3..530d1c03bb 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.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-sensor/src/test/test_fibaro_motion_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua index 53ce347428..3dbfd89bc7 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua index a4931e34f3..ac2022e069 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.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/src/test/test_generic_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua index 1315ffe638..3496ebb6d5 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_generic_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-sensor/src/test/test_glentronics_water_leak_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua index b78bc8df64..926558036b 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua index 2549508083..743ffc5298 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_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-sensor/src/test/test_no_wakeup_poll.lua b/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua index 0ee1fe63e6..c88527f0cd 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.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 capabilities = require "st.capabilities" @@ -115,4 +105,3 @@ test.register_message_test( ) test.run_registered_tests() - diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua b/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua index 72e0e59d07..fb9b519a42 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.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-sensor/src/test/test_smartthings_water_leak_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua index 7fdd26954c..e80e28dbd9 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua +++ b/drivers/SmartThings/zwave-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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua b/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua index 422347f770..4cfd906636 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.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 capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua b/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua index 1b372a9162..3cd16b6fa3 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.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/src/test/test_zooz_4_in_1_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua index 9737b0d863..fde2d5ebfe 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_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-sensor/src/test/test_zwave_motion_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua index df28af97d1..7a8adbeb10 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_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-sensor/src/test/test_zwave_motion_temp_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua index 56549ac78e..8e463e7625 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_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-sensor/src/test/test_zwave_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua index 7f234a05fb..677204e5d1 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_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-sensor/src/test/test_zwave_water_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua index ab296f8fed..775130d87e 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_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-sensor/src/timed-tamper-clear/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua new file mode 100644 index 0000000000..c05cbdcf7d --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/can_handle.lua @@ -0,0 +1,23 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_tamper_event(opts, driver, device, cmd, ...) + local cc = require "st.zwave.CommandClass" + local Notification = (require "st.zwave.CommandClass.Notification")({ version = 4 }) + local FIBARO_DOOR_WINDOW_MFR_ID = 0x010F + + if device.zwave_manufacturer_id ~= FIBARO_DOOR_WINDOW_MFR_ID and + 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 + return true, require("timed-tamper-clear") + end + return false +end + +return can_handle_tamper_event 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..c2420791b5 100644 --- a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua @@ -1,16 +1,7 @@ --- 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 + + --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -20,23 +11,6 @@ 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 - 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 - end -end -- This behavior is from zwave-door-window-sensor.groovy. We've seen this behavior -- in Ecolink and several other z-wave sensors that do not send tamper clear events @@ -60,7 +34,7 @@ local timed_tamper_clear = { } }, NAME = "timed tamper clear", - can_handle = can_handle_tamper_event + can_handle = require("timed-tamper-clear.can_handle"), } return timed_tamper_clear diff --git a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua new file mode 100644 index 0000000000..492a72dc74 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/can_handle.lua @@ -0,0 +1,22 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_v1_contact_event(opts, driver, device, cmd, ...) + local cc = require "st.zwave.CommandClass" + local Notification = (require "st.zwave.CommandClass.Notification")({ version = 4 }) + + if 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.v1_alarm_type == 0x07 then + local subdriver = require("v1-contact-event") + return true, subdriver + else + return false + end +end + +return can_handle_v1_contact_event diff --git a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua index 40efc7633e..887ac32bf0 100644 --- a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/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 2023 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -18,20 +7,6 @@ local cc = require "st.zwave.CommandClass" local Notification = (require "st.zwave.CommandClass.Notification")({ version = 4 }) local capabilities = require "st.capabilities" -local function can_handle_v1_contact_event(opts, driver, device, cmd, ...) - if 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.v1_alarm_type == 0x07 then - local subdriver = require("v1-contact-event") - return true, subdriver - else - return false - end -end -- This behavior is from zwave-door-window-sensor.groovy, where it is -- indicated that certain monoprice sensors had this behavior. Also, @@ -53,7 +28,7 @@ local v1_contact_event = { } }, NAME = "v1 contact event", - can_handle = can_handle_v1_contact_event + can_handle = require("v1-contact-event.can_handle"), } return v1_contact_event diff --git a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua new file mode 100644 index 0000000000..d270a7954d --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +--- Determine whether the passed device is zwave-plus-motion-temp-sensor +local function can_handle_vision_motion_detector(opts, driver, device, ...) + local VISION_MOTION_DETECTOR_FINGERPRINTS = { manufacturerId = 0x0109, productType = 0x2002, productId = 0x0205 } -- Vision Motion Detector ZP3102 + if device:id_match( + VISION_MOTION_DETECTOR_FINGERPRINTS.manufacturerId, + VISION_MOTION_DETECTOR_FINGERPRINTS.productType, + VISION_MOTION_DETECTOR_FINGERPRINTS.productId + ) then + return true, require("vision-motion-detector") + end + return false +end + +return can_handle_vision_motion_detector diff --git a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua index 320bf3824f..72934362c5 100644 --- a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -22,20 +13,6 @@ local Configuration = (require "st.zwave.CommandClass.Configuration")({ version --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) -local VISION_MOTION_DETECTOR_FINGERPRINTS = { manufacturerId = 0x0109, productType = 0x2002, productId = 0x0205 } -- Vision Motion Detector ZP3102 - ---- Determine whether the passed device is zwave-plus-motion-temp-sensor -local function can_handle_vision_motion_detector(opts, driver, device, ...) - if device:id_match( - VISION_MOTION_DETECTOR_FINGERPRINTS.manufacturerId, - VISION_MOTION_DETECTOR_FINGERPRINTS.productType, - VISION_MOTION_DETECTOR_FINGERPRINTS.productId - ) then - local subdriver = require("vision-motion-detector") - return true, subdriver - else return false end -end - --- Handler for notification report command class from sensor --- --- @param self st.zwave.Driver @@ -83,7 +60,7 @@ local vision_motion_detector = { doConfigure = do_configure, }, NAME = "Vision motion detector", - can_handle = can_handle_vision_motion_detector + can_handle = require("vision-motion-detector.can_handle"), } return vision_motion_detector diff --git a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua new file mode 100644 index 0000000000..15ac66d439 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle(opts, driver, device, ...) + local fingerprint = {manufacturerId = 0x014F, productType = 0x2001, productId = 0x0102} -- NorTek open/close sensor + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("wakeup-no-poll") + end + return false +end + +return can_handle diff --git a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua index 59d298a0e4..1270cd4557 100644 --- a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua @@ -1,16 +1,7 @@ --- 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" --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -21,17 +12,6 @@ local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({version = 2 --- @type st.zwave.CommandClass.Battery local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) -local fingerprint = {manufacturerId = 0x014F, productType = 0x2001, productId = 0x0102} -- NorTek open/close sensor - -local function can_handle(opts, driver, device, ...) - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("wakeup-no-poll") - return true, subdriver - else - return false - end -end - -- Nortek open/closed sensors _always_ respond with "open" when polled, and they are polled after wakeup local function wakeup_notification(driver, device, cmd) --Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher @@ -53,7 +33,7 @@ local wakeup_no_poll = { [WakeUp.NOTIFICATION] = wakeup_notification } }, - can_handle = can_handle + can_handle = require("wakeup-no-poll.can_handle"), } -return wakeup_no_poll \ No newline at end of file +return wakeup_no_poll diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua new file mode 100644 index 0000000000..e979e32153 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_zooz_4_in_1_sensor(opts, driver, device, ...) + local FINGERPRINTS = require("zooz-4-in-1-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("zooz-4-in-1-sensor") + return true, subdriver + end + end + return false +end + +return can_handle_zooz_4_in_1_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua new file mode 100644 index 0000000000..12d853b147 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZOOZ_4_IN_1_FINGERPRINTS = { + { manufacturerId = 0x027A, productType = 0x2021, productId = 0x2101 }, -- Zooz 4-in-1 sensor + { manufacturerId = 0x0109, productType = 0x2021, productId = 0x2101 }, -- ZP3111US 4-in-1 Motion + { manufacturerId = 0x0060, productType = 0x0001, productId = 0x0004 } -- Everspring Immune Pet PIR Sensor SP815 +} + +return ZOOZ_4_IN_1_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua index 5d4570e525..7321c3f9b4 100644 --- a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -22,22 +13,8 @@ local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ ve --- @type st.utils local utils = require "st.utils" -local ZOOZ_4_IN_1_FINGERPRINTS = { - { manufacturerId = 0x027A, productType = 0x2021, productId = 0x2101 }, -- Zooz 4-in-1 sensor - { manufacturerId = 0x0109, productType = 0x2021, productId = 0x2101 }, -- ZP3111US 4-in-1 Motion - { manufacturerId = 0x0060, productType = 0x0001, productId = 0x0004 } -- Everspring Immune Pet PIR Sensor SP815 -} --- Determine whether the passed device is zooz_4_in_1_sensor -local function can_handle_zooz_4_in_1_sensor(opts, driver, device, ...) - for _, fingerprint in ipairs(ZOOZ_4_IN_1_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("zooz-4-in-1-sensor") - return true, subdriver - end - end - return false -end --- Handler for notification report command class --- @@ -109,7 +86,7 @@ local zooz_4_in_1_sensor = { } }, NAME = "zooz 4 in 1 sensor", - can_handle = can_handle_zooz_4_in_1_sensor + can_handle = require("zooz-4-in-1-sensor.can_handle"), } return zooz_4_in_1_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua new file mode 100644 index 0000000000..63da2ae39c --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_water_leak_sensor(opts, driver, device, ...) + local FINGERPRINTS = require("zwave-water-leak-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("zwave-water-leak-sensor") + return true, subdriver + end + end + return false +end + +return can_handle_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua new file mode 100644 index 0000000000..c07bdb52cb --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/fingerprints.lua @@ -0,0 +1,19 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local WATER_LEAK_SENSOR_FINGERPRINTS = { + {mfr = 0x0084, prod = 0x0063, model = 0x010C}, -- SmartThings Water Leak Sensor + {mfr = 0x0084, prod = 0x0053, model = 0x0216}, -- FortrezZ Water Leak Sensor + {mfr = 0x021F, prod = 0x0003, model = 0x0085}, -- Dome Leak Sensor + {mfr = 0x0258, prod = 0x0003, model = 0x0085}, -- NEO Coolcam Water Sensor + {mfr = 0x0258, prod = 0x0003, model = 0x1085}, -- NEO Coolcam Water Sensor + {mfr = 0x0258, prod = 0x0003, model = 0x2085}, -- NEO Coolcam Water Sensor + {mfr = 0x0086, prod = 0x0002, model = 0x007A}, -- Aeotec Water Sensor 6 (EU) + {mfr = 0x0086, prod = 0x0102, model = 0x007A}, -- Aeotec Water Sensor 6 (US) + {mfr = 0x0086, prod = 0x0202, model = 0x007A}, -- Aeotec Water Sensor 6 (AU) + {mfr = 0x000C, prod = 0x0201, model = 0x000A}, -- HomeSeer LS100+ Water Sensor + {mfr = 0x0173, prod = 0x4C47, model = 0x4C44}, -- Leak Gopher Z-Wave Leak Detector + {mfr = 0x027A, prod = 0x7000, model = 0xE002} -- Zooz ZSE42 XS Water Leak Sensor +} + +return WATER_LEAK_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua index 1eefab7479..4575de352a 100644 --- a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua @@ -1,47 +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 cc = require "st.zwave.CommandClass" local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) - -local WATER_LEAK_SENSOR_FINGERPRINTS = { - {mfr = 0x0084, prod = 0x0063, model = 0x010C}, -- SmartThings Water Leak Sensor - {mfr = 0x0084, prod = 0x0053, model = 0x0216}, -- FortrezZ Water Leak Sensor - {mfr = 0x021F, prod = 0x0003, model = 0x0085}, -- Dome Leak Sensor - {mfr = 0x0258, prod = 0x0003, model = 0x0085}, -- NEO Coolcam Water Sensor - {mfr = 0x0258, prod = 0x0003, model = 0x1085}, -- NEO Coolcam Water Sensor - {mfr = 0x0258, prod = 0x0003, model = 0x2085}, -- NEO Coolcam Water Sensor - {mfr = 0x0086, prod = 0x0002, model = 0x007A}, -- Aeotec Water Sensor 6 (EU) - {mfr = 0x0086, prod = 0x0102, model = 0x007A}, -- Aeotec Water Sensor 6 (US) - {mfr = 0x0086, prod = 0x0202, model = 0x007A}, -- Aeotec Water Sensor 6 (AU) - {mfr = 0x000C, prod = 0x0201, model = 0x000A}, -- HomeSeer LS100+ Water Sensor - {mfr = 0x0173, prod = 0x4C47, model = 0x4C44}, -- Leak Gopher Z-Wave Leak Detector - {mfr = 0x027A, prod = 0x7000, model = 0xE002} -- Zooz ZSE42 XS Water Leak Sensor -} - -local function can_handle_water_leak_sensor(opts, driver, device, ...) - for _, fingerprint in ipairs(WATER_LEAK_SENSOR_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("zwave-water-leak-sensor") - return true, subdriver - 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 device:emit_event(value == 0xFF and capabilities.waterSensor.water.wet() or capabilities.waterSensor.water.dry()) @@ -54,7 +17,7 @@ local water_leak_sensor = { [Basic.SET] = basic_set_handler } }, - can_handle = can_handle_water_leak_sensor + can_handle = require("zwave-water-leak-sensor.can_handle"), } return water_leak_sensor From 709cda332e5dc8a7b39959c45a3a356d08be533f Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 21 Apr 2026 14:32:29 -0500 Subject: [PATCH 272/538] CHAD-18038: Enable single device thread --- .../SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua | 1 + .../src/aeotec-multisensor/multisensor-6/init.lua | 1 + .../src/aeotec-multisensor/multisensor-7/init.lua | 1 + .../zwave-sensor/src/aeotec-water-sensor/init.lua | 1 + drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua | 1 + .../zwave-sensor/src/enerwave-motion-sensor/init.lua | 3 ++- .../zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua | 1 + .../fibaro-door-window-sensor-1/init.lua | 1 + .../fibaro-door-window-sensor-2/init.lua | 1 + .../zwave-sensor/src/fibaro-door-window-sensor/init.lua | 1 + .../zwave-sensor/src/fibaro-flood-sensor/init.lua | 1 + .../zwave-sensor/src/fibaro-motion-sensor/init.lua | 3 ++- .../SmartThings/zwave-sensor/src/firmware-version/init.lua | 5 +++-- .../zwave-sensor/src/glentronics-water-leak-sensor/init.lua | 1 + .../zwave-sensor/src/homeseer-multi-sensor/init.lua | 1 + drivers/SmartThings/zwave-sensor/src/init.lua | 1 + .../SmartThings/zwave-sensor/src/sensative-strip/init.lua | 3 ++- .../SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua | 1 + .../SmartThings/zwave-sensor/src/v1-contact-event/init.lua | 1 + .../zwave-sensor/src/vision-motion-detector/init.lua | 1 + drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua | 1 + .../SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua | 1 + .../zwave-sensor/src/zwave-water-leak-sensor/init.lua | 1 + 23 files changed, 28 insertions(+), 5 deletions(-) diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua index d1759a41e0..947f2fea2b 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/init.lua @@ -38,6 +38,7 @@ local aeotec_multisensor = { sub_drivers = require("aeotec-multisensor.sub_drivers"), NAME = "aeotec multisensor", can_handle = require("aeotec-multisensor.can_handle"), + shared_device_thread_enabled = true, } return aeotec_multisensor diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua index 4174b3b14e..f8d001cdf4 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-6/init.lua @@ -50,6 +50,7 @@ local multisensor_6 = { }, NAME = "aeotec multisensor 6", can_handle = require("aeotec-multisensor.multisensor-6.can_handle"), + shared_device_thread_enabled = true, } return multisensor_6 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua index c3dc69178f..d97d65759c 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-multisensor/multisensor-7/init.lua @@ -49,6 +49,7 @@ local multisensor_7 = { }, NAME = "aeotec multisensor 7", can_handle = require("aeotec-multisensor.multisensor-7.can_handle"), + shared_device_thread_enabled = true, } return multisensor_7 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua index 4c7a86e708..509e38ac9e 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor/init.lua @@ -45,6 +45,7 @@ local zwave_water_temp_humidity_sensor = { }, NAME = "zwave water temp humidity sensor", can_handle = require("aeotec-water-sensor.can_handle"), + shared_device_thread_enabled = true, } return zwave_water_temp_humidity_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua index 94dc5975ab..38b8a6612f 100644 --- a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua @@ -16,6 +16,7 @@ local apiv6_bugfix = { }, NAME = "apiv6_bugfix", can_handle = require("apiv6_bugfix.can_handle"), + shared_device_thread_enabled = true, } return apiv6_bugfix diff --git a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua index 012a8884a3..53ef77c4be 100644 --- a/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/enerwave-motion-sensor/init.lua @@ -39,7 +39,8 @@ local enerwave_motion_sensor = { doConfigure = do_configure }, NAME = "enerwave_motion_sensor", - can_handle = require("enerwave-motion-sensor.can_handle") + can_handle = require("enerwave-motion-sensor.can_handle"), + shared_device_thread_enabled = true, } return enerwave_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua index f4cac30faa..64f34ffd96 100644 --- a/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/ezmultipli-multipurpose-sensor/init.lua @@ -83,6 +83,7 @@ local ezmultipli_multipurpose_sensor = { } }, can_handle = require("ezmultipli-multipurpose-sensor.can_handle"), + shared_device_thread_enabled = true, } return ezmultipli_multipurpose_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua index 4dbca58919..abe2a8b6dc 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua @@ -69,6 +69,7 @@ local fibaro_door_window_sensor_1 = { [capabilities.refresh.commands.refresh.NAME] = do_refresh }, can_handle = require("fibaro-door-window-sensor.fibaro-door-window-sensor-1.can_handle"), + shared_device_thread_enabled = true, } return fibaro_door_window_sensor_1 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua index 250203b0cd..a1f8ccee82 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua @@ -58,6 +58,7 @@ local fibaro_door_window_sensor_2 = { added = device_added }, can_handle = require("fibaro-door-window-sensor.fibaro-door-window-sensor-2.can_handle"), + shared_device_thread_enabled = true, } return fibaro_door_window_sensor_2 diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua index 6c30508fd1..dc26fcddcd 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/init.lua @@ -124,6 +124,7 @@ local fibaro_door_window_sensor = { }, sub_drivers = require("fibaro-door-window-sensor.sub_drivers"), can_handle = require("fibaro-door-window-sensor.can_handle"), + shared_device_thread_enabled = true, } return fibaro_door_window_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua index 9a0a8e7eaa..75fc982511 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-flood-sensor/init.lua @@ -77,6 +77,7 @@ local fibaro_flood_sensor = { doConfigure = do_configure }, can_handle = require("fibaro-flood-sensor.can_handle"), + shared_device_thread_enabled = true, } return fibaro_flood_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua index ed035bde18..e1aa2ccaf8 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-motion-sensor/init.lua @@ -24,7 +24,8 @@ local fibaro_motion_sensor = { [SensorAlarm.REPORT] = sensor_alarm_report } }, - can_handle = require("fibaro-motion-sensor.can_handle") + can_handle = require("fibaro-motion-sensor.can_handle"), + shared_device_thread_enabled = true, } return fibaro_motion_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua b/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua index 528cf7da45..8fe58a7167 100644 --- a/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua @@ -59,7 +59,8 @@ local firmware_version = { [cc.WAKE_UP] = { [WakeUp.NOTIFICATION] = wakeup_notification } - } + }, + shared_device_thread_enabled = true, } -return firmware_version \ No newline at end of file +return firmware_version diff --git a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua index 7400d889b7..cc6c476658 100644 --- a/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/glentronics-water-leak-sensor/init.lua @@ -53,6 +53,7 @@ local glentronics_water_leak_sensor = { }, NAME = "glentronics water leak sensor", can_handle = require("glentronics-water-leak-sensor.can_handle"), + shared_device_thread_enabled = true, } return glentronics_water_leak_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua index f89e6a7870..07835ac7d4 100644 --- a/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/homeseer-multi-sensor/init.lua @@ -62,6 +62,7 @@ local homeseer_multi_sensor = { }, NAME = "homeseer multi sensor", can_handle = require("homeseer-multi-sensor.can_handle"), + shared_device_thread_enabled = true, } return homeseer_multi_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/init.lua b/drivers/SmartThings/zwave-sensor/src/init.lua index 2c18e4c2ed..9ab4d0077d 100644 --- a/drivers/SmartThings/zwave-sensor/src/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/init.lua @@ -125,6 +125,7 @@ local driver_template = { [WakeUp.NOTIFICATION] = wakeup_notification } }, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, diff --git a/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua b/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua index 7fd9fb2258..89085c00c9 100644 --- a/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/sensative-strip/init.lua @@ -56,7 +56,8 @@ local sensative_strip = { doConfigure = do_configure }, NAME = "sensative_strip", - can_handle = require("sensative-strip.can_handle") + can_handle = require("sensative-strip.can_handle"), + shared_device_thread_enabled = true, } return sensative_strip 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 c2420791b5..8554dab28f 100644 --- a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua @@ -35,6 +35,7 @@ local timed_tamper_clear = { }, NAME = "timed tamper clear", can_handle = require("timed-tamper-clear.can_handle"), + shared_device_thread_enabled = true, } return timed_tamper_clear diff --git a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua index 887ac32bf0..78b3beefcb 100644 --- a/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/v1-contact-event/init.lua @@ -29,6 +29,7 @@ local v1_contact_event = { }, NAME = "v1 contact event", can_handle = require("v1-contact-event.can_handle"), + shared_device_thread_enabled = true, } return v1_contact_event diff --git a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua index 72934362c5..f9e8f8a04e 100644 --- a/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/vision-motion-detector/init.lua @@ -61,6 +61,7 @@ local vision_motion_detector = { }, NAME = "Vision motion detector", can_handle = require("vision-motion-detector.can_handle"), + shared_device_thread_enabled = true, } return vision_motion_detector diff --git a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua index 1270cd4557..42163aeaae 100644 --- a/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/wakeup-no-poll/init.lua @@ -34,6 +34,7 @@ local wakeup_no_poll = { } }, can_handle = require("wakeup-no-poll.can_handle"), + shared_device_thread_enabled = true, } return wakeup_no_poll diff --git a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua index 7321c3f9b4..2a4053302b 100644 --- a/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/zooz-4-in-1-sensor/init.lua @@ -87,6 +87,7 @@ local zooz_4_in_1_sensor = { }, NAME = "zooz 4 in 1 sensor", can_handle = require("zooz-4-in-1-sensor.can_handle"), + shared_device_thread_enabled = true, } return zooz_4_in_1_sensor diff --git a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua index 4575de352a..6a956a4343 100644 --- a/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/zwave-water-leak-sensor/init.lua @@ -18,6 +18,7 @@ local water_leak_sensor = { } }, can_handle = require("zwave-water-leak-sensor.can_handle"), + shared_device_thread_enabled = true, } return water_leak_sensor From 5a43981e0212d6ab4aa4126f3965985dc68e4fde Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 16 Dec 2025 09:41:45 -0600 Subject: [PATCH 273/538] CHAD-17163: lazy loading of matter-window-covering sub-drivers --- .../matter-window-covering/src/init.lua | 23 +++---------- .../src/lazy_load_subdriver.lua | 14 ++++++++ .../can_handle.lua | 19 +++++++++++ .../fingerprints.lua | 8 +++++ .../init.lua | 34 +++---------------- .../src/sub_drivers.lua | 8 +++++ .../src/test/test_matter_window_covering.lua | 16 ++------- 7 files changed, 61 insertions(+), 61 deletions(-) create mode 100644 drivers/SmartThings/matter-window-covering/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/can_handle.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/fingerprints.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/sub_drivers.lua diff --git a/drivers/SmartThings/matter-window-covering/src/init.lua b/drivers/SmartThings/matter-window-covering/src/init.lua index 6759560c50..eff18e7558 100644 --- a/drivers/SmartThings/matter-window-covering/src/init.lua +++ b/drivers/SmartThings/matter-window-covering/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 + --Note: Currently only support for window shades with the PositionallyAware Feature --Note: No support for setting device into calibration mode, it must be done manually @@ -376,11 +366,8 @@ local matter_driver_template = { capabilities.battery, capabilities.batteryLevel, }, - sub_drivers = { - -- for devices sending a position update while device is in motion - require("matter-window-covering-position-updates-while-moving") - } + sub_drivers = require("sub_drivers"), } local matter_driver = MatterDriver("matter-window-covering", matter_driver_template) -matter_driver:run() \ No newline at end of file +matter_driver:run() diff --git a/drivers/SmartThings/matter-window-covering/src/lazy_load_subdriver.lua b/drivers/SmartThings/matter-window-covering/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..a04740d267 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/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-window-covering/src/matter-window-covering-position-updates-while-moving/can_handle.lua b/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/can_handle.lua new file mode 100644 index 0000000000..ba9207f3d2 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/can_handle.lua @@ -0,0 +1,19 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_matter_window_covering_position_updates_while_moving(opts, driver, device) + local device_lib = require "st.device" + if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then + return false + end + local FINGERPRINTS = require("matter-window-covering-position-updates-while-moving.fingerprints") + for i, v in ipairs(FINGERPRINTS) do + if device.manufacturer_info.vendor_id == v[1] and + device.manufacturer_info.product_id == v[2] then + return true, require("matter-window-covering-position-updates-while-moving") + end + end + return false +end + +return is_matter_window_covering_position_updates_while_moving diff --git a/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/fingerprints.lua b/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/fingerprints.lua new file mode 100644 index 0000000000..37800fc680 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local SUB_WINDOW_COVERING_VID_PID = { + {0x10e1, 0x1005} -- VDA +} + +return SUB_WINDOW_COVERING_VID_PID diff --git a/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/init.lua b/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/init.lua index 11ad2d8ef9..9a86f967b7 100644 --- a/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/init.lua +++ b/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/init.lua @@ -1,20 +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 2023 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 DEFAULT_LEVEL = 0 local STATE_MACHINE = "__state_machine" @@ -27,22 +16,7 @@ local StateMachineEnum = { STATE_CURRENT_POSITION_FIRED = 0x03 } -local SUB_WINDOW_COVERING_VID_PID = { - {0x10e1, 0x1005} -- VDA -} -local function is_matter_window_covering_position_updates_while_moving(opts, driver, device) - if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then - return false - end - for i, v in ipairs(SUB_WINDOW_COVERING_VID_PID) do - if device.manufacturer_info.vendor_id == v[1] and - device.manufacturer_info.product_id == v[2] then - return true - end - end - return false -end local function device_init(driver, device) device:subscribe() @@ -145,7 +119,7 @@ local matter_window_covering_position_updates_while_moving_handler = { }, capability_handlers = { }, - can_handle = is_matter_window_covering_position_updates_while_moving, + can_handle = require("matter-window-covering-position-updates-while-moving.can_handle"), } return matter_window_covering_position_updates_while_moving_handler diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers.lua new file mode 100644 index 0000000000..ff048340d0 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/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("matter-window-covering-position-updates-while-moving"), +} +return sub_drivers 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 062b199ea1..9f273037f3 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 @@ -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" From fc60e2b6ef2d211493918b37ab30a61e3149aa7b Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 21 Apr 2026 14:27:57 -0500 Subject: [PATCH 274/538] CHAD-18037: Enable shared_device_thread_enabled --- drivers/SmartThings/matter-window-covering/src/init.lua | 1 + .../init.lua | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/SmartThings/matter-window-covering/src/init.lua b/drivers/SmartThings/matter-window-covering/src/init.lua index eff18e7558..a98f8a321d 100644 --- a/drivers/SmartThings/matter-window-covering/src/init.lua +++ b/drivers/SmartThings/matter-window-covering/src/init.lua @@ -367,6 +367,7 @@ local matter_driver_template = { capabilities.batteryLevel, }, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } local matter_driver = MatterDriver("matter-window-covering", matter_driver_template) diff --git a/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/init.lua b/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/init.lua index 9a86f967b7..56407c6667 100644 --- a/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/init.lua +++ b/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/init.lua @@ -120,6 +120,7 @@ local matter_window_covering_position_updates_while_moving_handler = { capability_handlers = { }, can_handle = require("matter-window-covering-position-updates-while-moving.can_handle"), + shared_device_thread_enabled = true, } return matter_window_covering_position_updates_while_moving_handler From aa3e513e46fa2c758042960a417595ada724458c Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:30 -0600 Subject: [PATCH 275/538] CHAD-17087: zwave-fan lazy lading of sub-drivers --- drivers/SmartThings/zwave-fan/src/init.lua | 21 +++--------- .../zwave-fan/src/lazy_load_subdriver.lua | 18 ++++++++++ .../SmartThings/zwave-fan/src/sub_drivers.lua | 9 +++++ .../src/test/test_zwave_fan_3_speed.lua | 16 ++------- .../src/test/test_zwave_fan_4_speed.lua | 16 ++------- .../src/zwave-fan-3-speed/can_handle.lua | 14 ++++++++ .../src/zwave-fan-3-speed/fingerprints.lua | 12 +++++++ .../zwave-fan/src/zwave-fan-3-speed/init.lua | 33 +++---------------- .../src/zwave-fan-4-speed/can_handle.lua | 14 ++++++++ .../src/zwave-fan-4-speed/fingerprints.lua | 8 +++++ .../zwave-fan/src/zwave-fan-4-speed/init.lua | 29 +++------------- .../zwave-fan/src/zwave_fan_helpers.lua | 16 ++------- 12 files changed, 96 insertions(+), 110 deletions(-) create mode 100644 drivers/SmartThings/zwave-fan/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-fan/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/can_handle.lua create mode 100644 drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/can_handle.lua create mode 100644 drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/fingerprints.lua diff --git a/drivers/SmartThings/zwave-fan/src/init.lua b/drivers/SmartThings/zwave-fan/src/init.lua index acdb34ae76..8b3a7d0cea 100644 --- a/drivers/SmartThings/zwave-fan/src/init.lua +++ b/drivers/SmartThings/zwave-fan/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 @@ -27,10 +17,7 @@ local driver_template = { capabilities.switch, capabilities.fanSpeed, }, - sub_drivers = { - require("zwave-fan-3-speed"), - require("zwave-fan-4-speed") - }, + sub_drivers = require("sub_drivers"), } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zwave-fan/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-fan/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-fan/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-fan/src/sub_drivers.lua b/drivers/SmartThings/zwave-fan/src/sub_drivers.lua new file mode 100644 index 0000000000..373e4daf52 --- /dev/null +++ b/drivers/SmartThings/zwave-fan/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("zwave-fan-3-speed"), + lazy_load_if_possible("zwave-fan-4-speed"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_3_speed.lua b/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_3_speed.lua index 01c5a2b856..534c48698e 100644 --- a/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_3_speed.lua +++ b/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_3_speed.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-fan/src/test/test_zwave_fan_4_speed.lua b/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_4_speed.lua index 4c9b252877..80ccef948b 100644 --- a/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_4_speed.lua +++ b/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_4_speed.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-fan/src/zwave-fan-3-speed/can_handle.lua b/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/can_handle.lua new file mode 100644 index 0000000000..66a04b41a9 --- /dev/null +++ b/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_fan_3_speed(opts, driver, device, ...) + local FINGERPRINTS = require("zwave-fan-3-speed.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("zwave-fan-3-speed") + end + end + return false +end + +return is_fan_3_speed diff --git a/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/fingerprints.lua b/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/fingerprints.lua new file mode 100644 index 0000000000..7241769dcd --- /dev/null +++ b/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/fingerprints.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FAN_3_SPEED_FINGERPRINTS = { + {mfr = 0x001D, prod = 0x1001, model = 0x0334}, -- Leviton 3-Speed Fan Controller + {mfr = 0x0063, prod = 0x4944, model = 0x3034}, -- GE In-Wall Smart Fan Control + {mfr = 0x0063, prod = 0x4944, model = 0x3131}, -- GE In-Wall Smart Fan Control + {mfr = 0x0039, prod = 0x4944, model = 0x3131}, -- Honeywell Z-Wave Plus In-Wall Fan Speed Control + {mfr = 0x0063, prod = 0x4944, model = 0x3337}, -- GE In-Wall Smart Fan Control +} + +return FAN_3_SPEED_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/init.lua b/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/init.lua index 282f2e35db..c58ab97684 100644 --- a/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/init.lua +++ b/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/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 log = require "log" local capabilities = require "st.capabilities" @@ -22,13 +12,6 @@ local Basic = (require "st.zwave.CommandClass.Basic")({ version=1 }) local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ version=4 }) local fan_speed_helper = (require "zwave_fan_helpers") -local FAN_3_SPEED_FINGERPRINTS = { - {mfr = 0x001D, prod = 0x1001, model = 0x0334}, -- Leviton 3-Speed Fan Controller - {mfr = 0x0063, prod = 0x4944, model = 0x3034}, -- GE In-Wall Smart Fan Control - {mfr = 0x0063, prod = 0x4944, model = 0x3131}, -- GE In-Wall Smart Fan Control - {mfr = 0x0039, prod = 0x4944, model = 0x3131}, -- Honeywell Z-Wave Plus In-Wall Fan Speed Control - {mfr = 0x0063, prod = 0x4944, model = 0x3337}, -- GE In-Wall Smart Fan Control -} local function map_fan_3_speed_to_switch_level (speed) if speed == fan_speed_helper.fan_speed.OFF then @@ -63,14 +46,6 @@ end --- @param driver st.zwave.Driver --- @param device st.zwave.Device --- @return boolean true if the device is an 3-speed fan, else false -local function is_fan_3_speed(opts, driver, device, ...) - for _, fingerprint in ipairs(FAN_3_SPEED_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end local capability_handlers = {} @@ -110,7 +85,7 @@ local zwave_fan_3_speed = { } }, NAME = "Z-Wave fan 3 speed", - can_handle = is_fan_3_speed, + can_handle = require("zwave-fan-3-speed.can_handle"), } return zwave_fan_3_speed diff --git a/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/can_handle.lua b/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/can_handle.lua new file mode 100644 index 0000000000..b67f3c2ec7 --- /dev/null +++ b/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fan_4_speed(opts, driver, device, ...) + local FINGERPRINTS = require("zwave-fan-4-speed.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("zwave-fan-4-speed") + end + end + return false +end + +return can_handle_fan_4_speed diff --git a/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/fingerprints.lua b/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/fingerprints.lua new file mode 100644 index 0000000000..2e2f2b13fb --- /dev/null +++ b/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FAN_4_SPEED_FINGERPRINTS = { + {mfr = 0x001D, prod = 0x0038, model = 0x0002}, -- Leviton 4-Speed Fan Controller +} + +return FAN_4_SPEED_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/init.lua b/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/init.lua index f714a56b1b..a6a9e4efb6 100644 --- a/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/init.lua +++ b/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/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 log = require "log" local capabilities = require "st.capabilities" @@ -22,9 +12,6 @@ local Basic = (require "st.zwave.CommandClass.Basic")({ version=1 }) local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ version=4 }) local fan_speed_helper = (require "zwave_fan_helpers") -local FAN_4_SPEED_FINGERPRINTS = { - {mfr = 0x001D, prod = 0x0038, model = 0x0002}, -- Leviton 4-Speed Fan Controller -} local function map_fan_4_speed_to_switch_level (speed) if speed == fan_speed_helper.fan_speed.OFF then @@ -64,14 +51,6 @@ end --- @param driver st.zwave.Driver --- @param device st.zwave.Device --- @return boolean true if the device is 4-speed fan, else false -local function can_handle_fan_4_speed(opts, driver, device, ...) - for _, fingerprint in ipairs(FAN_4_SPEED_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end local capability_handlers = {} @@ -111,7 +90,7 @@ local zwave_fan_4_speed = { } }, NAME = "Z-Wave fan 4 speed", - can_handle = can_handle_fan_4_speed, + can_handle = require("zwave-fan-4-speed.can_handle"), } return zwave_fan_4_speed diff --git a/drivers/SmartThings/zwave-fan/src/zwave_fan_helpers.lua b/drivers/SmartThings/zwave-fan/src/zwave_fan_helpers.lua index bb056cf06a..38c26fa78c 100644 --- a/drivers/SmartThings/zwave-fan/src/zwave_fan_helpers.lua +++ b/drivers/SmartThings/zwave-fan/src/zwave_fan_helpers.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 From e8abbc0e0bb4421952d66b531861bb7484d6de9e Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 21 Apr 2026 14:23:46 -0500 Subject: [PATCH 276/538] CHAD-18035: Enable shared_device_thread_enabled --- drivers/SmartThings/zwave-fan/src/init.lua | 1 + drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/init.lua | 1 + drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/init.lua | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/SmartThings/zwave-fan/src/init.lua b/drivers/SmartThings/zwave-fan/src/init.lua index 8b3a7d0cea..d3b51d8344 100644 --- a/drivers/SmartThings/zwave-fan/src/init.lua +++ b/drivers/SmartThings/zwave-fan/src/init.lua @@ -18,6 +18,7 @@ local driver_template = { capabilities.fanSpeed, }, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/init.lua b/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/init.lua index c58ab97684..ad629ca54c 100644 --- a/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/init.lua +++ b/drivers/SmartThings/zwave-fan/src/zwave-fan-3-speed/init.lua @@ -86,6 +86,7 @@ local zwave_fan_3_speed = { }, NAME = "Z-Wave fan 3 speed", can_handle = require("zwave-fan-3-speed.can_handle"), + shared_device_thread_enabled = true, } return zwave_fan_3_speed diff --git a/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/init.lua b/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/init.lua index a6a9e4efb6..62ec23bfa0 100644 --- a/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/init.lua +++ b/drivers/SmartThings/zwave-fan/src/zwave-fan-4-speed/init.lua @@ -91,6 +91,7 @@ local zwave_fan_4_speed = { }, NAME = "Z-Wave fan 4 speed", can_handle = require("zwave-fan-4-speed.can_handle"), + shared_device_thread_enabled = true, } return zwave_fan_4_speed From ca450adb91d25687e49a3216411c86b69ae07a4f Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:08 -0600 Subject: [PATCH 277/538] CHAD-17085: zwave-button lazy loading of sub-drivers --- .../src/apiv6_bugfix/can_handle.lua | 16 ++++++ .../zwave-button/src/apiv6_bugfix/init.lua | 11 ++-- .../zwave-button/src/configurations.lua | 16 ++---- drivers/SmartThings/zwave-button/src/init.lua | 21 ++------ .../zwave-button/src/lazy_load_subdriver.lua | 18 +++++++ .../zwave-button/src/sub_drivers.lua | 9 ++++ .../src/test/test_zwave_aeotec_minimote.lua | 16 ++---- .../test/test_zwave_aeotec_nanomote_one.lua | 16 ++---- .../src/test/test_zwave_button.lua | 16 ++---- .../src/test/test_zwave_fibaro_button.lua | 16 ++---- .../src/test/test_zwave_multi_button.lua | 16 ++---- .../aeotec-keyfob/can_handle.lua | 14 +++++ .../aeotec-keyfob/fingerprints.lua | 10 ++++ .../zwave-multi-button/aeotec-keyfob/init.lua | 32 ++---------- .../aeotec-minimote/can_handle.lua | 14 +++++ .../aeotec-minimote/fingerprints.lua | 8 +++ .../aeotec-minimote/init.lua | 29 ++--------- .../src/zwave-multi-button/can_handle.lua | 14 +++++ .../fibaro-keyfob/can_handle.lua | 14 +++++ .../fibaro-keyfob/fingerprints.lua | 10 ++++ .../zwave-multi-button/fibaro-keyfob/init.lua | 31 ++--------- .../src/zwave-multi-button/fingerprints.lua | 23 +++++++++ .../src/zwave-multi-button/init.lua | 51 +++---------------- .../shelly_wave_i4/can_handle.lua | 14 +++++ .../shelly_wave_i4/fingerprints.lua | 9 ++++ .../shelly_wave_i4/init.lua | 30 ++--------- .../src/zwave-multi-button/sub_drivers.lua | 11 ++++ 27 files changed, 231 insertions(+), 254 deletions(-) create mode 100644 drivers/SmartThings/zwave-button/src/apiv6_bugfix/can_handle.lua create mode 100644 drivers/SmartThings/zwave-button/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-button/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/can_handle.lua create mode 100644 drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/can_handle.lua create mode 100644 drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-button/src/zwave-multi-button/can_handle.lua create mode 100644 drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/can_handle.lua create mode 100644 drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-button/src/zwave-multi-button/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/can_handle.lua create mode 100644 drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-button/src/zwave-multi-button/sub_drivers.lua diff --git a/drivers/SmartThings/zwave-button/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-button/src/apiv6_bugfix/can_handle.lua new file mode 100644 index 0000000000..8a9b8cc6cc --- /dev/null +++ b/drivers/SmartThings/zwave-button/src/apiv6_bugfix/can_handle.lua @@ -0,0 +1,16 @@ +-- 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-button/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-button/src/apiv6_bugfix/init.lua index 0204b7b2d5..2e7e3ca3b8 100644 --- a/drivers/SmartThings/zwave-button/src/apiv6_bugfix/init.lua +++ b/drivers/SmartThings/zwave-button/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-button/src/configurations.lua b/drivers/SmartThings/zwave-button/src/configurations.lua index 5dc1b96e0f..2c93b5075c 100644 --- a/drivers/SmartThings/zwave-button/src/configurations.lua +++ b/drivers/SmartThings/zwave-button/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 = { AEOTEC_NANOMOTE_ONE = { diff --git a/drivers/SmartThings/zwave-button/src/init.lua b/drivers/SmartThings/zwave-button/src/init.lua index b369197a5b..73672f3bcb 100644 --- a/drivers/SmartThings/zwave-button/src/init.lua +++ b/drivers/SmartThings/zwave-button/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 @@ -41,10 +31,7 @@ local driver_template = { lifecycle_handlers = { added = added_handler, }, - sub_drivers = { - require("zwave-multi-button"), - require("apiv6_bugfix"), - } + sub_drivers = require("sub_drivers"), } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zwave-button/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-button/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-button/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-button/src/sub_drivers.lua b/drivers/SmartThings/zwave-button/src/sub_drivers.lua new file mode 100644 index 0000000000..57e87ad8ad --- /dev/null +++ b/drivers/SmartThings/zwave-button/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("zwave-multi-button"), + lazy_load_if_possible("apiv6_bugfix"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_minimote.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_minimote.lua index 6876b7026f..88bf890686 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_minimote.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_minimote.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-button/src/test/test_zwave_aeotec_nanomote_one.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_nanomote_one.lua index 4d8be4dad2..7df9c6cada 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_nanomote_one.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_nanomote_one.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-button/src/test/test_zwave_button.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_button.lua index f5e16ba158..664d048010 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_button.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_button.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-button/src/test/test_zwave_fibaro_button.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_fibaro_button.lua index e03471594f..5418917ee5 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_fibaro_button.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_fibaro_button.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-button/src/test/test_zwave_multi_button.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua index 07132534c5..b88a85a800 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.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-button/src/zwave-multi-button/aeotec-keyfob/can_handle.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/can_handle.lua new file mode 100644 index 0000000000..5a2fec217c --- /dev/null +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_aeotec_keyfob(opts, driver, device, ...) + local FINGERPRINTS = require("zwave-multi-button.aeotec-keyfob.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("zwave-multi-button.aeotec-keyfob") + end + end + return false +end + +return can_handle_aeotec_keyfob diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/fingerprints.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/fingerprints.lua new file mode 100644 index 0000000000..dd6a9c8219 --- /dev/null +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZWAVE_AEOTEC_KEYFOB_FINGERPRINTS = { + {mfr = 0x0086, prod = 0x0101, model = 0x0058}, -- Aeotec KeyFob US + {mfr = 0x0086, prod = 0x0001, model = 0x0058}, -- Aeotec KeyFob EU + {mfr = 0x0086, prod = 0x0001, model = 0x0026} -- Aeotec Panic Button +} + +return ZWAVE_AEOTEC_KEYFOB_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/init.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/init.lua index c8b655bff2..ad3f69c41b 100644 --- a/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/init.lua +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/init.lua @@ -1,37 +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 --- @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 }) -local ZWAVE_AEOTEC_KEYFOB_FINGERPRINTS = { - {mfr = 0x0086, prod = 0x0101, model = 0x0058}, -- Aeotec KeyFob US - {mfr = 0x0086, prod = 0x0001, model = 0x0058}, -- Aeotec KeyFob EU - {mfr = 0x0086, prod = 0x0001, model = 0x0026} -- Aeotec Panic Button -} - -local function can_handle_aeotec_keyfob(opts, driver, device, ...) - for _, fingerprint in ipairs(ZWAVE_AEOTEC_KEYFOB_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:refresh() device:send(Configuration:Set({ configuration_value = 1, parameter_number = 250, size = 1 })) @@ -43,7 +17,7 @@ local aeotec_keyfob = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_aeotec_keyfob, + can_handle = require("zwave-multi-button.aeotec-keyfob.can_handle"), } return aeotec_keyfob diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/can_handle.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/can_handle.lua new file mode 100644 index 0000000000..fb39fa8f70 --- /dev/null +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_aeotec_minimote(opts, driver, device, ...) + local FINGERPRINTS = require("zwave-multi-button.aeotec-minimote.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("zwave-multi-button.aeotec-minimote") + end + end + return false +end + +return can_handle_aeotec_minimote diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/fingerprints.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/fingerprints.lua new file mode 100644 index 0000000000..b63e217394 --- /dev/null +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZWAVE_AEOTEC_MINIMOTE_FINGERPRINTS = { + {mfr = 0x0086, prod = 0x0001, model = 0x0003} -- Aeotec Mimimote +} + +return ZWAVE_AEOTEC_MINIMOTE_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/init.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/init.lua index 814bcb775b..05bba2cd8b 100644 --- a/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/init.lua +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/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.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) -local ZWAVE_AEOTEC_MINIMOTE_FINGERPRINTS = { - {mfr = 0x0086, prod = 0x0001, model = 0x0003} -- Aeotec Mimimote -} -local function can_handle_aeotec_minimote(opts, driver, device, ...) - for _, fingerprint in ipairs(ZWAVE_AEOTEC_MINIMOTE_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end local function basic_set_handler(self, device, cmd) local button = cmd.args.value // 40 + 1 @@ -59,7 +38,7 @@ local aeotec_minimote = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_aeotec_minimote, + can_handle = require("zwave-multi-button.aeotec-minimote.can_handle"), } return aeotec_minimote diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/can_handle.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/can_handle.lua new file mode 100644 index 0000000000..a9d7d24be2 --- /dev/null +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_zwave_multi_button(opts, driver, device, ...) + local FINGERPRINTS = require("zwave-multi-button.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("zwave-multi-button") + end + end + return false +end + +return can_handle_zwave_multi_button diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/can_handle.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/can_handle.lua new file mode 100644 index 0000000000..055e4f1eff --- /dev/null +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_keyfob(opts, driver, device, ...) + local FINGERPRINTS = require("zwave-multi-button.fibaro-keyfob.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("zwave-multi-button.fibaro-keyfob") + end + end + return false +end + +return can_handle_fibaro_keyfob diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/fingerprints.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/fingerprints.lua new file mode 100644 index 0000000000..269d136689 --- /dev/null +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZWAVE_FIBARO_KEYFOB_FINGERPRINTS = { + {mfr = 0x010F, prod = 0x1001, model = 0x1000}, -- Fibaro KeyFob EU + {mfr = 0x010F, prod = 0x1001, model = 0x2000}, -- Fibaro KeyFob US + {mfr = 0x010F, prod = 0x1001, model = 0x3000} -- Fibaro KeyFob AU +} + +return ZWAVE_FIBARO_KEYFOB_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/init.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/init.lua index 653cd21ddd..c4138fdcaa 100644 --- a/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/init.lua +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/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 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) -local ZWAVE_FIBARO_KEYFOB_FINGERPRINTS = { - {mfr = 0x010F, prod = 0x1001, model = 0x1000}, -- Fibaro KeyFob EU - {mfr = 0x010F, prod = 0x1001, model = 0x2000}, -- Fibaro KeyFob US - {mfr = 0x010F, prod = 0x1001, model = 0x3000} -- Fibaro KeyFob AU -} -local function can_handle_fibaro_keyfob(opts, driver, device, ...) - for _, fingerprint in ipairs(ZWAVE_FIBARO_KEYFOB_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:refresh() @@ -46,7 +23,7 @@ local fibaro_keyfob = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_fibaro_keyfob, + can_handle = require("zwave-multi-button.fibaro-keyfob.can_handle"), } return fibaro_keyfob diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/fingerprints.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/fingerprints.lua new file mode 100644 index 0000000000..4e539c90ab --- /dev/null +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/fingerprints.lua @@ -0,0 +1,23 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZWAVE_MULTI_BUTTON_FINGERPRINTS = { + {mfr = 0x010F, prod = 0x1001, model = 0x1000}, -- Fibaro KeyFob EU + {mfr = 0x010F, prod = 0x1001, model = 0x2000}, -- Fibaro KeyFob US + {mfr = 0x010F, prod = 0x1001, model = 0x3000}, -- Fibaro KeyFob AU + {mfr = 0x0371, prod = 0x0002, model = 0x0003}, -- Aeotec NanoMote Quad EU + {mfr = 0x0371, prod = 0x0102, model = 0x0003}, -- Aeotec NanoMote Quad US + {mfr = 0x0086, prod = 0x0001, model = 0x0058}, -- Aeotec KeyFob EU + {mfr = 0x0086, prod = 0x0101, model = 0x0058}, -- Aeotec KeyFob US + {mfr = 0x0086, prod = 0x0002, model = 0x0082}, -- Aeotec Wallmote Quad EU + {mfr = 0x0086, prod = 0x0102, model = 0x0082}, -- Aeotec Wallmote Quad US + {mfr = 0x0086, prod = 0x0002, model = 0x0081}, -- Aeotec Wallmote EU + {mfr = 0x0086, prod = 0x0102, model = 0x0081}, -- Aeotec Wallmote US + {mfr = 0x0060, prod = 0x000A, model = 0x0003}, -- Everspring Remote Control + {mfr = 0x0086, prod = 0x0001, model = 0x0003}, -- Aeotec Mimimote, + {mfr = 0x0371, prod = 0x0102, model = 0x0016}, -- Aeotec illumino Wallmote 7, + {mfr = 0x0460, prod = 0x0009, model = 0x0081}, -- Shelly Wave i4, + {mfr = 0x0460, prod = 0x0009, model = 0x0082} -- Shelly Wave i4DC, +} + +return ZWAVE_MULTI_BUTTON_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/init.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/init.lua index 9094dc4111..acad935ad4 100644 --- a/drivers/SmartThings/zwave-button/src/zwave-multi-button/init.lua +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/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 capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -20,33 +11,7 @@ local CentralScene = (require "st.zwave.CommandClass.CentralScene")({ version=1 --- @type st.zwave.CommandClass.SceneActivation local SceneActivation = (require "st.zwave.CommandClass.SceneActivation")({ version=1 }) -local ZWAVE_MULTI_BUTTON_FINGERPRINTS = { - {mfr = 0x010F, prod = 0x1001, model = 0x1000}, -- Fibaro KeyFob EU - {mfr = 0x010F, prod = 0x1001, model = 0x2000}, -- Fibaro KeyFob US - {mfr = 0x010F, prod = 0x1001, model = 0x3000}, -- Fibaro KeyFob AU - {mfr = 0x0371, prod = 0x0002, model = 0x0003}, -- Aeotec NanoMote Quad EU - {mfr = 0x0371, prod = 0x0102, model = 0x0003}, -- Aeotec NanoMote Quad US - {mfr = 0x0086, prod = 0x0001, model = 0x0058}, -- Aeotec KeyFob EU - {mfr = 0x0086, prod = 0x0101, model = 0x0058}, -- Aeotec KeyFob US - {mfr = 0x0086, prod = 0x0002, model = 0x0082}, -- Aeotec Wallmote Quad EU - {mfr = 0x0086, prod = 0x0102, model = 0x0082}, -- Aeotec Wallmote Quad US - {mfr = 0x0086, prod = 0x0002, model = 0x0081}, -- Aeotec Wallmote EU - {mfr = 0x0086, prod = 0x0102, model = 0x0081}, -- Aeotec Wallmote US - {mfr = 0x0060, prod = 0x000A, model = 0x0003}, -- Everspring Remote Control - {mfr = 0x0086, prod = 0x0001, model = 0x0003}, -- Aeotec Mimimote, - {mfr = 0x0371, prod = 0x0102, model = 0x0016}, -- Aeotec illumino Wallmote 7, - {mfr = 0x0460, prod = 0x0009, model = 0x0081}, -- Shelly Wave i4, - {mfr = 0x0460, prod = 0x0009, model = 0x0082} -- Shelly Wave i4DC, -} -local function can_handle_zwave_multi_button(opts, driver, device, ...) - for _, fingerprint in ipairs(ZWAVE_MULTI_BUTTON_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end local map_key_attribute_to_capability = { [CentralScene.key_attributes.KEY_PRESSED_1_TIME] = capabilities.button.button.pushed, @@ -115,12 +80,8 @@ local zwave_multi_button = { lifecycle_handlers = { init = device_init }, - can_handle = can_handle_zwave_multi_button, - sub_drivers = { - require("zwave-multi-button/aeotec-keyfob"), - require("zwave-multi-button/fibaro-keyfob"), - require("zwave-multi-button/aeotec-minimote") - } + can_handle = require("zwave-multi-button.can_handle"), + sub_drivers = require("zwave-multi-button.sub_drivers"), } return zwave_multi_button diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/can_handle.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/can_handle.lua new file mode 100644 index 0000000000..6f532ab2af --- /dev/null +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_shelly_wave_i4(opts, driver, device, ...) + local FINGERPRINTS = require("zwave-multi-button.shelly_wave_i4.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("zwave-multi-button.shelly_wave_i4") + end + end + return false +end + +return can_handle_shelly_wave_i4 diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/fingerprints.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/fingerprints.lua new file mode 100644 index 0000000000..459a811d9a --- /dev/null +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local SHELLY_WAVE_i4_FINGERPRINTS = { + {mfr = 0x0460, prod = 0x0009, model = 0x0081}, -- Shelly Wave i4 + {mfr = 0x0460, prod = 0x0009, model = 0x0082} -- Shelly Wave i4 DC +} + +return SHELLY_WAVE_i4_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/init.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/init.lua index 4e962cebb1..2034e05eb8 100644 --- a/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/init.lua +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/init.lua @@ -1,35 +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 + -- @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=2 }) -local SHELLY_WAVE_i4_FINGERPRINTS = { - {mfr = 0x0460, prod = 0x0009, model = 0x0081}, -- Shelly Wave i4 - {mfr = 0x0460, prod = 0x0009, model = 0x0082} -- Shelly Wave i4 DC -} -local function can_handle_shelly_wave_i4(opts, driver, device, ...) - for _, fingerprint in ipairs(SHELLY_WAVE_i4_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:refresh() @@ -45,7 +23,7 @@ local shelly_wave_i4 = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_shelly_wave_i4, + can_handle = require("zwave-multi-button.shelly_wave_i4.can_handle"), } return shelly_wave_i4 diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/sub_drivers.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/sub_drivers.lua new file mode 100644 index 0000000000..7ec5622dea --- /dev/null +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/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("zwave-multi-button/aeotec-keyfob"), + lazy_load_if_possible("zwave-multi-button/fibaro-keyfob"), + lazy_load_if_possible("zwave-multi-button/aeotec-minimote"), + lazy_load_if_possible("zwave-multi-button/shelly_wave_i4"), +} +return sub_drivers From f97770eb5176711a19dea434fa4f39e1d6b542c9 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 21 Apr 2026 14:20:24 -0500 Subject: [PATCH 278/538] CHAD-18034: Enable shared_device_thread_enabled --- drivers/SmartThings/zwave-button/src/apiv6_bugfix/init.lua | 1 + drivers/SmartThings/zwave-button/src/init.lua | 1 + .../zwave-button/src/zwave-multi-button/aeotec-keyfob/init.lua | 1 + .../zwave-button/src/zwave-multi-button/aeotec-minimote/init.lua | 1 + .../zwave-button/src/zwave-multi-button/fibaro-keyfob/init.lua | 1 + drivers/SmartThings/zwave-button/src/zwave-multi-button/init.lua | 1 + .../zwave-button/src/zwave-multi-button/shelly_wave_i4/init.lua | 1 + 7 files changed, 7 insertions(+) diff --git a/drivers/SmartThings/zwave-button/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-button/src/apiv6_bugfix/init.lua index 2e7e3ca3b8..fd309d8e29 100644 --- a/drivers/SmartThings/zwave-button/src/apiv6_bugfix/init.lua +++ b/drivers/SmartThings/zwave-button/src/apiv6_bugfix/init.lua @@ -18,6 +18,7 @@ local apiv6_bugfix = { }, NAME = "apiv6_bugfix", can_handle = require("apiv6_bugfix.can_handle"), + shared_device_thread_enabled = true, } return apiv6_bugfix diff --git a/drivers/SmartThings/zwave-button/src/init.lua b/drivers/SmartThings/zwave-button/src/init.lua index 73672f3bcb..18469ca56b 100644 --- a/drivers/SmartThings/zwave-button/src/init.lua +++ b/drivers/SmartThings/zwave-button/src/init.lua @@ -32,6 +32,7 @@ local driver_template = { added = added_handler, }, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/init.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/init.lua index ad3f69c41b..13096c0579 100644 --- a/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/init.lua +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-keyfob/init.lua @@ -18,6 +18,7 @@ local aeotec_keyfob = { doConfigure = do_configure }, can_handle = require("zwave-multi-button.aeotec-keyfob.can_handle"), + shared_device_thread_enabled = true, } return aeotec_keyfob diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/init.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/init.lua index 05bba2cd8b..3b8a04d90c 100644 --- a/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/init.lua +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/aeotec-minimote/init.lua @@ -39,6 +39,7 @@ local aeotec_minimote = { doConfigure = do_configure }, can_handle = require("zwave-multi-button.aeotec-minimote.can_handle"), + shared_device_thread_enabled = true, } return aeotec_minimote diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/init.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/init.lua index c4138fdcaa..178d440783 100644 --- a/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/init.lua +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/fibaro-keyfob/init.lua @@ -24,6 +24,7 @@ local fibaro_keyfob = { doConfigure = do_configure }, can_handle = require("zwave-multi-button.fibaro-keyfob.can_handle"), + shared_device_thread_enabled = true, } return fibaro_keyfob diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/init.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/init.lua index acad935ad4..57b9d42be4 100644 --- a/drivers/SmartThings/zwave-button/src/zwave-multi-button/init.lua +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/init.lua @@ -82,6 +82,7 @@ local zwave_multi_button = { }, can_handle = require("zwave-multi-button.can_handle"), sub_drivers = require("zwave-multi-button.sub_drivers"), + shared_device_thread_enabled = true, } return zwave_multi_button diff --git a/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/init.lua b/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/init.lua index 2034e05eb8..4b78cffac1 100644 --- a/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/init.lua +++ b/drivers/SmartThings/zwave-button/src/zwave-multi-button/shelly_wave_i4/init.lua @@ -24,6 +24,7 @@ local shelly_wave_i4 = { doConfigure = do_configure }, can_handle = require("zwave-multi-button.shelly_wave_i4.can_handle"), + shared_device_thread_enabled = true, } return shelly_wave_i4 From 451b95a5616b29babcb1221bcc8deb96698c5889 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:06 -0600 Subject: [PATCH 279/538] CHAD-17088: zwave-garage-door-opener lazy load subdrivers --- .../src/ecolink-zw-gdo/can_handle.lua | 14 +++++++++ .../src/ecolink-zw-gdo/fingerprints.lua | 9 ++++++ .../src/ecolink-zw-gdo/init.lua | 27 ++--------------- .../zwave-garage-door-opener/src/init.lua | 21 +++----------- .../src/lazy_load_subdriver.lua | 18 ++++++++++++ .../src/mimolite-garage-door/can_handle.lua | 14 +++++++++ .../src/mimolite-garage-door/fingerprints.lua | 8 +++++ .../src/mimolite-garage-door/init.lua | 29 +++---------------- .../src/sub_drivers.lua | 9 ++++++ .../test_ecolink_garage_door_operator.lua | 16 ++-------- .../src/test/test_mimolite_garage_door.lua | 16 ++-------- .../test/test_zwave_garage_door_opener.lua | 16 ++-------- 12 files changed, 92 insertions(+), 105 deletions(-) create mode 100644 drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/can_handle.lua create mode 100644 drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-garage-door-opener/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/can_handle.lua create mode 100644 drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-garage-door-opener/src/sub_drivers.lua diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/can_handle.lua b/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/can_handle.lua new file mode 100644 index 0000000000..571ba6bce1 --- /dev/null +++ b/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_ecolink_garage_door(opts, driver, device, ...) + local FINGERPRINTS = require("ecolink-zw-gdo.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("ecolink-zw-gdo") + end + end + return false +end + +return can_handle_ecolink_garage_door diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/fingerprints.lua b/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/fingerprints.lua new file mode 100644 index 0000000000..15de27ad0b --- /dev/null +++ b/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Ecolink garage door operator +local ECOLINK_GARAGE_DOOR_FINGERPRINTS = { + {manufacturerId = 0x014A, productType = 0x0007, productId = 0x4731}, +} + +return ECOLINK_GARAGE_DOOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/init.lua b/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/init.lua index e6638841be..304f6d902a 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/init.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/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 2023 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 --- @type st.capabilities local capabilities = require "st.capabilities" @@ -28,11 +17,6 @@ local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ ve --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 8 }) --- Ecolink garage door operator -local ECOLINK_GARAGE_DOOR_FINGERPRINTS = { - manufacturerId = 0x014A, productType = 0x0007, productId = 0x4731 -} - local GDO_ENDPOINT_NAME = "main" local CONTACTSENSOR_ENDPOINT_NAME = "sensor" local GDO_ENDPOINT_NUMBER = 1 @@ -55,11 +39,6 @@ local GDO_CONFIG_PARAMS = { --- @param driver Driver driver instance --- @param device Device device isntance --- @return boolean true if the device proper, else false -local function can_handle_ecolink_garage_door(opts, driver, device, ...) - return device:id_match(ECOLINK_GARAGE_DOOR_FINGERPRINTS.manufacturerId, - ECOLINK_GARAGE_DOOR_FINGERPRINTS.productType, - ECOLINK_GARAGE_DOOR_FINGERPRINTS.productId) -end local function component_to_endpoint(device, component_id) if (CONTACTSENSOR_ENDPOINT_NAME == component_id) then @@ -282,7 +261,7 @@ local ecolink_garage_door_operator = { doConfigure = configure_device_with_updated_config, infoChanged = configure_device_with_updated_config }, - can_handle = can_handle_ecolink_garage_door + can_handle = require("ecolink-zw-gdo.can_handle"), } return ecolink_garage_door_operator diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/init.lua b/drivers/SmartThings/zwave-garage-door-opener/src/init.lua index 6d0a0a880c..9856682607 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/init.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/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 @@ -24,10 +14,7 @@ local driver_template = { capabilities.doorControl, capabilities.contactSensor, }, - sub_drivers = { - require("mimolite-garage-door"), - require("ecolink-zw-gdo") - } + sub_drivers = require("sub_drivers"), } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-garage-door-opener/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-garage-door-opener/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-garage-door-opener/src/mimolite-garage-door/can_handle.lua b/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/can_handle.lua new file mode 100644 index 0000000000..e515aae646 --- /dev/null +++ b/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_mimolite_garage_door(opts, driver, device, ...) + local FINGERPRINTS = require("mimolite-garage-door.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("mimolite-garage-door") + end + end + return false +end + +return can_handle_mimolite_garage_door diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/fingerprints.lua b/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/fingerprints.lua new file mode 100644 index 0000000000..52f0969e84 --- /dev/null +++ b/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local MIMOLITE_GARAGE_DOOR_FINGERPRINTS = { + { manufacturerId = 0x0084, productType = 0x0453, productId = 0x0111 } -- mimolite garage door +} + +return MIMOLITE_GARAGE_DOOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/init.lua b/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/init.lua index 1fd1b4362b..1427d4abed 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/init.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/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,23 +18,12 @@ local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = --- @type st.zwave.CommandClass.SwitchBinary local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = 2 }) -local MIMOLITE_GARAGE_DOOR_FINGERPRINTS = { - { manufacturerId = 0x0084, productType = 0x0453, productId = 0x0111 } -- mimolite garage door -} --- Determine whether the passed device is mimolite garage door --- --- @param driver Driver driver instance --- @param device Device device isntance --- @return boolean true if the device proper, else false -local function can_handle_mimolite_garage_door(opts, driver, device, ...) - for _, fingerprint in ipairs(MIMOLITE_GARAGE_DOOR_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -end local function door_event_helper(device, value) device:emit_event(value == 0x00 and capabilities.doorControl.door.closed() or capabilities.doorControl.door.open()) @@ -118,7 +97,7 @@ local mimolite_garage_door = { doConfigure = do_configure }, NAME = "mimolite garage door", - can_handle = can_handle_mimolite_garage_door + can_handle = require("mimolite-garage-door.can_handle"), } return mimolite_garage_door diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/sub_drivers.lua b/drivers/SmartThings/zwave-garage-door-opener/src/sub_drivers.lua new file mode 100644 index 0000000000..8de6edfd56 --- /dev/null +++ b/drivers/SmartThings/zwave-garage-door-opener/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("mimolite-garage-door"), + lazy_load_if_possible("ecolink-zw-gdo"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_ecolink_garage_door_operator.lua b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_ecolink_garage_door_operator.lua index 8d0ebfb7a9..0608ca2ef2 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_ecolink_garage_door_operator.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_ecolink_garage_door_operator.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-garage-door-opener/src/test/test_mimolite_garage_door.lua b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_mimolite_garage_door.lua index 319a823daf..cc33391a4c 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_mimolite_garage_door.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_mimolite_garage_door.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-garage-door-opener/src/test/test_zwave_garage_door_opener.lua b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_zwave_garage_door_opener.lua index 1fd7c8a3d6..0d5da468e8 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_zwave_garage_door_opener.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_zwave_garage_door_opener.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" From 48c086cdb3d45739a3e0a88d90c8f68ef9839583 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 21 Apr 2026 14:25:55 -0500 Subject: [PATCH 280/538] CHAD-18036: Enable shared_device_thread_enabled --- .../zwave-garage-door-opener/src/ecolink-zw-gdo/init.lua | 1 + drivers/SmartThings/zwave-garage-door-opener/src/init.lua | 1 + .../zwave-garage-door-opener/src/mimolite-garage-door/init.lua | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/init.lua b/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/init.lua index 304f6d902a..1019056330 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/init.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/ecolink-zw-gdo/init.lua @@ -262,6 +262,7 @@ local ecolink_garage_door_operator = { infoChanged = configure_device_with_updated_config }, can_handle = require("ecolink-zw-gdo.can_handle"), + shared_device_thread_enabled = true, } return ecolink_garage_door_operator diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/init.lua b/drivers/SmartThings/zwave-garage-door-opener/src/init.lua index 9856682607..3286471d83 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/init.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/init.lua @@ -15,6 +15,7 @@ local driver_template = { capabilities.contactSensor, }, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/init.lua b/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/init.lua index 1427d4abed..138a4f1b1d 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/init.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/mimolite-garage-door/init.lua @@ -98,6 +98,7 @@ local mimolite_garage_door = { }, NAME = "mimolite garage door", can_handle = require("mimolite-garage-door.can_handle"), + shared_device_thread_enabled = true, } return mimolite_garage_door From 57128954cb6612fef82fcdcbbe665a0f2dccd85e Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Thu, 16 Apr 2026 13:17:45 -0500 Subject: [PATCH 281/538] Removing pull_request and leaving pull_request_target --- .github/workflows/jenkins-driver-tests.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/jenkins-driver-tests.yml b/.github/workflows/jenkins-driver-tests.yml index 153b8f6913..f04d237442 100644 --- a/.github/workflows/jenkins-driver-tests.yml +++ b/.github/workflows/jenkins-driver-tests.yml @@ -1,16 +1,9 @@ name: Run Jenkins driver tests on: - pull_request: - paths: - - 'drivers/**' - pull_request_target: paths: - 'drivers/**' -permissions: - statuses: write - jobs: trigger-driver-test: strategy: From fa3f457e6616d390fd66efd20be63bf9fe5aa213 Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:22:33 -0500 Subject: [PATCH 282/538] fix feature check for presence sensor device profiling (#2919) --- .../matter-sensor/src/sensor_utils/device_configuration.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/device_configuration.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/device_configuration.lua index 2c5f38524a..ea56ab710e 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/device_configuration.lua @@ -107,7 +107,7 @@ function DeviceConfiguration.match_profile(driver, device, battery_supported) 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 + #device:get_endpoints(clusters.OccupancySensing.ID, {feature_bitmap = clusters.OccupancySensing.types.Feature.VISION}) > 0 then occupancy_support = "-presence" end end From 1d53b8dff671fbaf2cee9a73970b083a50d85347 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Wed, 22 Apr 2026 15:31:35 -0500 Subject: [PATCH 283/538] WWSTCERT-11060 TOFSMYGAA Plug Black/Outdoor (#2911) --- .../matter-switch/fingerprints.yml | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 55f3fc945c..082b41db57 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -925,6 +925,56 @@ matterManufacturer: vendorId: 0x117C productId: 0x8001 deviceProfileName: ikea-2-button-battery + - id: "4476/36885" + deviceLabel: KAJPLATS E12 WS Globe 800lm + vendorId: 0x117C + productId: 0x9015 + deviceProfileName: light-level-colorTemperature + - id: "4476/36880" + deviceLabel: VARMBLIXT table/wall lamp + vendorId: 0x117C + productId: 0x9010 + deviceProfileName: light-color-level + - id: "4476/36881" + deviceLabel: KAJPLATS E26 450lm + vendorId: 0x117C + productId: 0x9011 + deviceProfileName: light-level-colorTemperature + - id: "4476/36882" + deviceLabel: KAJPLATS E26 1100lm + vendorId: 0x117C + productId: 0x9012 + deviceProfileName: light-level-colorTemperature + - id: "4476/36884" + deviceLabel: KAJPLATS E26 WS 1600lm + vendorId: 0x117C + productId: 0x9014 + deviceProfileName: light-level-colorTemperature + - id: "4476/36886" + deviceLabel: KAJPLATS E26 1100lm CWS + vendorId: 0x117C + productId: 0x9016 + deviceProfileName: light-color-level + - id: "4476/36887" + deviceLabel: KAJPLATS E12 CWS Globe 800lm + vendorId: 0x117C + productId: 0x9017 + deviceProfileName: light-color-level + - id: "4476/36889" + deviceLabel: KAJPLATS E12 WS B38 CL 470lm + vendorId: 0x117C + productId: 0x9019 + deviceProfileName: light-level-colorTemperature + - id: "17526/16534" + deviceLabel: GRILLPLATS plug + vendorId: 0x4476 + productId: 0x4096 + deviceProfileName: plug-power-energy-powerConsumption + - id: "17526/16535" + deviceLabel: TOFSMYGGA + vendorId: 0x4476 + productId: 0x4097 + deviceProfileName: plug-power-energy-powerConsumption #Innovation Matters - id: "4978/1" deviceLabel: M2D Bridge From e62738bc76839e785b2deb3a327ffc746b74756a Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Wed, 22 Apr 2026 15:31:35 -0500 Subject: [PATCH 284/538] WWSTCERT-11060 TOFSMYGAA Plug Black/Outdoor (#2911) --- .../matter-switch/fingerprints.yml | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 132f792085..623dad6806 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -925,6 +925,56 @@ matterManufacturer: vendorId: 0x117C productId: 0x8001 deviceProfileName: ikea-2-button-battery + - id: "4476/36885" + deviceLabel: KAJPLATS E12 WS Globe 800lm + vendorId: 0x117C + productId: 0x9015 + deviceProfileName: light-level-colorTemperature + - id: "4476/36880" + deviceLabel: VARMBLIXT table/wall lamp + vendorId: 0x117C + productId: 0x9010 + deviceProfileName: light-color-level + - id: "4476/36881" + deviceLabel: KAJPLATS E26 450lm + vendorId: 0x117C + productId: 0x9011 + deviceProfileName: light-level-colorTemperature + - id: "4476/36882" + deviceLabel: KAJPLATS E26 1100lm + vendorId: 0x117C + productId: 0x9012 + deviceProfileName: light-level-colorTemperature + - id: "4476/36884" + deviceLabel: KAJPLATS E26 WS 1600lm + vendorId: 0x117C + productId: 0x9014 + deviceProfileName: light-level-colorTemperature + - id: "4476/36886" + deviceLabel: KAJPLATS E26 1100lm CWS + vendorId: 0x117C + productId: 0x9016 + deviceProfileName: light-color-level + - id: "4476/36887" + deviceLabel: KAJPLATS E12 CWS Globe 800lm + vendorId: 0x117C + productId: 0x9017 + deviceProfileName: light-color-level + - id: "4476/36889" + deviceLabel: KAJPLATS E12 WS B38 CL 470lm + vendorId: 0x117C + productId: 0x9019 + deviceProfileName: light-level-colorTemperature + - id: "17526/16534" + deviceLabel: GRILLPLATS plug + vendorId: 0x4476 + productId: 0x4096 + deviceProfileName: plug-power-energy-powerConsumption + - id: "17526/16535" + deviceLabel: TOFSMYGGA + vendorId: 0x4476 + productId: 0x4097 + deviceProfileName: plug-power-energy-powerConsumption #Innovation Matters - id: "4978/1" deviceLabel: M2D Bridge From 5a6541704071420f9f746b11fe0c3f658c8bb1fc Mon Sep 17 00:00:00 2001 From: cjswedes Date: Wed, 22 Apr 2026 09:24:42 -0500 Subject: [PATCH 285/538] Fix ledvance unit tests Device init messages must be disabled before adding the mock device to the test framework for these tests. --- .../src/test/test_ledvance_metering_plug.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua b/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua index a2087797d4..6e63bba1a9 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua @@ -23,6 +23,7 @@ local mock_device = test.mock_device.build_test_zigbee_device( zigbee_test_utils.prepare_zigbee_env_info() local function test_init() + test.disable_startup_messages() test.mock_device.add_test_device(mock_device) end @@ -37,7 +38,10 @@ test.register_coroutine_test( test.wait_for_events() assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == 1) assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 100) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -49,7 +53,10 @@ test.register_coroutine_test( test.wait_for_events() assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == 5) assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 1000) - end + end, + { + min_api_version = 15 + } ) test.run_registered_tests() From f79b3d56e29266a6d2991ec73f2cc1e1721f459b Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu, 23 Apr 2026 16:16:00 -0500 Subject: [PATCH 286/538] Add Stateless Native Handler Registration (#2915) --- .../src/stateless_handlers/init.lua | 6 +++ .../test/test_all_capability_zigbee_bulb.lua | 48 +++++++++++++++++++ .../src/test/test_sengled_color_temp_bulb.lua | 3 +- 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua b/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua index 5dc1f18b3c..6b0c2c9bfe 100644 --- a/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua @@ -18,6 +18,9 @@ local OPTIONS_MASK = 0x01 -- default: The `ExecuteIfOff` option is overriden local IGNORE_COMMAND_IF_OFF = 0x00 -- default: the command will not be executed if the device is off local function step_color_temperature_by_percent_handler(driver, device, cmd) + if type(device.register_native_capability_cmd_handler) == "function" then + device:register_native_capability_cmd_handler(cmd.capability, cmd.command) + end local step_percent_change = cmd.args and cmd.args.stepSize or 0 if step_percent_change == 0 then return end -- Reminder, stepSize > 0 == Kelvin UP == Mireds DOWN. stepSize < 0 == Kelvin DOWN == Mireds UP @@ -34,6 +37,9 @@ local function step_color_temperature_by_percent_handler(driver, device, cmd) end local function step_level_handler(driver, device, cmd) + if type(device.register_native_capability_cmd_handler) == "function" then + device:register_native_capability_cmd_handler(cmd.capability, cmd.command) + end local step_size = st_utils.round((cmd.args and cmd.args.stepSize or 0)/100.0 * 254) if step_size == 0 then return end local step_mode = (step_size > 0) and clusters.Level.types.MoveStepMode.UP or clusters.Level.types.MoveStepMode.DOWN 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 20e1c513d1..8826ca535d 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 @@ -313,6 +313,14 @@ test.register_message_test( { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { 20 } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "statelessColorTemperatureStep", capability_cmd_id = "stepColorTemperatureByPercent" } + } + }, { channel = "zigbee", direction = "send", @@ -329,6 +337,14 @@ test.register_message_test( { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { 90 } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "statelessColorTemperatureStep", capability_cmd_id = "stepColorTemperatureByPercent" } + } + }, { channel = "zigbee", direction = "send", @@ -345,6 +361,14 @@ test.register_message_test( { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { -50 } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "statelessColorTemperatureStep", capability_cmd_id = "stepColorTemperatureByPercent" } + } + }, { channel = "zigbee", direction = "send", @@ -370,6 +394,14 @@ test.register_message_test( { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 25 } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "statelessSwitchLevelStep", capability_cmd_id = "stepLevel" } + } + }, { channel = "zigbee", direction = "send", @@ -386,6 +418,14 @@ test.register_message_test( { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { -50 } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "statelessSwitchLevelStep", capability_cmd_id = "stepLevel" } + } + }, { channel = "zigbee", direction = "send", @@ -402,6 +442,14 @@ test.register_message_test( { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 100 } } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "statelessSwitchLevelStep", capability_cmd_id = "stepLevel" } + } + }, { channel = "zigbee", direction = "send", 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 ae99183527..80241b4ca9 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 @@ -289,6 +289,7 @@ test.register_coroutine_test( test.wait_for_events() test.socket.capability:__queue_receive({mock_device.id, { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { 20 } } }) + mock_device:expect_native_cmd_handler_registration("statelessColorTemperatureStep", "stepColorTemperatureByPercent") test.socket.zigbee:__expect_send( { mock_device.id, @@ -305,7 +306,7 @@ test.register_coroutine_test( "Step Level command test", function() test.socket.capability:__queue_receive({mock_device.id, { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 25 } } }) - + mock_device:expect_native_cmd_handler_registration("statelessSwitchLevelStep", "stepLevel") test.socket.zigbee:__expect_send( { mock_device.id, From d7849aee6a58c2d228fb3b1da51e6be6686f4d9e Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu, 23 Apr 2026 16:30:59 -0500 Subject: [PATCH 287/538] Philips Hue: Add support for statelessSwitchLevelStep and statelessColorTemperatureStep capabilities (#2916) --- .../philips-hue/profiles/legacy-color.yml | 2 + .../philips-hue/profiles/white-ambiance.yml | 4 + .../profiles/white-and-color-ambiance.yml | 4 + .../philips-hue/profiles/white.yml | 2 + .../philips-hue/src/handlers/commands.lua | 215 +++++++----------- .../SmartThings/philips-hue/src/hue/api.lua | 48 ++++ .../philips-hue/src/hue_driver_template.lua | 8 + 7 files changed, 156 insertions(+), 127 deletions(-) diff --git a/drivers/SmartThings/philips-hue/profiles/legacy-color.yml b/drivers/SmartThings/philips-hue/profiles/legacy-color.yml index fa3adedb6d..2d906dab24 100644 --- a/drivers/SmartThings/philips-hue/profiles/legacy-color.yml +++ b/drivers/SmartThings/philips-hue/profiles/legacy-color.yml @@ -6,6 +6,8 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: colorControl version: 1 - id: samsungim.hueSyncMode diff --git a/drivers/SmartThings/philips-hue/profiles/white-ambiance.yml b/drivers/SmartThings/philips-hue/profiles/white-ambiance.yml index b7c6efc7eb..354b8bbc2e 100644 --- a/drivers/SmartThings/philips-hue/profiles/white-ambiance.yml +++ b/drivers/SmartThings/philips-hue/profiles/white-ambiance.yml @@ -6,8 +6,12 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: colorTemperature version: 1 + - id: statelessColorTemperatureStep + version: 1 - id: samsungim.hueSyncMode version: 1 - id: refresh diff --git a/drivers/SmartThings/philips-hue/profiles/white-and-color-ambiance.yml b/drivers/SmartThings/philips-hue/profiles/white-and-color-ambiance.yml index 35fa5550bd..7fe8797be2 100644 --- a/drivers/SmartThings/philips-hue/profiles/white-and-color-ambiance.yml +++ b/drivers/SmartThings/philips-hue/profiles/white-and-color-ambiance.yml @@ -6,10 +6,14 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: colorControl version: 1 - id: colorTemperature version: 1 + - id: statelessColorTemperatureStep + version: 1 - id: samsungim.hueSyncMode version: 1 - id: refresh diff --git a/drivers/SmartThings/philips-hue/profiles/white.yml b/drivers/SmartThings/philips-hue/profiles/white.yml index 447ddcad81..18312c48e2 100644 --- a/drivers/SmartThings/philips-hue/profiles/white.yml +++ b/drivers/SmartThings/philips-hue/profiles/white.yml @@ -6,6 +6,8 @@ components: version: 1 - id: switchLevel version: 1 + - id: statelessSwitchLevelStep + version: 1 - id: samsungim.hueSyncMode version: 1 - id: refresh diff --git a/drivers/SmartThings/philips-hue/src/handlers/commands.lua b/drivers/SmartThings/philips-hue/src/handlers/commands.lua index cf30834a2c..6bee932239 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/commands.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/commands.lua @@ -19,9 +19,7 @@ local CommandHandlers = {} ---@param driver HueDriver ---@param device HueChildDevice ----@param args table -local function do_switch_action(driver, device, args) - local on = args.command == "on" +local function get_light_device_id_and_hue_api_module(driver, device) local id = device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) local bridge_device = utils.get_hue_bridge_for_device(driver, device, id) @@ -49,14 +47,19 @@ local function do_switch_action(driver, device, args) return end - local resp, err = hue_api:set_light_on_state(light_id, on) + return light_id, hue_api +end - if not resp or (resp.errors and #resp.errors == 0) then +---@param response table? Command response from the Hue API, expected to have an 'errors' field if there were issues +---@param err string? Error message returned from the Hue API call, if any +---@param action_desc string Description of the action being performed, for logging purposes +local function log_command_response_errors(response, err, action_desc) + if not response or (response.errors and #response.errors == 0) then if err ~= nil then - log.error_with({ hub_logs = true }, "Error performing on/off action: " .. err) - elseif resp and #resp.errors > 0 then - for _, error in ipairs(resp.errors) do - log.error_with({ hub_logs = true }, "Error returned in Hue response: " .. error.description) + log.error_with({ hub_logs = true }, "Error performing " .. action_desc .. ": " .. err) + elseif response and #response.errors > 0 then + for _, error in ipairs(response.errors) do + log.error_with({ hub_logs = true }, "Error returned in Hue response for " .. action_desc .. ": " .. error.description) end end end @@ -65,60 +68,30 @@ end ---@param driver HueDriver ---@param device HueChildDevice ---@param args table -local function do_switch_level_action(driver, device, args) - local level = st_utils.clamp_value(args.args.level, 1, 100) - local id = device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) - local bridge_device = utils.get_hue_bridge_for_device(driver, device, id) - - if not bridge_device then - log.warn( - "Couldn't get a bridge for light with Child Key " .. - (device.parent_assigned_child_key or "unexpected nil parent_assigned_child_key")) - return - end +local function do_switch_action(driver, device, args) + local on = args.command == "on" + local light_id, hue_api = get_light_device_id_and_hue_api_module(driver, device) + if not (light_id and hue_api) then return end - local light_id = utils.get_hue_rid(device) - local hue_api = bridge_device:get_field(Fields.BRIDGE_API) --[[@as PhilipsHueApi]] + local resp, err = hue_api:set_light_on_state(light_id, on) + log_command_response_errors(resp, err, "on/off action") +end - if not (light_id and hue_api) then - log.warn( - string.format( - "Could not get a proper light resource ID or API instance for %s" .. - "\n\tLight Resource ID: %s" .. - "\n\tHue API nil? %s", - (device.label or device.id or "unknown device"), - light_id, - (hue_api == nil) - ) - ) - return - end +---@param driver HueDriver +---@param device HueChildDevice +---@param args table +local function do_switch_level_action(driver, device, args) + local level = st_utils.clamp_value(args.args.level, 1, 100) + local light_id, hue_api = get_light_device_id_and_hue_api_module(driver, device) + if not (light_id and hue_api) then return end local is_off = device:get_field(Fields.SWITCH_STATE) == "off" - if is_off then local resp, err = hue_api:set_light_on_state(light_id, true) - if not resp or (resp.errors and #resp.errors == 0) then - if err ~= nil then - log.error_with({ hub_logs = true }, "Error performing on/off action: " .. err) - elseif resp and #resp.errors > 0 then - for _, error in ipairs(resp.errors) do - log.error_with({ hub_logs = true }, "Error returned in Hue response: " .. error.description) - end - end - end + log_command_response_errors(resp, err, "on/off action") end - local resp, err = hue_api:set_light_level(light_id, level) - if not resp or (resp.errors and #resp.errors == 0) then - if err ~= nil then - log.error_with({ hub_logs = true }, "Error performing switch level action: " .. err) - elseif resp and #resp.errors > 0 then - for _, error in ipairs(resp.errors) do - log.error_with({ hub_logs = true }, "Error returned in Hue response: " .. error.description) - end - end - end + log_command_response_errors(resp, err, "switch level action") end ---@param driver HueDriver @@ -130,46 +103,13 @@ local function do_color_action(driver, device, args) hue = 0 device:set_field(Fields.WRAPPED_HUE, true) end - local id = device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) - local bridge_device = utils.get_hue_bridge_for_device(driver, device, id) - - if not bridge_device then - log.warn( - "Couldn't get a bridge for light with Child Key " .. - (device.parent_assigned_child_key or "unexpected nil parent_assigned_child_key")) - return - end - - local light_id = utils.get_hue_rid(device) - local hue_api = bridge_device:get_field(Fields.BRIDGE_API) --[[@as PhilipsHueApi]] - - if not (light_id and hue_api) then - log.warn( - string.format( - "Could not get a proper light resource ID or API instance for %s" .. - "\n\tLight Resource ID: %s" .. - "\n\tHue API nil? %s", - (device.label or device.id or "unknown device"), - light_id, - (hue_api == nil) - ) - ) - return - end + local light_id, hue_api = get_light_device_id_and_hue_api_module(driver, device) + if not (light_id and hue_api) then return end local red, green, blue = st_utils.hsv_to_rgb(hue, sat) local xy = HueColorUtils.safe_rgb_to_xy(red, green, blue, device:get_field(Fields.GAMUT)) - local resp, err = hue_api:set_light_color_xy(light_id, xy) - if not resp or (resp.errors and #resp.errors == 0) then - if err ~= nil then - log.error_with({ hub_logs = true }, "Error performing color action: " .. err) - elseif resp and #resp.errors > 0 then - for _, error in ipairs(resp.errors) do - log.error_with({ hub_logs = true }, "Error returned in Hue response: " .. error.description) - end - end - end + log_command_response_errors(resp, err, "color action") end -- Function to allow changes to "setHue" attribute to Philips Hue light devices @@ -207,51 +147,58 @@ end ---@param args table local function do_color_temp_action(driver, device, args) local kelvin = args.args.temperature - local id = device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) - local bridge_device = utils.get_hue_bridge_for_device(driver, device, id) - - if not bridge_device then - log.warn( - "Couldn't get a bridge for light with Child Key " .. - (device.parent_assigned_child_key or "unexpected nil parent_assigned_child_key")) - return - end - - local light_id = utils.get_hue_rid(device) - local hue_api = bridge_device:get_field(Fields.BRIDGE_API) --[[@as PhilipsHueApi]] - - if not (light_id and hue_api) then - log.warn( - string.format( - "Could not get a proper light resource ID or API instance for %s" .. - "\n\tLight Resource ID: %s" .. - "\n\tHue API nil? %s", - (device.label or device.id or "unknown device"), - light_id, - (hue_api == nil) - ) - ) - return - end + local light_id, hue_api = get_light_device_id_and_hue_api_module(driver, device) + if not (light_id and hue_api) then return end local min = device:get_field(Fields.MIN_KELVIN) or Consts.MIN_TEMP_KELVIN_WHITE_AMBIANCE local clamped_kelvin = st_utils.clamp_value(kelvin, min, Consts.MAX_TEMP_KELVIN) local mirek = math.floor(utils.kelvin_to_mirek(clamped_kelvin)) local resp, err = hue_api:set_light_color_temp(light_id, mirek) - - if not resp or (resp.errors and #resp.errors == 0) then - if err ~= nil then - log.error_with({ hub_logs = true }, "Error performing color temp action: " .. err) - elseif resp and #resp.errors > 0 then - for _, error in ipairs(resp.errors) do - log.error_with({ hub_logs = true }, "Error returned in Hue response: " .. error.description) - end - end - end + log_command_response_errors(resp, err, "color temp action") device:set_field(Fields.COLOR_TEMP_SETPOINT, clamped_kelvin); end + +---@param driver HueDriver +---@param device HueChildDevice +---@param args table +local function do_step_level_action(driver, device, args) + local step_percent = args.args and args.args.stepSize or 0 + if step_percent == 0 then return end + local light_id, hue_api = get_light_device_id_and_hue_api_module(driver, device) + if not (light_id and hue_api) then return end + + -- stepSize is already in percent; Hue brightness_delta is also in percent + local action = (step_percent > 0) and "up" or "down" + local brightness_delta = math.abs(step_percent) + local resp, err = hue_api:set_light_level_delta(light_id, brightness_delta, action) + log_command_response_errors(resp, err, "step level action") +end + +---@param driver HueDriver +---@param device HueChildDevice +---@param args table +local function do_step_color_temp_action(driver, device, args) + local step_percent = args.args and args.args.stepSize or 0 + if step_percent == 0 then return end + local light_id, hue_api = get_light_device_id_and_hue_api_module(driver, device) + if not (light_id and hue_api) then return end + + -- Reminder, stepSize > 0 == Kelvin UP == Mireds DOWN. stepSize < 0 == Kelvin DOWN == Mireds UP + local action = (step_percent > 0) and "down" or "up" + + -- Derive the mirek range from stored Kelvin bounds (note: higher Kelvin = lower mirek) + local min_kelvin = device:get_field(Fields.MIN_KELVIN) or Consts.MIN_TEMP_KELVIN_WHITE_AMBIANCE + local max_kelvin = device:get_field(Fields.MAX_KELVIN) or Consts.MAX_TEMP_KELVIN + local min_mirek = math.floor(utils.kelvin_to_mirek(max_kelvin)) + local max_mirek = math.ceil(utils.kelvin_to_mirek(min_kelvin)) + local mirek_delta = st_utils.round((max_mirek - min_mirek) * (math.abs(step_percent) / 100.0)) + + local resp, err = hue_api:set_light_color_temp_delta(light_id, mirek_delta, action) + log_command_response_errors(resp, err, "step color temp action") +end + ---@param driver HueDriver ---@param device HueChildDevice ---@param args table @@ -301,6 +248,20 @@ function CommandHandlers.set_color_temp_handler(driver, device, args) do_color_temp_action(driver, device, args) end +---@param driver HueDriver +---@param device HueChildDevice +---@param args table +function CommandHandlers.step_level_handler(driver, device, args) + do_step_level_action(driver, device, args) +end + +---@param driver HueDriver +---@param device HueChildDevice +---@param args table +function CommandHandlers.step_color_temp_handler(driver, device, args) + do_step_color_temp_action(driver, device, args) +end + local refresh_handlers = require "handlers.refresh_handlers" ---@param driver HueDriver diff --git a/drivers/SmartThings/philips-hue/src/hue/api.lua b/drivers/SmartThings/philips-hue/src/hue/api.lua index e05fa3d0a0..6039b395cc 100644 --- a/drivers/SmartThings/philips-hue/src/hue/api.lua +++ b/drivers/SmartThings/philips-hue/src/hue/api.lua @@ -507,4 +507,52 @@ function PhilipsHueApi:set_light_color_temp_by_device_type(id, mirek, device_typ end end +---@param id string +---@param brightness_delta number absolute brightness percentage delta +---@param action "up"|"down" +---@return { errors: table[], [string]: any }? response json payload in response to the request, nil on error +---@return string? err error, nil on successful HTTP request but the response may indicate a problem with the request itself. +function PhilipsHueApi:set_light_level_delta(id, brightness_delta, action) + return self:set_light_level_delta_by_device_type(id, brightness_delta, action, HueDeviceTypes.LIGHT) +end + +function PhilipsHueApi:set_grouped_light_level_delta(id, brightness_delta, action) + return self:set_light_level_delta_by_device_type(id, brightness_delta, action, GROUPED_LIGHT) +end + +function PhilipsHueApi:set_light_level_delta_by_device_type(id, brightness_delta, action, device_type) + if type(brightness_delta) == "number" then + local url = string.format("/clip/v2/resource/%s/%s", device_type, id) + local payload = json.encode { dimming_delta = { action = action, brightness_delta = brightness_delta } } + return do_put(self, url, payload) + else + return nil, + string.format("Expected number for brightness delta, received %s", st_utils.stringify_table(brightness_delta, nil, false)) + end +end + +---@param id string +---@param mirek_delta number absolute mirek delta +---@param action "up"|"down" +---@return { errors: table[], [string]: any }? response json payload in response to the request, nil on error +---@return string? err error, nil on successful HTTP request but the response may indicate a problem with the request itself. +function PhilipsHueApi:set_light_color_temp_delta(id, mirek_delta, action) + return self:set_light_color_temp_delta_by_device_type(id, mirek_delta, action, HueDeviceTypes.LIGHT) +end + +function PhilipsHueApi:set_grouped_light_color_temp_delta(id, mirek_delta, action) + return self:set_light_color_temp_delta_by_device_type(id, mirek_delta, action, GROUPED_LIGHT) +end + +function PhilipsHueApi:set_light_color_temp_delta_by_device_type(id, mirek_delta, action, device_type) + if type(mirek_delta) == "number" then + local url = string.format("/clip/v2/resource/%s/%s", device_type, id) + local payload = json.encode { color_temperature_delta = { action = action, mirek_delta = mirek_delta } } + return do_put(self, url, payload) + else + return nil, + string.format("Expected number for color temp mirek delta, received %s", st_utils.stringify_table(mirek_delta, nil, false)) + end +end + return PhilipsHueApi diff --git a/drivers/SmartThings/philips-hue/src/hue_driver_template.lua b/drivers/SmartThings/philips-hue/src/hue_driver_template.lua index 3ffd02b3b6..1c32b260e9 100644 --- a/drivers/SmartThings/philips-hue/src/hue_driver_template.lua +++ b/drivers/SmartThings/philips-hue/src/hue_driver_template.lua @@ -59,6 +59,8 @@ local set_color_handler = utils.safe_wrap_handler(command_handlers.set_color_han local set_hue_handler = utils.safe_wrap_handler(command_handlers.set_hue_handler) local set_saturation_handler = utils.safe_wrap_handler(command_handlers.set_saturation_handler) local set_color_temp_handler = utils.safe_wrap_handler(command_handlers.set_color_temp_handler) +local step_level_handler = utils.safe_wrap_handler(command_handlers.step_level_handler) +local step_color_temp_handler = utils.safe_wrap_handler(command_handlers.step_color_temp_handler) --- @class HueDriverDatastore --- @field public bridge_netinfo table @@ -105,6 +107,12 @@ function HueDriver.new_driver_template(dbg_config) [capabilities.colorTemperature.ID] = { [capabilities.colorTemperature.commands.setColorTemperature.NAME] = set_color_temp_handler, }, + [capabilities.statelessSwitchLevelStep.ID] = { + [capabilities.statelessSwitchLevelStep.commands.stepLevel.NAME] = step_level_handler, + }, + [capabilities.statelessColorTemperatureStep.ID] = { + [capabilities.statelessColorTemperatureStep.commands.stepColorTemperatureByPercent.NAME] = step_color_temp_handler, + }, }, -- override the default capability message handler if batched receives are supported From 2300a3336798dfec386222cae499491b4ed23cb4 Mon Sep 17 00:00:00 2001 From: Konrad K <33450498+KKlimczukS@users.noreply.github.com> Date: Mon, 27 Apr 2026 18:38:01 +0200 Subject: [PATCH 288/538] updates in SNZB-04PR2 and SNZB-04P fingerprints (WWSTCERT-10731, WWSTCERT-10704) (#2909) --- .../SmartThings/zigbee-contact/fingerprints.yml | 4 ++-- .../profiles/contact-battery-tamper.yml | 16 ---------------- 2 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml diff --git a/drivers/SmartThings/zigbee-contact/fingerprints.yml b/drivers/SmartThings/zigbee-contact/fingerprints.yml index b3eaa9ca95..56e1599c96 100644 --- a/drivers/SmartThings/zigbee-contact/fingerprints.yml +++ b/drivers/SmartThings/zigbee-contact/fingerprints.yml @@ -208,12 +208,12 @@ zigbeeManufacturer: deviceLabel: SONOFF Contact Sensor manufacturer: eWeLink model: SNZB-04P - deviceProfileName: contact-battery-profile + deviceProfileName: contact-battery-profile - id: "SONOFF/SNZB-04PR2" deviceLabel: SONOFF Contact Sensor manufacturer: SONOFF model: SNZB-04PR2 - deviceProfileName: contact-battery-profile + deviceProfileName: contact-battery-profile - id: "Aug. Winkhaus SE/FM.V.ZB" deviceLabel: Funkkontakt FM.V.ZB manufacturer: Aug. Winkhaus SE diff --git a/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml deleted file mode 100644 index 524783af37..0000000000 --- a/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: contact-battery-tamper -components: -- id: main - capabilities: - - id: contactSensor - version: 1 - - id: battery - version: 1 - - id: tamperAlert - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: ContactSensor From 4e4c07378c205fc79a3eec4af73e7f625be1ec2c Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Mon, 27 Apr 2026 11:52:07 -0500 Subject: [PATCH 289/538] WWSTCERT-11195 Govee Smart Bulb PAR38 (#2925) --- .../matter-switch/fingerprints.yml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 082b41db57..8f0566ec7f 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -783,6 +783,36 @@ matterManufacturer: vendorId: 0x1387 productId: 0x6056 deviceProfileName: light-color-level + - id: "4999/5281" + deviceLabel: Govee Smart Bulb PAR38 + vendorId: 0x1387 + productId: 0x14A1 + deviceProfileName: light-color-level + - id: "4999/5121" + deviceLabel: Govee Smart Bulb A21 1600lm + vendorId: 0x1387 + productId: 0x1401 + deviceProfileName: light-color-level + - id: "4999/5313" + deviceLabel: Govee Edison Bulb + vendorId: 0x1387 + productId: 0x14C1 + deviceProfileName: light-color-level + - id: "4999/5312" + deviceLabel: Govee Edison Bulb + vendorId: 0x1387 + productId: 0x14C0 + deviceProfileName: light-color-level + - id: "4999/5680" + deviceLabel: Govee Lantern Floor Lamp + vendorId: 0x1387 + productId: 0x1630 + deviceProfileName: light-color-level + - id: "4999/5953" + deviceLabel: Govee Table Lamp Classic + vendorId: 0x1387 + productId: 0x1741 + deviceProfileName: light-color-level # Hager - id: "4741/8" deviceLabel: Hager matter 2 buttons (battery) From a6e93dd602395850be29216ceedf624a37e8149f Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Mon, 27 Apr 2026 11:56:58 -0500 Subject: [PATCH 290/538] WWSTCERT-11168 Smart Radiator Thermostat X (#2924) --- drivers/SmartThings/matter-thermostat/fingerprints.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/SmartThings/matter-thermostat/fingerprints.yml b/drivers/SmartThings/matter-thermostat/fingerprints.yml index cd1e7c5cbd..b33e03f791 100644 --- a/drivers/SmartThings/matter-thermostat/fingerprints.yml +++ b/drivers/SmartThings/matter-thermostat/fingerprints.yml @@ -89,6 +89,16 @@ matterManufacturer: vendorId: 0x134E productId: 0x0002 deviceProfileName: thermostat-humidity-heating-only-nostate-nobattery + - id: "4942/9" + deviceLabel: Smart Radiator Thermostat X + vendorId: 0x134E + productId: 0x0009 + deviceProfileName: thermostat-humidity-heating-only-nostate-batteryLevel + - id: "4942/8" + deviceLabel: Wireless Temperature Sensor X (2nd Gen) + vendorId: 0x134E + productId: 0x0008 + deviceProfileName: thermostat-humidity-heating-only-nostate-batteryLevel #Taruie - id: "5151/4101" deviceLabel: TARUIE AC Remote From d958b4793f9107c532ff5d84af769affad69ee5e Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 27 Apr 2026 19:53:23 -0500 Subject: [PATCH 291/538] Matter/Zigbee Switch: Make transition time for stateless capabilities configurable (#2921) --- .../src/switch_handlers/capability_handlers.lua | 16 +++++++++------- .../matter-switch/src/switch_utils/fields.lua | 9 +++++++-- .../src/test/test_stateless_step.lua | 12 ++++++------ .../src/stateless_handlers/init.lua | 8 +++++--- .../zigbee-switch/src/switch_utils.lua | 5 +++++ 5 files changed, 32 insertions(+), 18 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua index 9bf402c8ad..13b66892a2 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua @@ -57,7 +57,8 @@ function CapabilityHandlers.handle_step_level(driver, device, cmd) if step_size == 0 then return end local endpoint_id = device:component_to_endpoint(cmd.component) local step_mode = step_size > 0 and clusters.LevelControl.types.StepMode.UP or clusters.LevelControl.types.StepMode.DOWN - device:send(clusters.LevelControl.server.commands.Step(device, endpoint_id, step_mode, math.abs(step_size), fields.TRANSITION_TIME_FAST, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF)) + local transition_time = device:get_field(fields.TRANSITION_TIME.SWITCH_LEVEL_STEP) or fields.DEFAULT_STEP_TRANSITION_TIME + device:send(clusters.LevelControl.server.commands.Step(device, endpoint_id, step_mode, math.abs(step_size), transition_time, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF)) end @@ -70,10 +71,10 @@ function CapabilityHandlers.handle_set_color(driver, device, cmd) if switch_utils.tbl_contains(huesat_endpoints, endpoint_id) then local hue = switch_utils.convert_huesat_st_to_matter(cmd.args.color.hue) local sat = switch_utils.convert_huesat_st_to_matter(cmd.args.color.saturation) - req = clusters.ColorControl.server.commands.MoveToHueAndSaturation(device, endpoint_id, hue, sat, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) + req = clusters.ColorControl.server.commands.MoveToHueAndSaturation(device, endpoint_id, hue, sat, fields.ZERO_TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) else local x, y, _ = st_utils.safe_hsv_to_xy(cmd.args.color.hue, cmd.args.color.saturation) - req = clusters.ColorControl.server.commands.MoveToColor(device, endpoint_id, x, y, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) + req = clusters.ColorControl.server.commands.MoveToColor(device, endpoint_id, x, y, fields.ZERO_TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) end device:send(req) end @@ -83,7 +84,7 @@ function CapabilityHandlers.handle_set_hue(driver, device, cmd) local huesat_endpoints = device:get_endpoints(clusters.ColorControl.ID, {feature_bitmap = clusters.ColorControl.FeatureMap.HUE_AND_SATURATION}) if switch_utils.tbl_contains(huesat_endpoints, endpoint_id) then local hue = switch_utils.convert_huesat_st_to_matter(cmd.args.hue) - local req = clusters.ColorControl.server.commands.MoveToHue(device, endpoint_id, hue, 0, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) + local req = clusters.ColorControl.server.commands.MoveToHue(device, endpoint_id, hue, 0, fields.ZERO_TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) device:send(req) else device.log.warn("Device does not support huesat features on its color control cluster") @@ -95,7 +96,7 @@ function CapabilityHandlers.handle_set_saturation(driver, device, cmd) local huesat_endpoints = device:get_endpoints(clusters.ColorControl.ID, {feature_bitmap = clusters.ColorControl.FeatureMap.HUE_AND_SATURATION}) if switch_utils.tbl_contains(huesat_endpoints, endpoint_id) then local sat = switch_utils.convert_huesat_st_to_matter(cmd.args.saturation) - local req = clusters.ColorControl.server.commands.MoveToSaturation(device, endpoint_id, sat, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) + local req = clusters.ColorControl.server.commands.MoveToSaturation(device, endpoint_id, sat, fields.ZERO_TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) device:send(req) else device.log.warn("Device does not support huesat features on its color control cluster") @@ -117,7 +118,7 @@ function CapabilityHandlers.handle_set_color_temperature(driver, device, cmd) elseif max_temp_kelvin ~= nil and temp_in_kelvin >= max_temp_kelvin then temp_in_mired = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) end - local req = clusters.ColorControl.server.commands.MoveToColorTemperature(device, endpoint_id, temp_in_mired, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) + local req = clusters.ColorControl.server.commands.MoveToColorTemperature(device, endpoint_id, temp_in_mired, fields.ZERO_TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) device:set_field(fields.MOST_RECENT_TEMP, cmd.args.temperature, {persist = true}) device:send(req) end @@ -138,7 +139,8 @@ function CapabilityHandlers.handle_step_color_temperature_by_percent(driver, dev local min_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) or fields.DEFAULT_MIRED_MIN local max_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MAX, endpoint_id) or fields.DEFAULT_MIRED_MAX local step_size_in_mireds = st_utils.round((max_mireds - min_mireds) * (math.abs(step_percent_change)/100.0)) - device:send(clusters.ColorControl.server.commands.StepColorTemperature(device, endpoint_id, step_mode, step_size_in_mireds, fields.TRANSITION_TIME_FAST, min_mireds, max_mireds, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF)) + local transition_time = device:get_field(fields.TRANSITION_TIME.COLOR_TEMP_STEP) or fields.DEFAULT_STEP_TRANSITION_TIME + device:send(clusters.ColorControl.server.commands.StepColorTemperature(device, endpoint_id, step_mode, step_size_in_mireds, transition_time, min_mireds, max_mireds, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF)) end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 39a60e5eaa..0ecdbb4ba2 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -194,8 +194,13 @@ SwitchFields.TEMP_BOUND_RECEIVED = "__temp_bound_received" SwitchFields.TEMP_MIN = "__temp_min" SwitchFields.TEMP_MAX = "__temp_max" -SwitchFields.TRANSITION_TIME = 0 -- number of 10ths of a second -SwitchFields.TRANSITION_TIME_FAST = 3 -- 0.3 seconds +SwitchFields.ZERO_TRANSITION_TIME = 0 -- 0.0 seconds +SwitchFields.DEFAULT_STEP_TRANSITION_TIME = 3 -- 0.3 seconds, measured in tenths of a second as per the Matter spec + +SwitchFields.TRANSITION_TIME = { + SWITCH_LEVEL_STEP = "__switch_level_step_transition_time", + COLOR_TEMP_STEP = "__color_temp_step_transition_time", +} -- For Level/Color Control cluster commands, this field indicates which bits in the OptionsOverride field are valid. In this case, we specify that the ExecuteIfOff option (bit 1) may be overridden. SwitchFields.OPTIONS_MASK = 0x01 diff --git a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua index 1022acd795..a92c547ac7 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua @@ -75,7 +75,7 @@ test.register_message_test( direction = "send", message = { mock_device_color_temp.id, - clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.DOWN, 60, fields.TRANSITION_TIME_FAST, fields.DEFAULT_MIRED_MIN, fields.DEFAULT_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.DOWN, 60, fields.DEFAULT_STEP_TRANSITION_TIME, fields.DEFAULT_MIRED_MIN, fields.DEFAULT_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) }, }, { @@ -99,7 +99,7 @@ test.register_message_test( direction = "send", message = { mock_device_color_temp.id, - clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.DOWN, 271, fields.TRANSITION_TIME_FAST, fields.DEFAULT_MIRED_MIN, fields.DEFAULT_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.DOWN, 271, fields.DEFAULT_STEP_TRANSITION_TIME, fields.DEFAULT_MIRED_MIN, fields.DEFAULT_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) }, }, { @@ -123,7 +123,7 @@ test.register_message_test( direction = "send", message = { mock_device_color_temp.id, - clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.UP, 151, fields.TRANSITION_TIME_FAST, fields.DEFAULT_MIRED_MIN, fields.DEFAULT_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + clusters.ColorControl.server.commands.StepColorTemperature(mock_device_color_temp, 1, clusters.ColorControl.types.StepModeEnum.UP, 151, fields.DEFAULT_STEP_TRANSITION_TIME, fields.DEFAULT_MIRED_MIN, fields.DEFAULT_MIRED_MAX, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) }, } }, @@ -157,7 +157,7 @@ test.register_message_test( direction = "send", message = { mock_device_color_temp.id, - clusters.LevelControl.server.commands.Step(mock_device_color_temp, 1, clusters.LevelControl.types.StepModeEnum.UP, 64, fields.TRANSITION_TIME_FAST, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + clusters.LevelControl.server.commands.Step(mock_device_color_temp, 1, clusters.LevelControl.types.StepModeEnum.UP, 64, fields.DEFAULT_STEP_TRANSITION_TIME, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) }, }, { @@ -181,7 +181,7 @@ test.register_message_test( direction = "send", message = { mock_device_color_temp.id, - clusters.LevelControl.server.commands.Step(mock_device_color_temp, 1, clusters.LevelControl.types.StepModeEnum.DOWN, 127, fields.TRANSITION_TIME_FAST, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + clusters.LevelControl.server.commands.Step(mock_device_color_temp, 1, clusters.LevelControl.types.StepModeEnum.DOWN, 127, fields.DEFAULT_STEP_TRANSITION_TIME, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) }, }, { @@ -205,7 +205,7 @@ test.register_message_test( direction = "send", message = { mock_device_color_temp.id, - clusters.LevelControl.server.commands.Step(mock_device_color_temp, 1, clusters.LevelControl.types.StepModeEnum.UP, 254, fields.TRANSITION_TIME_FAST, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + clusters.LevelControl.server.commands.Step(mock_device_color_temp, 1, clusters.LevelControl.types.StepModeEnum.UP, 254, fields.DEFAULT_STEP_TRANSITION_TIME, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) }, } }, diff --git a/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua b/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua index 6b0c2c9bfe..c697a20c7d 100644 --- a/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua @@ -11,7 +11,7 @@ local DEFAULT_MIRED_MAX_BOUND = 370 -- 2700 Kelvin (Mireds are the inverse of Ke local DEFAULT_MIRED_MIN_BOUND = 154 -- 6500 Kelvin (Mireds are the inverse of Kelvin) -- Transition Time: The time that shall be taken to perform the step change, in units of 1/10ths of a second. -local TRANSITION_TIME = 3 -- default: 0.3 seconds +local DEFAULT_STEP_TRANSITION_TIME = 3 -- 0.3 seconds -- Options Mask & Override: Indicates which options are being overridden by the Level/ColorControl cluster commands local OPTIONS_MASK = 0x01 -- default: The `ExecuteIfOff` option is overriden @@ -23,6 +23,7 @@ local function step_color_temperature_by_percent_handler(driver, device, cmd) end local step_percent_change = cmd.args and cmd.args.stepSize or 0 if step_percent_change == 0 then return end + local transition_time = device:get_field(switch_utils.COLOR_TEMP_STEP_TRANSITION_TIME) or DEFAULT_STEP_TRANSITION_TIME -- Reminder, stepSize > 0 == Kelvin UP == Mireds DOWN. stepSize < 0 == Kelvin DOWN == Mireds UP local step_mode = (step_percent_change > 0) and clusters.ColorControl.types.CcStepMode.DOWN or clusters.ColorControl.types.CcStepMode.UP local min_mireds = device:get_field(switch_utils.MIRED_MIN_BOUND) @@ -33,7 +34,7 @@ local function step_color_temperature_by_percent_handler(driver, device, cmd) max_mireds = DEFAULT_MIRED_MAX_BOUND end local step_size_in_mireds = st_utils.round((max_mireds - min_mireds) * (math.abs(step_percent_change)/100.0)) - device:send(clusters.ColorControl.server.commands.StepColorTemperature(device, step_mode, step_size_in_mireds, TRANSITION_TIME, min_mireds, max_mireds, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF)) + device:send(clusters.ColorControl.server.commands.StepColorTemperature(device, step_mode, step_size_in_mireds, transition_time, min_mireds, max_mireds, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF)) end local function step_level_handler(driver, device, cmd) @@ -42,8 +43,9 @@ local function step_level_handler(driver, device, cmd) end local step_size = st_utils.round((cmd.args and cmd.args.stepSize or 0)/100.0 * 254) if step_size == 0 then return end + local transition_time = device:get_field(switch_utils.SWITCH_LEVEL_STEP_TRANSITION_TIME) or DEFAULT_STEP_TRANSITION_TIME local step_mode = (step_size > 0) and clusters.Level.types.MoveStepMode.UP or clusters.Level.types.MoveStepMode.DOWN - device:send(clusters.Level.server.commands.Step(device, step_mode, math.abs(step_size), TRANSITION_TIME, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF)) + device:send(clusters.Level.server.commands.Step(device, step_mode, math.abs(step_size), transition_time, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF)) end local stateless_handlers = { diff --git a/drivers/SmartThings/zigbee-switch/src/switch_utils.lua b/drivers/SmartThings/zigbee-switch/src/switch_utils.lua index d30ada0588..e974d52474 100644 --- a/drivers/SmartThings/zigbee-switch/src/switch_utils.lua +++ b/drivers/SmartThings/zigbee-switch/src/switch_utils.lua @@ -8,6 +8,11 @@ local switch_utils = {} switch_utils.MIRED_MAX_BOUND = "__max_mired_bound" switch_utils.MIRED_MIN_BOUND = "__min_mired_bound" +-- Fields to store the transition times for the stateless capabilities, +-- in case native handler implementations need to be re-configured in the future +switch_utils.SWITCH_LEVEL_STEP_TRANSITION_TIME = "__switch_level_step_transition_time" +switch_utils.COLOR_TEMP_STEP_TRANSITION_TIME = "__color_temp_step_transition_time" + switch_utils.MIREDS_CONVERSION_CONSTANT = 1000000 switch_utils.convert_mired_to_kelvin = function(mired) From e85bd15414406990150cf9f1a1243ac35704dbce Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 28 Apr 2026 09:33:18 -0500 Subject: [PATCH 292/538] Matter Switch: Use defaults bounds if any custom bound is missing (#2877) --- .../src/switch_handlers/capability_handlers.lua | 9 +++++++-- .../matter-switch/src/switch_utils/fields.lua | 14 ++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua index 13b66892a2..4e87f1bfdc 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua @@ -136,8 +136,13 @@ function CapabilityHandlers.handle_step_color_temperature_by_percent(driver, dev local endpoint_id = device:component_to_endpoint(cmd.component) -- before the Matter 1.3 lua libs update (HUB FW 55), there was no ColorControl StepModeEnum type defined local step_mode = step_percent_change > 0 and (clusters.ColorControl.types.StepModeEnum and clusters.ColorControl.types.StepModeEnum.DOWN or 3) or (clusters.ColorControl.types.StepModeEnum and clusters.ColorControl.types.StepModeEnum.UP or 1) - local min_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) or fields.DEFAULT_MIRED_MIN - local max_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MAX, endpoint_id) or fields.DEFAULT_MIRED_MAX + local min_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) + local max_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MAX, endpoint_id) + -- since colorTemperatureRange is only set after both custom bounds are, use defaults if any custom bound is missing + if not (min_mireds and max_mireds) then + min_mireds = fields.DEFAULT_MIRED_MIN + max_mireds = fields.DEFAULT_MIRED_MAX + end local step_size_in_mireds = st_utils.round((max_mireds - min_mireds) * (math.abs(step_percent_change)/100.0)) local transition_time = device:get_field(fields.TRANSITION_TIME.COLOR_TEMP_STEP) or fields.DEFAULT_STEP_TRANSITION_TIME device:send(clusters.ColorControl.server.commands.StepColorTemperature(device, endpoint_id, step_mode, step_size_in_mireds, transition_time, min_mireds, max_mireds, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF)) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 0ecdbb4ba2..cb93b6331a 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -1,8 +1,6 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 -local st_utils = require "st.utils" - local SwitchFields = {} SwitchFields.MOST_RECENT_TEMP = "mostRecentTemp" @@ -13,16 +11,12 @@ 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 = st_utils.round(SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MIN) -- 1000 Mireds -SwitchFields.COLOR_TEMPERATURE_MIRED_MIN = st_utils.round(SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MAX) -- 67 Mireds +SwitchFields.COLOR_TEMPERATURE_MIRED_MIN = 67 -- 15000 Kelvin +SwitchFields.COLOR_TEMPERATURE_MIRED_MAX = 1000 -- 1000 Kelvin -- These values are the config bounds in the default Matter profiles (e.g. light-level-colorTemperature, light-color-level) -local DEFAULT_KELVIN_MIN = 2200 -local DEFAULT_KELVIN_MAX = 6500 -SwitchFields.DEFAULT_MIRED_MIN = st_utils.round(SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/DEFAULT_KELVIN_MAX) -- 154 Mireds -SwitchFields.DEFAULT_MIRED_MAX = st_utils.round(SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/DEFAULT_KELVIN_MIN) -- 455 Mireds +SwitchFields.DEFAULT_MIRED_MIN = 154 -- 6500 Kelvin +SwitchFields.DEFAULT_MIRED_MAX = 455 -- 2200 Kelvin SwitchFields.SWITCH_LEVEL_LIGHTING_MIN = 1 SwitchFields.CURRENT_HUESAT_ATTR_MIN = 0 From adc9ff964e83555c29cbcecd1dec6261a28d1bd7 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:11:15 -0500 Subject: [PATCH 293/538] Matter Switch: use parent device for get_field calls in capability commands (#2930) * update matter tests to provide more clarity around parent/child and other onoff device types --- .../switch_handlers/capability_handlers.lua | 17 +- .../test/test_light_illuminance_motion.lua | 79 ++ ...est_matter_on_off_device_configuration.lua | 306 ++++++ .../test/test_matter_on_off_parent_child.lua | 869 ++++++++++++++++++ .../test/test_matter_switch_device_types.lua | 804 ---------------- .../src/test/test_matter_water_valve.lua | 13 + .../test_multi_switch_parent_child_lights.lua | 740 --------------- .../test_multi_switch_parent_child_plugs.lua | 720 --------------- .../src/stateless_handlers/init.lua | 6 +- 9 files changed, 1281 insertions(+), 2273 deletions(-) create mode 100644 drivers/SmartThings/matter-switch/src/test/test_matter_on_off_device_configuration.lua create mode 100644 drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua delete mode 100644 drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua delete mode 100644 drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua delete mode 100644 drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua index 4e87f1bfdc..030a54a751 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua @@ -109,14 +109,16 @@ end function CapabilityHandlers.handle_set_color_temperature(driver, device, cmd) local endpoint_id = device:component_to_endpoint(cmd.component) local temp_in_kelvin = cmd.args.temperature - local min_temp_kelvin = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_KELVIN..fields.COLOR_TEMP_MIN, endpoint_id) - local max_temp_kelvin = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_KELVIN..fields.COLOR_TEMP_MAX, endpoint_id) + -- note: the field containing the color temp bounds will be associated with a parent device + local field_device = device:get_parent_device() or device + local min_temp_kelvin = switch_utils.get_field_for_endpoint(field_device, fields.COLOR_TEMP_BOUND_RECEIVED_KELVIN..fields.COLOR_TEMP_MIN, endpoint_id) + local max_temp_kelvin = switch_utils.get_field_for_endpoint(field_device, fields.COLOR_TEMP_BOUND_RECEIVED_KELVIN..fields.COLOR_TEMP_MAX, endpoint_id) local temp_in_mired = st_utils.round(fields.MIRED_KELVIN_CONVERSION_CONSTANT/temp_in_kelvin) if min_temp_kelvin ~= nil and temp_in_kelvin <= min_temp_kelvin then - temp_in_mired = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MAX, endpoint_id) + temp_in_mired = switch_utils.get_field_for_endpoint(field_device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MAX, endpoint_id) elseif max_temp_kelvin ~= nil and temp_in_kelvin >= max_temp_kelvin then - temp_in_mired = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) + temp_in_mired = switch_utils.get_field_for_endpoint(field_device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) end local req = clusters.ColorControl.server.commands.MoveToColorTemperature(device, endpoint_id, temp_in_mired, fields.ZERO_TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) device:set_field(fields.MOST_RECENT_TEMP, cmd.args.temperature, {persist = true}) @@ -136,8 +138,10 @@ function CapabilityHandlers.handle_step_color_temperature_by_percent(driver, dev local endpoint_id = device:component_to_endpoint(cmd.component) -- before the Matter 1.3 lua libs update (HUB FW 55), there was no ColorControl StepModeEnum type defined local step_mode = step_percent_change > 0 and (clusters.ColorControl.types.StepModeEnum and clusters.ColorControl.types.StepModeEnum.DOWN or 3) or (clusters.ColorControl.types.StepModeEnum and clusters.ColorControl.types.StepModeEnum.UP or 1) - local min_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) - local max_mireds = switch_utils.get_field_for_endpoint(device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MAX, endpoint_id) + -- note: the field containing the color temp bounds will be associated with a parent device + local field_device = device:get_parent_device() or device + local min_mireds = switch_utils.get_field_for_endpoint(field_device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) + local max_mireds = switch_utils.get_field_for_endpoint(field_device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MAX, endpoint_id) -- since colorTemperatureRange is only set after both custom bounds are, use defaults if any custom bound is missing if not (min_mireds and max_mireds) then min_mireds = fields.DEFAULT_MIRED_MIN @@ -148,7 +152,6 @@ function CapabilityHandlers.handle_step_color_temperature_by_percent(driver, dev device:send(clusters.ColorControl.server.commands.StepColorTemperature(device, endpoint_id, step_mode, step_size_in_mireds, transition_time, min_mireds, max_mireds, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF)) end - -- [[ VALVE CAPABILITY COMMANDS ]] -- function CapabilityHandlers.handle_valve_open(driver, device, cmd) 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 ccf952a824..02feaa245b 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 @@ -656,4 +656,83 @@ test.register_message_test( } ) +local generic_manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000 } +local generic_matter_version = { hardware = 1, software = 1 } +local root_endpoint = { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0016, device_type_revision = 1} -- RootNode + } +} + +local mock_device_light_level_motion = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("light-level-motion.yml"), + manufacturer_info = generic_manufacturer_info, + matter_version = generic_matter_version, + endpoints = { + root_endpoint, + { + endpoint_id = 1, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER"} + }, + device_types = { + {device_type_id = 0x0101, device_type_revision = 1} -- Dimmable Light + } + }, + { + endpoint_id = 2, + clusters = { + {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- Occupancy Sensor + } + } + } +}) + +local function test_init_light_level_motion() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device_light_level_motion) +end + +test.register_coroutine_test( + "Test init and doConfigure for Dimmable Light device type with Occupancy Sensor", + function() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.OccupancySensing.attributes.Occupancy + } + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_light_level_motion) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_light_level_motion)) + end + end + + test.socket.device_lifecycle:__queue_receive({ mock_device_light_level_motion.id, "init" }) + 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, + { + test_init = test_init_light_level_motion, + min_api_version = 17 + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_device_configuration.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_device_configuration.lua new file mode 100644 index 0000000000..6c3d168c91 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_device_configuration.lua @@ -0,0 +1,306 @@ +-- 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" + +test.disable_startup_messages() + + +local generic_manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000 } +local generic_matter_version = { hardware = 1, software = 1 } +local root_endpoint = { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0016, device_type_revision = 1} -- RootNode + } +} + + +local mock_device_onoff_switch_as_server = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("matter-thing.yml"), + manufacturer_info = generic_manufacturer_info, + matter_version = generic_matter_version, + endpoints = { + root_endpoint, + { + endpoint_id = 1, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, + }, + device_types = { + {device_type_id = 0x0103, device_type_revision = 1} -- On/Off Light Switch + } + } + } +}) + +test.register_coroutine_test( + "Test profile change on init for onoff parent cluster as server", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_onoff_switch_as_server.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_onoff_switch_as_server.id, "init" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_onoff_switch_as_server.id, "doConfigure" }) + mock_device_onoff_switch_as_server:expect_metadata_update({ profile = "switch-binary" }) + mock_device_onoff_switch_as_server:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + test_init = function() test.mock_device.add_test_device(mock_device_onoff_switch_as_server) end, + min_api_version = 17 + } +) + + +local mock_device_onoff_switch_as_client = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("matter-thing.yml"), + manufacturer_info = generic_manufacturer_info, + matter_version = generic_matter_version, + endpoints = { + root_endpoint, + { + endpoint_id = 1, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "CLIENT", cluster_revision = 1, feature_map = 0}, + }, + device_types = { + {device_type_id = 0x0103, device_type_revision = 1} -- On/Off Light Switch + } + } + } +}) + +test.register_coroutine_test( + "Test init for onoff parent cluster as client", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_onoff_switch_as_client.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_onoff_switch_as_client.id, "init" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_onoff_switch_as_client.id, "doConfigure" }) + mock_device_onoff_switch_as_client:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + test_init = function() test.mock_device.add_test_device(mock_device_onoff_switch_as_client) end, + min_api_version = 17 + } +) + + +local mock_device_dimmer_switch_as_server = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("matter-thing.yml"), + manufacturer_info = generic_manufacturer_info, + matter_version = generic_matter_version, + endpoints = { + root_endpoint, + { + endpoint_id = 1, + 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 = 0x0104, device_type_revision = 1} -- Dimmer Switch + } + } + } +}) + +test.register_coroutine_test( + "Test profile change on init for dimmer parent cluster as server", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_dimmer_switch_as_server.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_dimmer_switch_as_server.id, "init" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_dimmer_switch_as_server.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_dimmer_switch_as_server.id, + clusters.LevelControl.attributes.Options:write(mock_device_dimmer_switch_as_server, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + mock_device_dimmer_switch_as_server:expect_metadata_update({ profile = "switch-level" }) + mock_device_dimmer_switch_as_server:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + test_init = function() test.mock_device.add_test_device(mock_device_dimmer_switch_as_server) end, + min_api_version = 17 + } +) + + +local mock_device_plug_with_switch_profile_vendor_override = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("switch-binary.yml"), + manufacturer_info = { vendor_id = 0x142B, product_id = 0x1003}, -- this device has a vendor override to join as a switch instead of a plug + matter_version = generic_matter_version, + endpoints = { + root_endpoint, + { + 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 + } + } + } +}) + +test.register_coroutine_test( + "Test init for device with requiring the switch category as a vendor override", + function() + local mock_device = mock_device_plug_with_switch_profile_vendor_override + local subscribe_request = clusters.OnOff.attributes.OnOff:subscribe(mock_device) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + 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 = "switch-binary" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + test_init = function() test.mock_device.add_test_device(mock_device_plug_with_switch_profile_vendor_override) end, + min_api_version = 17 + } +) + + +local mock_device_color_dimmer = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("matter-thing.yml"), + manufacturer_info = generic_manufacturer_info, + matter_version = generic_matter_version, + endpoints = { + root_endpoint, + { + 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.ColorControl.ID, cluster_type = "CLIENT", feature_map = 31}, + + }, + device_types = { + {device_type_id = 0x0105, device_type_revision = 1} -- Color Dimmer Switch + } + } + } +}) + +test.register_coroutine_test( + "Test profile change on init for color dimmer device type as server", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_color_dimmer.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_color_dimmer.id, "init" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_color_dimmer.id, "doConfigure" }) + mock_device_color_dimmer:expect_metadata_update({ profile = "switch-color-level" }) + mock_device_color_dimmer:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + test_init = function() test.mock_device.add_test_device(mock_device_color_dimmer) end, + min_api_version = 17 + } +) + + +local mock_device_mounted_on_off_control = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("switch-binary.yml"), + manufacturer_info = generic_manufacturer_info, + matter_version = generic_matter_version, + endpoints = { + root_endpoint, + { + 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 = "SERVER", feature_map = 2}, + + }, + device_types = { + {device_type_id = 0x010F, device_type_revision = 1} -- Mounted On/Off Control + } + } + } +}) + +test.register_coroutine_test( + "Test init for mounted onoff control", + function() + local subscribe_request = clusters.OnOff.attributes.OnOff:subscribe(mock_device_mounted_on_off_control) + + test.socket.device_lifecycle:__queue_receive({ mock_device_mounted_on_off_control.id, "added" }) + 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, "init" }) + 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, + { + test_init = function() test.mock_device.add_test_device(mock_device_mounted_on_off_control) end, + min_api_version = 17 + } +) + + +local mock_device_mounted_dimmable_load_control = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("switch-level.yml"), + manufacturer_info = generic_manufacturer_info, + matter_version = generic_matter_version, + endpoints = { + root_endpoint, + { + 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 = "SERVER", feature_map = 2}, + + }, + device_types = { + {device_type_id = 0x0110, device_type_revision = 1} -- Mounted Dimmable Load Control + } + } + } +}) + +test.register_coroutine_test( + "Test init for mounted dimmable load control", + function() + 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 + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_mounted_dimmable_load_control)) + end + end + test.socket.device_lifecycle:__queue_receive({ mock_device_mounted_dimmable_load_control.id, "added" }) + 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, "init" }) + 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, + { + test_init = function() test.mock_device.add_test_device(mock_device_mounted_dimmable_load_control) end, + min_api_version = 17 + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua new file mode 100644 index 0000000000..624edc6e49 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua @@ -0,0 +1,869 @@ +-- Copyright © 2026 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 fields = require "switch_utils.fields" +local switch_utils = require "switch_utils.utils" + +test.disable_startup_messages() + +local generic_manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000 } +local generic_matter_version = { hardware = 1, software = 1 } +local root_endpoint = { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0016, device_type_revision = 1} -- RootNode + } +} + +local parent_ep_id = 10 +local dimmable_ep_id = 30 +local extended_color_ep_id = 50 + +-- this parent device would fingerprint as light-color-level, since the most feature-rich endpoint is the extended color one, +-- but it should re-configure to light-binary in doConfigure +local mock_device = test.mock_device.build_test_matter_device({ + label = "Matter Switch", + profile = t_utils.get_profile_definition("light-color-level.yml"), + manufacturer_info = generic_manufacturer_info, + matter_version = generic_matter_version, + endpoints = { + root_endpoint, + { + endpoint_id = parent_ep_id, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0100, device_type_revision = 2} -- On/Off Light + } + }, + { + endpoint_id = extended_color_ep_id, + 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 = dimmable_ep_id, + 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 = 0x0100, device_type_revision = 2}, -- On/Off Light + {device_type_id = 0x0101, device_type_revision = 2} -- Dimmable Light + } + }, + } +}) + +local child_profiles = { + [dimmable_ep_id] = t_utils.get_profile_definition("light-level.yml"), + [extended_color_ep_id] = 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_id and endpoint.endpoint_id ~= 0 then + local child_data = { + 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) + } + mock_children[endpoint.endpoint_id] = test.mock_device.build_test_child_device(child_data) + end +end + +local function handle_init_event(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 expected_subscriptions = cluster_subscribe_list[1]:subscribe(mock_device) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + expected_subscriptions:merge(cluster:subscribe(mock_device)) + end + end + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.socket.matter:__expect_send({mock_device.id, expected_subscriptions}) +end + +local function handle_do_configure_event(mock_device) + 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, extended_color_ep_id, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, extended_color_ep_id, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, dimmable_ep_id, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + + mock_device:expect_metadata_update({ profile = "light-binary" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + + mock_device:expect_device_create({ + type = "EDGE_CHILD", + label = "Matter Switch 2", + profile = "light-level", + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%d", dimmable_ep_id) + }) + + mock_device:expect_device_create({ + type = "EDGE_CHILD", + label = "Matter Switch 3", + profile = "light-color-level", + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%d", extended_color_ep_id) + }) +end + +local function test_init_for_lifecycle_tests() + test.mock_device.add_test_device(mock_device) + for _, child in pairs(mock_children) do + test.mock_device.add_test_device(child) + end +end + +-- due to device copy logic in the integration tests, we need to handle init and doConfigure before generating an infoChanged event +local function test_init_for_generate_info_changed_tests() + test.mock_device.add_test_device(mock_device) + for _, child in pairs(mock_children) do + test.mock_device.add_test_device(child) + end + handle_init_event(mock_device) + handle_do_configure_event(mock_device) +end + +local function test_init_for_post_configure_tests() + test.mock_device.add_test_device(mock_device) + for _, child in pairs(mock_children) do + test.mock_device.add_test_device(child) + end + local FIND_CHILD_KEY = "__find_child_fn" + mock_device:set_field(FIND_CHILD_KEY, switch_utils.find_child, { persist = false }) + mock_device:set_field(fields.IS_PARENT_CHILD_DEVICE, true, { persist = false }) +end + +test.set_test_init_function(test_init_for_post_configure_tests) + +test.register_coroutine_test( + "Handle initial init lifecycle event, before children are created", + function() + handle_init_event(mock_device) + test.wait_for_events() + assert(mock_device:get_field(fields.profiling_data.POWER_TOPOLOGY) == false, "Device should be marked as not needing to configure power topology") + assert(mock_device:get_field(fields.profiling_data.BATTERY_SUPPORT) == fields.battery_support.NO_BATTERY, "Device should be marked as having no battery") + end, + { + test_init = test_init_for_lifecycle_tests, + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Handle doConfigure lifecycle event", + function() + mock_device:set_field(fields.profiling_data.BATTERY_SUPPORT, false, { persist = true }) + mock_device:set_field(fields.profiling_data.POWER_TOPOLOGY, false, { persist = true }) + handle_do_configure_event(mock_device) + test.wait_for_events() + local FIND_CHILD_KEY = "__find_child_fn" + assert(type(mock_device:get_field(FIND_CHILD_KEY)) == "function", "Child find function should be stored in doConfigure") + end, + { + test_init = test_init_for_lifecycle_tests, + min_api_version = 17 + } +) + +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[dimmable_ep_id]:expect_metadata_update({ profile = "light-level" }) + mock_children[extended_color_ep_id]:expect_metadata_update({ profile = "light-color-level" }) + mock_device:expect_metadata_update({ profile = "light-binary" }) + end, + { + test_init = test_init_for_generate_info_changed_tests, + min_api_version = 17 + } +) + + +test.register_message_test( + "Dimmable Child: Current level cluster reports generate switch level events appropriately", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.server.attributes.CurrentLevel:build_test_report_data(mock_device, dimmable_ep_id, 50) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[dimmable_ep_id]: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" } + } + }, + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Children: Level Control Min and max attributes set switch level constraints appropriately", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.attributes.MinLevel:build_test_report_data(mock_device, dimmable_ep_id, 1) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.attributes.MaxLevel:build_test_report_data(mock_device, dimmable_ep_id, 254) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[dimmable_ep_id]: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, extended_color_ep_id, 127) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.attributes.MaxLevel:build_test_report_data(mock_device, extended_color_ep_id, 203) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[extended_color_ep_id]:generate_test_message("main", capabilities.switchLevel.levelRange({minimum = 50, maximum = 80})) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Extended Color Child: 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, extended_color_ep_id, 15091) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, extended_color_ep_id, 21547) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[extended_color_ep_id]:generate_test_message("main", capabilities.colorControl.hue(50)) + }, + { + channel = "capability", + direction = "send", + message = mock_children[extended_color_ep_id]:generate_test_message("main", capabilities.colorControl.saturation(72)) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Extended Color Child: colorTemperatureRange, setColorTemperature, stepColorTemperatureByPercent handled appropriately", + { + -- setColorTemperature before a color temperature range is set + { + channel = "capability", + direction = "receive", + message = { + mock_children[extended_color_ep_id].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, extended_color_ep_id, 15182, 21547, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToColor:build_test_command_response(mock_device, extended_color_ep_id) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, extended_color_ep_id, 15091) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, extended_color_ep_id, 21547) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[extended_color_ep_id]:generate_test_message("main", capabilities.colorControl.hue(50)) + }, + { + channel = "capability", + direction = "send", + message = mock_children[extended_color_ep_id]:generate_test_message("main", capabilities.colorControl.saturation(72)) + }, + + -- colorTemperatureRange testing + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, extended_color_ep_id, 153) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, extended_color_ep_id, 555) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[extended_color_ep_id]:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 1800, maximum = 6500})) + }, + + -- setColorTemperature testing + { + channel = "capability", + direction = "receive", + message = { + mock_children[extended_color_ep_id].id, + { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {1800} } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, extended_color_ep_id, 555, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) + } + }, -- 555 is expected since it is re-bounded by the given range + + -- stepColorTemperatureByPercent testing + { + channel = "capability", + direction = "receive", + message = { + mock_children[extended_color_ep_id].id, + { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { 20 } } + } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_children[extended_color_ep_id].id, capability_id = "statelessColorTemperatureStep", capability_cmd_id = "stepColorTemperatureByPercent" } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.ColorControl.server.commands.StepColorTemperature(mock_device, extended_color_ep_id, clusters.ColorControl.types.StepModeEnum.DOWN, 80, fields.TRANSITION_TIME_FAST, 153, 555, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + }, + }, + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Parent: switch capability <-> On Off cluster should handle events appropriately", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "switch", component = "main", command = "on", args = { } } + } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "on" } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.On(mock_device, parent_ep_id) + }, + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, parent_ep_id, 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" } + } + }, + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Children: switch capability <-> On Off Cluster should handle events appropriately", + { + { + channel = "capability", + direction = "receive", + message = { + mock_children[dimmable_ep_id].id, + { capability = "switch", component = "main", command = "on", args = { } } + } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_children[dimmable_ep_id].id, capability_id = "switch", capability_cmd_id = "on" } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.On(mock_device, dimmable_ep_id) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, dimmable_ep_id, true) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[dimmable_ep_id]: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" } + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_children[extended_color_ep_id].id, + { capability = "switch", component = "main", command = "on", args = { } } + } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_children[extended_color_ep_id].id, capability_id = "switch", capability_cmd_id = "on" } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.On(mock_device, extended_color_ep_id) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, extended_color_ep_id, true) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[extended_color_ep_id]: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" } + } + }, + }, + { + min_api_version = 17 + } +) + +local dimmable_child_plug_ep_id = 30 + +local mock_plug = test.mock_device.build_test_matter_device({ + label = "Matter Plug", + profile = t_utils.get_profile_definition("plug-level.yml"), + manufacturer_info = generic_manufacturer_info, + matter_version = generic_matter_version, + endpoints = { + root_endpoint, + { + endpoint_id = parent_ep_id, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = fields.DEVICE_TYPE_ID.ON_OFF_PLUG_IN_UNIT, device_type_revision = 2} + } + }, + { + endpoint_id = dimmable_child_plug_ep_id, + 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 = fields.DEVICE_TYPE_ID.DIMMABLE_PLUG_IN_UNIT, device_type_revision = 2} + } + }, + } +}) + +local function handle_init_event_for_plug(mock_device) + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel + } + local expected_subscriptions = cluster_subscribe_list[1]:subscribe(mock_device) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + expected_subscriptions:merge(cluster:subscribe(mock_device)) + end + end + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.socket.matter:__expect_send({mock_device.id, expected_subscriptions}) +end + +local function handle_do_configure_event_for_plug(mock_device) + 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, dimmable_child_plug_ep_id, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + mock_device:expect_metadata_update({ profile = "plug-binary" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + + mock_device:expect_device_create({ + type = "EDGE_CHILD", + label = "Matter Plug 2", + profile = "plug-level", + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%d", dimmable_child_plug_ep_id) + }) +end + +test.register_coroutine_test( + "Plug: Handle initial init lifecycle event, before children are created", + function() + handle_init_event_for_plug(mock_plug) + test.wait_for_events() + assert(mock_plug:get_field(fields.profiling_data.POWER_TOPOLOGY) == false, "Device should be marked as not needing to configure power topology") + assert(mock_plug:get_field(fields.profiling_data.BATTERY_SUPPORT) == fields.battery_support.NO_BATTERY, "Device should be marked as having no battery") + end, + { + test_init = function() + test.mock_device.add_test_device(mock_plug) + end, + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Plug: Handle doConfigure lifecycle event", + function() + mock_plug:set_field(fields.profiling_data.BATTERY_SUPPORT, false, { persist = true }) + mock_plug:set_field(fields.profiling_data.POWER_TOPOLOGY, false, { persist = true }) + handle_do_configure_event_for_plug(mock_plug) + test.wait_for_events() + local FIND_CHILD_KEY = "__find_child_fn" + assert(type(mock_plug:get_field(FIND_CHILD_KEY)) == "function", "Child find function should be stored in doConfigure") + end, + { + test_init = function() + test.mock_device.add_test_device(mock_plug) + end, + min_api_version = 17 + } +) + + +local overriden_plug_child_ep_id = 30 + +-- This device overrides both its parent and child profiles to become the Switch category +local mock_plug_profile_override = test.mock_device.build_test_matter_device({ + label = "Matter Plug", + profile = t_utils.get_profile_definition("switch-binary.yml"), + manufacturer_info = { vendor_id = 0x1321, product_id = 0x000C }, -- this Sonoff device has an overloaded profile only for its children + endpoints = { + root_endpoint, + { + endpoint_id = parent_ep_id, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = fields.DEVICE_TYPE_ID.ON_OFF_PLUG_IN_UNIT, device_type_revision = 2} + } + }, + { + endpoint_id = overriden_plug_child_ep_id, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = fields.DEVICE_TYPE_ID.ON_OFF_PLUG_IN_UNIT, device_type_revision = 2} + } + }, + } +}) + + +local function handle_do_configure_event_for_plug_with_profile_override(mock_device) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + mock_device:expect_metadata_update({ profile = "switch-binary" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + + mock_device:expect_device_create({ + type = "EDGE_CHILD", + label = "Matter Plug 2", + profile = "switch-binary", -- overriden profile for Sonoff device + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%d", overriden_plug_child_ep_id) + }) +end + +test.register_coroutine_test( + "Plug with Overriden Profile: Handle doConfigure lifecycle event", + function() + mock_plug_profile_override:set_field(fields.profiling_data.BATTERY_SUPPORT, false, { persist = true }) + mock_plug_profile_override:set_field(fields.profiling_data.POWER_TOPOLOGY, false, { persist = true }) + handle_do_configure_event_for_plug_with_profile_override(mock_plug_profile_override) + end, + { + test_init = function() + test.mock_device.add_test_device(mock_plug_profile_override) + end, + min_api_version = 17 + } +) + +local mock_switch = test.mock_device.build_test_matter_device({ + label = "Matter Switch", + profile = t_utils.get_profile_definition("matter-thing.yml"), + manufacturer_info = generic_manufacturer_info, + matter_version = generic_matter_version, + endpoints = { + root_endpoint, + { + 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 = "SERVER", feature_map = 2} + }, + device_types = { + {device_type_id = fields.DEVICE_TYPE_ID.SWITCH.DIMMER, device_type_revision = 1} + } + }, + { + endpoint_id = 10, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, + }, + device_types = { + {device_type_id = fields.DEVICE_TYPE_ID.SWITCH.ON_OFF_LIGHT, device_type_revision = 1} + } + }, + { + endpoint_id = 20, -- this endpoint should not generate a child device since it only has a client OnOff cluster + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "CLIENT", cluster_revision = 1, feature_map = 0}, + }, + device_types = { + {device_type_id = fields.DEVICE_TYPE_ID.SWITCH.ON_OFF_LIGHT, device_type_revision = 1} + } + }, + { + endpoint_id = 30, -- this endpoint should profile correctly, though it is not a switch + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = fields.DEVICE_TYPE_ID.LIGHT.ON_OFF, device_type_revision = 2} + } + }, + { + endpoint_id = 40, -- this endpoint should generate a switch-binary child device since it has a SERVER OnOff cluster,though the device type is unknown. + 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 = 0x0304, device_type_revision = 2} -- Pump Controller + } + } + } +}) + +test.register_coroutine_test( + "Switch Profile: Handle doConfigure lifecycle event", + function() + mock_switch:set_field(fields.profiling_data.BATTERY_SUPPORT, false, { persist = true }) + mock_switch:set_field(fields.profiling_data.POWER_TOPOLOGY, false, { persist = true }) + test.socket.device_lifecycle:__queue_receive({ mock_switch.id, "doConfigure" }) + test.socket.matter:__expect_send({ mock_switch.id, clusters.LevelControl.attributes.Options:write(mock_switch, 7, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) }) + test.socket.matter:__expect_send({ mock_switch.id, clusters.LevelControl.attributes.Options:write(mock_switch, 40, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) }) + mock_switch:expect_metadata_update({ profile = "switch-level" }) + mock_switch:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + + mock_switch:expect_device_create({ + type = "EDGE_CHILD", + label = "Matter Switch 2", + profile = "switch-binary", + parent_device_id = mock_switch.id, + parent_assigned_child_key = string.format("%d", 10) + }) + + -- client cluster only endpoint (20) should not generate a child device + + mock_switch:expect_device_create({ + type = "EDGE_CHILD", + label = "Matter Switch 3", + profile = "light-binary", + parent_device_id = mock_switch.id, + parent_assigned_child_key = string.format("%d", 30) + }) + + mock_switch:expect_device_create({ + type = "EDGE_CHILD", + label = "Matter Switch 4", + profile = "switch-binary", + parent_device_id = mock_switch.id, + parent_assigned_child_key = string.format("%d", 40) + }) + end, + { + test_init = function() test.mock_device.add_test_device(mock_switch) end, + min_api_version = 17 + } +) + +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 deleted file mode 100644 index 37b5ec8ca5..0000000000 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua +++ /dev/null @@ -1,804 +0,0 @@ --- 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" - -test.disable_startup_messages() - -local mock_device_onoff = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("matter-thing.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 = 1, - clusters = { - {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - }, - device_types = { - {device_type_id = 0x0103, device_type_revision = 1} -- On/Off Light Switch - } - } - } -}) - -local mock_device_onoff_client = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("matter-thing.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 = "CLIENT", cluster_revision = 1, feature_map = 0}, - }, - device_types = { - {device_type_id = 0x0103, device_type_revision = 1} -- On/Off Light Switch - } - } - } -}) - -local mock_device_dimmer = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("matter-thing.yml"), - manufacturer_info = { - vendor_id = 0x0000, - product_id = 0x0000, - }, - endpoints = { - { - endpoint_id = 5, - 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.LevelControl.ID, cluster_type = "SERVER", feature_map = 2} - }, - device_types = { - {device_type_id = 0x0104, device_type_revision = 1} -- Dimmer Switch - } - } - } -}) - -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 = { - 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 = 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.ColorControl.ID, cluster_type = "CLIENT", feature_map = 31}, - - }, - device_types = { - {device_type_id = 0x0105, device_type_revision = 1} -- Color Dimmer Switch - } - } - } -}) - -local mock_device_mounted_on_off_control = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("switch-binary.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 = 7, - 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 = 0x010F, device_type_revision = 1} -- Mounted On/Off Control - } - } - } -}) - -local mock_device_mounted_dimmable_load_control = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("switch-level.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 = 7, - 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 = 0x0110, device_type_revision = 1} -- Mounted Dimmable Load Control - } - } - } -}) - -local mock_device_water_valve = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("matter-thing.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.ValveConfigurationAndControl.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 2}, - }, - device_types = { - {device_type_id = 0x0042, device_type_revision = 1} -- Water Valve - } - } - } -}) - -local mock_device_parent_client_child_server = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("matter-thing.yml"), - manufacturer_info = { - vendor_id = 0x0000, - product_id = 0x0000, - }, - endpoints = { - { - endpoint_id = 0, - clusters = { - {cluster_id = clusters.Basic.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - }, - device_types = { - {device_type_id = 0x0016, device_type_revision = 1} -- RootNode - } - }, - { - endpoint_id = 7, - clusters = { - {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - }, - device_types = { - {device_type_id = 0x0103, device_type_revision = 1} -- OnOff Switch - } - }, - { - endpoint_id = 10, - clusters = { - {cluster_id = clusters.OnOff.ID, cluster_type = "CLIENT", cluster_revision = 1, feature_map = 0}, - }, - device_types = { - {device_type_id = 0x0103, device_type_revision = 1} -- OnOff Switch - } - }, - } -}) - -local mock_device_parent_child_switch_types = test.mock_device.build_test_matter_device({ - label = "Matter Switch", - profile = t_utils.get_profile_definition("matter-thing.yml"), - manufacturer_info = { - vendor_id = 0x0000, - product_id = 0x0000, - }, - endpoints = { - { - endpoint_id = 0, - clusters = { - {cluster_id = clusters.Basic.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - }, - device_types = { - {device_type_id = 0x0016, device_type_revision = 1} -- RootNode - } - }, - { - 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 = "SERVER", feature_map = 2} - }, - device_types = { - {device_type_id = 0x0104, device_type_revision = 1} -- Dimmer Switch - } - }, - { - endpoint_id = 10, - clusters = { - {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - }, - device_types = { - {device_type_id = 0x0103, device_type_revision = 1} -- OnOff Switch - } - }, - } -}) - -local mock_device_parent_child_different_types = test.mock_device.build_test_matter_device({ - label = "Matter Switch", - profile = t_utils.get_profile_definition("switch-binary.yml"), - manufacturer_info = { - vendor_id = 0x0000, - product_id = 0x0000, - }, - endpoints = { - { - endpoint_id = 0, - clusters = { - {cluster_id = clusters.Basic.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - }, - device_types = { - {device_type_id = 0x0016, device_type_revision = 1} -- RootNode - } - }, - { - endpoint_id = 7, - clusters = { - {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - }, - device_types = { - {device_type_id = 0x0103, device_type_revision = 1} -- OnOff Switch - } - }, - { - endpoint_id = 10, - 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 - } - } - } -}) - -local mock_device_parent_child_unsupported_device_type = test.mock_device.build_test_matter_device({ - label = "Matter Switch", - profile = t_utils.get_profile_definition("matter-thing.yml"), - manufacturer_info = { - vendor_id = 0x0000, - product_id = 0x0000, - }, - endpoints = { - { - endpoint_id = 0, - clusters = { - {cluster_id = clusters.Basic.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - }, - device_types = { - {device_type_id = 0x0016, device_type_revision = 1} -- RootNode - } - }, - { - endpoint_id = 7, - clusters = { - {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - }, - device_types = { - {device_type_id = 0x0103, device_type_revision = 1} -- OnOff Switch - } - }, - { - endpoint_id = 10, - 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 = 0x0304, device_type_revision = 2} -- Pump Controller - } - } - } -}) - -local mock_device_light_level_motion = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("light-level-motion.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_id = clusters.LevelControl.ID, cluster_type = "SERVER"} - }, - device_types = { - {device_type_id = 0x0101, device_type_revision = 1} -- Dimmable Light - } - }, - { - endpoint_id = 2, - clusters = { - {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, - }, - device_types = { - {device_type_id = 0x0107, device_type_revision = 1} -- Occupancy Sensor - } - } - } -}) - -local function test_init_parent_child_switch_types() - test.mock_device.add_test_device(mock_device_parent_child_switch_types) - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_switch_types.id, "added" }) - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_switch_types.id, "init" }) - 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" }) - - mock_device_parent_child_switch_types:expect_device_create({ - type = "EDGE_CHILD", - label = "Matter Switch 2", - profile = "switch-binary", - parent_device_id = mock_device_parent_child_switch_types.id, - parent_assigned_child_key = string.format("%d", 10) - }) -end - -local function test_init_onoff() - test.mock_device.add_test_device(mock_device_onoff) - test.socket.device_lifecycle:__queue_receive({ mock_device_onoff.id, "added" }) - test.socket.device_lifecycle:__queue_receive({ mock_device_onoff.id, "init" }) - test.socket.device_lifecycle:__queue_receive({ mock_device_onoff.id, "doConfigure" }) - mock_device_onoff:expect_metadata_update({ profile = "switch-binary" }) - mock_device_onoff:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -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) - - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_client_child_server.id, "added" }) - - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_client_child_server.id, "init" }) - - 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" }) - mock_device_parent_client_child_server:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -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 - -local function test_init_color_dimmer() - test.mock_device.add_test_device(mock_device_color_dimmer) - test.socket.device_lifecycle:__queue_receive({ mock_device_color_dimmer.id, "added" }) - test.socket.device_lifecycle:__queue_receive({ mock_device_color_dimmer.id, "init" }) - test.socket.device_lifecycle:__queue_receive({ mock_device_color_dimmer.id, "doConfigure" }) - mock_device_color_dimmer:expect_metadata_update({ profile = "switch-color-level" }) - 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 = { - clusters.OnOff.attributes.OnOff, - } - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_mounted_on_off_control) - for i, cluster in ipairs(cluster_subscribe_list) do - if i > 1 then - subscribe_request:merge(cluster:subscribe(mock_device_mounted_on_off_control)) - end - end - test.socket.device_lifecycle:__queue_receive({ mock_device_mounted_on_off_control.id, "added" }) - 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, "init" }) - 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 - -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 - if i > 1 then - subscribe_request:merge(cluster:subscribe(mock_device_mounted_dimmable_load_control)) - end - end - test.socket.device_lifecycle:__queue_receive({ mock_device_mounted_dimmable_load_control.id, "added" }) - 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, "init" }) - 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 - -local function test_init_water_valve() - test.mock_device.add_test_device(mock_device_water_valve) - test.socket.device_lifecycle:__queue_receive({ mock_device_water_valve.id, "added" }) - test.socket.device_lifecycle:__queue_receive({ mock_device_water_valve.id, "init" }) - test.socket.device_lifecycle:__queue_receive({ mock_device_water_valve.id, "doConfigure" }) - mock_device_water_valve:expect_metadata_update({ profile = "water-valve-level" }) - mock_device_water_valve:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -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 - } - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_parent_child_different_types) - 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}) - - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_different_types.id, "init" }) - 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({ - type = "EDGE_CHILD", - label = "Matter Switch 2", - profile = "light-color-level", - parent_device_id = mock_device_parent_child_different_types.id, - parent_assigned_child_key = string.format("%d", 10) - }) -end - -local function test_init_parent_child_unsupported_device_type() - test.mock_device.add_test_device(mock_device_parent_child_unsupported_device_type) - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_unsupported_device_type.id, "added" }) - 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({ - type = "EDGE_CHILD", - label = "Matter Switch 2", - profile = "switch-binary", - parent_device_id = mock_device_parent_child_unsupported_device_type.id, - parent_assigned_child_key = string.format("%d", 10) - }) -end - -local function test_init_light_level_motion() - test.mock_device.add_test_device(mock_device_light_level_motion) - local cluster_subscribe_list = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel, - clusters.OccupancySensing.attributes.Occupancy - } - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_light_level_motion) - for i, cluster in ipairs(cluster_subscribe_list) do - if i > 1 then - subscribe_request:merge(cluster:subscribe(mock_device_light_level_motion)) - end - end - - test.socket.device_lifecycle:__queue_receive({ mock_device_light_level_motion.id, "added" }) - 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, "init" }) - 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 - -test.register_coroutine_test( - "Test profile change on init for onoff parent cluster as server", - function() - end, - { - test_init = test_init_onoff, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test profile change on init for dimmer parent cluster as server", - function() - end, - { - test_init = test_init_dimmer, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test profile change on init for color dimmer parent cluster as server", - function() - end, - { - test_init = test_init_color_dimmer, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test init for onoff parent cluster as client", - function() - end, - { - test_init = test_init_onoff_client, - min_api_version = 17 - } -) - -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, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test init for mounted onoff control parent cluster as server", - function() - end, - { - test_init = test_init_mounted_on_off_control, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test init for mounted dimmable load control parent cluster as server", - function() - end, - { - test_init = test_init_mounted_dimmable_load_control, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test profile change on init for water valve parent cluster as server", - function() - end, - { - test_init = test_init_water_valve, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test profile change on init for onoff parent cluster as client and onoff child as server", - function() - end, - { - test_init = test_init_parent_client_child_server, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test profile change on init for onoff device when parent and child are both server", - function() - end, - { - test_init = test_init_parent_child_switch_types, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test child device attribute subscriptions when parent device has clusters that are not a superset of child device clusters", - function() - end, - { - test_init = test_init_parent_child_different_types, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test child device attributes not subscribed to for unsupported device type for child device", - function() - end, - { - test_init = test_init_parent_child_unsupported_device_type, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test init for light with motion sensor", - function() - end, - { - test_init = test_init_light_level_motion, - min_api_version = 17 - } -) - -test.run_registered_tests() - 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 338acd58c7..ced13d6eb8 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 @@ -66,6 +66,19 @@ local function test_init() end test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Test profile change on init for water valve parent cluster as server", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + mock_device:expect_metadata_update({ profile = "water-valve-level" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17 + } +) + test.register_message_test( "Open command should send the appropriate commands", { 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 deleted file mode 100644 index 8102c61865..0000000000 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua +++ /dev/null @@ -1,740 +0,0 @@ --- Copyright © 2024 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" - -test.disable_startup_messages() - -local TRANSITION_TIME = 0 -local OPTIONS_MASK = 0x01 -local HANDLE_COMMAND_IF_OFF = 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("light-level-colorTemperature.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 = parent_ep, - clusters = { - {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, - }, - device_types = { - {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 = 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 = 0x010D, device_type_revision = 2} -- Extended Color Light - } - }, - } -}) - -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("light-level-colorTemperature.yml"), - manufacturer_info = { - vendor_id = 0x1321, - product_id = 0x000C, - }, - 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 = parent_ep, - clusters = { - {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, - }, - device_types = { - {device_type_id = 0x0100, device_type_revision = 2} -- On/Off Light - } - }, - { - 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 = 0x0100, device_type_revision = 2}, -- On/Off Light - {device_type_id = 0x0101, device_type_revision = 2} -- Dimmable Light - } - }, - { - 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"}, - }, - device_types = { - {device_type_id = 0x010A, device_type_revision = 2} -- On/Off Plug - } - }, - } -}) - -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_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) - } - mock_children[endpoint.endpoint_id] = test.mock_device.build_test_child_device(child_data) - end -end - -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}) - - 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" }) - 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 - test.mock_device.add_test_device(child) - end - - mock_device:expect_device_create({ - type = "EDGE_CHILD", - label = "Matter Switch 2", - profile = "light-level", - parent_device_id = mock_device.id, - parent_assigned_child_key = string.format("%d", child1_ep) - }) - - mock_device:expect_device_create({ - type = "EDGE_CHILD", - label = "Matter Switch 3", - profile = "light-color-level", - parent_device_id = mock_device.id, - parent_assigned_child_key = string.format("%d", child2_ep) - }) -end - -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_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_non_sequential[endpoint.endpoint_id] = test.mock_device.build_test_child_device(child_data) - end -end - -local function test_init_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, - 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(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.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({ unsup_mock_device.id, "init" }) - test.socket.matter:__expect_send({unsup_mock_device.id, subscribe_request}) - - 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 - - unsup_mock_device:expect_device_create({ - type = "EDGE_CHILD", - label = "Matter Switch 2", - profile = "light-color-level", - 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 - unsup_mock_device:expect_device_create({ - type = "EDGE_CHILD", - label = "Matter Switch 3", - profile = "switch-binary", - parent_device_id = unsup_mock_device.id, - parent_assigned_child_key = string.format("%d", child3_ep_non_sequential) - }) - - unsup_mock_device:expect_device_create({ - type = "EDGE_CHILD", - label = "Matter Switch 4", - profile = "light-level", - parent_device_id = unsup_mock_device.id, - parent_assigned_child_key = string.format("%d", child1_ep_non_sequential) - }) -end - -test.set_test_init_function(test_init) - -test.register_message_test( - "Parent device: switch capability should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "switch", component = "main", command = "on", args = { } } - } - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "on" } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.On(mock_device, parent_ep) - }, - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, parent_ep, 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" } - } - }, - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "First child device: switch capability switch should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_children[child1_ep].id, - { capability = "switch", component = "main", command = "on", args = { } } - } - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_children[child1_ep].id, capability_id = "switch", capability_cmd_id = "on" } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.On(mock_device, child1_ep) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, child1_ep, true) - } - }, - { - channel = "capability", - direction = "send", - message = mock_children[child1_ep]: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" } - } - }, - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Second child device: switch capability should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_children[child2_ep].id, - { capability = "switch", component = "main", command = "on", args = { } } - } - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_children[child2_ep].id, capability_id = "switch", capability_cmd_id = "on" } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.On(mock_device, child2_ep) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, child2_ep, true) - } - }, - { - channel = "capability", - direction = "send", - message = mock_children[child2_ep]: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" } - } - }, - }, - { - min_api_version = 17 - } -) - -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" } - } - }, - }, - { - min_api_version = 17 - } -) - -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, HANDLE_COMMAND_IF_OFF) - } - }, - { - 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)) - }, - }, - { - min_api_version = 17 - } -) - -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)) - } - }, - { - min_api_version = 17 - } -) - -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, HANDLE_COMMAND_IF_OFF) - } - }, - { - 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)) - } - }, - { - min_api_version = 17 - } -) - -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})) - } - }, - { - min_api_version = 17 - } -) - -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})) - } - }, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test child devices are created in order of their endpoints", - function() - end, - { - test_init = test_init_parent_child_endpoints_non_sequential, - min_api_version = 17 - } -) - -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, - { - min_api_version = 17 - } -) - -test.run_registered_tests() - 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 deleted file mode 100644 index 9beed1805e..0000000000 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua +++ /dev/null @@ -1,720 +0,0 @@ --- Copyright © 2023 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" - -test.disable_startup_messages() - -local TRANSITION_TIME = 0 -local OPTIONS_MASK = 0x01 -local HANDLE_COMMAND_IF_OFF = 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("light-level-colorTemperature.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 = parent_ep, - clusters = { - {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, - }, - device_types = { - {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 = 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 = 0x010D, device_type_revision = 2} -- Extended Color Light - } - }, - } -}) - -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("light-level-colorTemperature.yml"), - manufacturer_info = { - vendor_id = 0x1321, - product_id = 0x000C, - }, - 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 = parent_ep, - clusters = { - {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, - }, - device_types = { - {device_type_id = 0x0100, device_type_revision = 2} -- On/Off Light - } - }, - { - 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 = 0x0100, device_type_revision = 2}, -- On/Off Light - {device_type_id = 0x0101, device_type_revision = 2} -- Dimmable Light - } - }, - { - 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"}, - }, - device_types = { - {device_type_id = 0x010A, device_type_revision = 2} -- On/Off Plug - } - }, - } -}) - -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_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) - } - mock_children[endpoint.endpoint_id] = test.mock_device.build_test_child_device(child_data) - end -end - -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}) - - 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" }) - 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 - test.mock_device.add_test_device(child) - end - - mock_device:expect_device_create({ - type = "EDGE_CHILD", - label = "Matter Switch 2", - profile = "light-level", - parent_device_id = mock_device.id, - parent_assigned_child_key = string.format("%d", child1_ep) - }) - - mock_device:expect_device_create({ - type = "EDGE_CHILD", - label = "Matter Switch 3", - profile = "light-color-level", - parent_device_id = mock_device.id, - parent_assigned_child_key = string.format("%d", child2_ep) - }) -end - -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_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_non_sequential[endpoint.endpoint_id] = test.mock_device.build_test_child_device(child_data) - end -end - -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_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_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_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_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_non_sequential) do - test.mock_device.add_test_device(child) - end - - mock_device_parent_child_endpoints_non_sequential: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_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({ - type = "EDGE_CHILD", - label = "Matter Switch 3", - profile = "switch-binary", - 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 - -test.set_test_init_function(test_init) - -test.register_message_test( - "Parent device: switch capability should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "switch", component = "main", command = "on", args = { } } - } - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "on" } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.On(mock_device, parent_ep) - }, - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, parent_ep, 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" } - } - }, - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "First child device: switch capability switch should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_children[child1_ep].id, - { capability = "switch", component = "main", command = "on", args = { } } - } - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_children[child1_ep].id, capability_id = "switch", capability_cmd_id = "on" } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.On(mock_device, child1_ep) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, child1_ep, true) - } - }, - { - channel = "capability", - direction = "send", - message = mock_children[child1_ep]: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" } - } - }, - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Second child device: switch capability should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_children[child2_ep].id, - { capability = "switch", component = "main", command = "on", args = { } } - } - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_children[child2_ep].id, capability_id = "switch", capability_cmd_id = "on" } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.On(mock_device, child2_ep) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, child2_ep, true) - } - }, - { - channel = "capability", - direction = "send", - message = mock_children[child2_ep]: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" } - } - }, - }, - { - min_api_version = 17 - } -) - -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" } - } - }, - }, - { - min_api_version = 17 - } -) - -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, HANDLE_COMMAND_IF_OFF) - } - }, - { - 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)) - }, - }, - { - min_api_version = 17 - } -) - -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)) - } - }, - { - min_api_version = 17 - } -) - -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, HANDLE_COMMAND_IF_OFF) - } - }, - { - 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)) - } - }, - { - min_api_version = 17 - } -) - -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})) - } - }, - { - min_api_version = 17 - } -) - -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})) - } - }, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Test child devices are created in order of their endpoints", - function() - end, - { - test_init = test_init_parent_child_endpoints_non_sequential, - min_api_version = 17 - } -) - -test.run_registered_tests() - diff --git a/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua b/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua index c697a20c7d..82be544f4a 100644 --- a/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua @@ -26,8 +26,10 @@ local function step_color_temperature_by_percent_handler(driver, device, cmd) local transition_time = device:get_field(switch_utils.COLOR_TEMP_STEP_TRANSITION_TIME) or DEFAULT_STEP_TRANSITION_TIME -- Reminder, stepSize > 0 == Kelvin UP == Mireds DOWN. stepSize < 0 == Kelvin DOWN == Mireds UP local step_mode = (step_percent_change > 0) and clusters.ColorControl.types.CcStepMode.DOWN or clusters.ColorControl.types.CcStepMode.UP - local min_mireds = device:get_field(switch_utils.MIRED_MIN_BOUND) - local max_mireds = device:get_field(switch_utils.MIRED_MAX_BOUND) + -- note: the field containing the color temp bounds will be associated with a parent device + local field_device = device:get_parent_device() or device + local min_mireds = field_device:get_field(switch_utils.MIRED_MIN_BOUND) + local max_mireds = field_device:get_field(switch_utils.MIRED_MAX_BOUND) -- since colorTemperatureRange is only set after both custom bounds are, use defaults if any custom bound is missing if not (min_mireds and max_mireds) then min_mireds = DEFAULT_MIRED_MIN_BOUND From a225a2c24facb1c10846848b660b17f13fba85f1 Mon Sep 17 00:00:00 2001 From: laity-w-sudo <1090741189@qq.com> Date: Fri, 10 Apr 2026 04:00:07 +0800 Subject: [PATCH 294/538] Add Sonoff SNZB-04PR2 (WWSTCERT-10731) and SNZB-04P (WWSTCERT-10704) Smart Scene Contact into zigbee-contact (#2539) * Add Sonoff SNZB-04PR2 Smart Scene Contact into zigbee-contact * Add Sonoff profile into zigbee-contact * Delete the program, that is only submit the relevant configuration. * Modify the profile configuration of the fingerprint file * The anti-tampering function has been changed to a standard attribute --- .../SmartThings/zigbee-contact/fingerprints.yml | 10 ++++++++++ .../profiles/contact-battery-tamper.yml | 16 ++++++++++++++++ .../zigbee-contact/src/configurations.lua | 1 - 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml diff --git a/drivers/SmartThings/zigbee-contact/fingerprints.yml b/drivers/SmartThings/zigbee-contact/fingerprints.yml index 3dba88a8e9..b3eaa9ca95 100644 --- a/drivers/SmartThings/zigbee-contact/fingerprints.yml +++ b/drivers/SmartThings/zigbee-contact/fingerprints.yml @@ -204,6 +204,16 @@ zigbeeManufacturer: manufacturer: Third Reality, Inc model: 3RVS01031Z deviceProfileName: thirdreality-multi-sensor + - id: "SONOFF/SNZB-04P" + deviceLabel: SONOFF Contact Sensor + manufacturer: eWeLink + model: SNZB-04P + deviceProfileName: contact-battery-profile + - id: "SONOFF/SNZB-04PR2" + deviceLabel: SONOFF Contact Sensor + manufacturer: SONOFF + model: SNZB-04PR2 + deviceProfileName: contact-battery-profile - id: "Aug. Winkhaus SE/FM.V.ZB" deviceLabel: Funkkontakt FM.V.ZB manufacturer: Aug. Winkhaus SE diff --git a/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml new file mode 100644 index 0000000000..524783af37 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml @@ -0,0 +1,16 @@ +name: contact-battery-tamper +components: +- id: main + capabilities: + - id: contactSensor + version: 1 + - id: battery + version: 1 + - id: tamperAlert + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: ContactSensor diff --git a/drivers/SmartThings/zigbee-contact/src/configurations.lua b/drivers/SmartThings/zigbee-contact/src/configurations.lua index cd5ede1b6e..668e8c5ac6 100644 --- a/drivers/SmartThings/zigbee-contact/src/configurations.lua +++ b/drivers/SmartThings/zigbee-contact/src/configurations.lua @@ -26,7 +26,6 @@ local devices = { EWELINK_HEIMAN = { FINGERPRINTS = { { mfr = "eWeLink", model = "DS01" }, - { mfr = "eWeLink", model = "SNZB-04P" }, { mfr = "HEIMAN", model = "DoorSensor-N" } }, CONFIGURATION = { From c29ee38f0b21baafc3651b158c8e4c88ba6d26fb Mon Sep 17 00:00:00 2001 From: Konrad K <33450498+KKlimczukS@users.noreply.github.com> Date: Mon, 27 Apr 2026 18:38:01 +0200 Subject: [PATCH 295/538] updates in SNZB-04PR2 and SNZB-04P fingerprints (WWSTCERT-10731, WWSTCERT-10704) (#2909) --- .../SmartThings/zigbee-contact/fingerprints.yml | 4 ++-- .../profiles/contact-battery-tamper.yml | 16 ---------------- 2 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml diff --git a/drivers/SmartThings/zigbee-contact/fingerprints.yml b/drivers/SmartThings/zigbee-contact/fingerprints.yml index b3eaa9ca95..56e1599c96 100644 --- a/drivers/SmartThings/zigbee-contact/fingerprints.yml +++ b/drivers/SmartThings/zigbee-contact/fingerprints.yml @@ -208,12 +208,12 @@ zigbeeManufacturer: deviceLabel: SONOFF Contact Sensor manufacturer: eWeLink model: SNZB-04P - deviceProfileName: contact-battery-profile + deviceProfileName: contact-battery-profile - id: "SONOFF/SNZB-04PR2" deviceLabel: SONOFF Contact Sensor manufacturer: SONOFF model: SNZB-04PR2 - deviceProfileName: contact-battery-profile + deviceProfileName: contact-battery-profile - id: "Aug. Winkhaus SE/FM.V.ZB" deviceLabel: Funkkontakt FM.V.ZB manufacturer: Aug. Winkhaus SE diff --git a/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml deleted file mode 100644 index 524783af37..0000000000 --- a/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: contact-battery-tamper -components: -- id: main - capabilities: - - id: contactSensor - version: 1 - - id: battery - version: 1 - - id: tamperAlert - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: ContactSensor From 427ccd82b1fa1fc4c0c429b404124f5a457bf042 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 21 Apr 2026 12:53:10 -0500 Subject: [PATCH 296/538] WWSTCERT-11096 Sombra Automated Shades and Blinds (#2912) --- drivers/SmartThings/zigbee-window-treatment/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml b/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml index c59d96f93f..b2e918746b 100644 --- a/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml +++ b/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml @@ -143,6 +143,11 @@ zigbeeManufacturer: manufacturer: Sombra Shades model: WM25/L-Z deviceProfileName: window-treatment-battery + - id: "Sombra Shades/SS25/L-Z" + deviceLabel: Sombra Automated Shades and Blinds + manufacturer: Sombra Shades + model: SS25/L-Z + deviceProfileName: window-treatment-battery zigbeeGeneric: - id: "genericShade" From 01edd5f50b8b5dcb00bbe1fb4526649dd8f70f10 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Mon, 27 Apr 2026 11:52:07 -0500 Subject: [PATCH 297/538] WWSTCERT-11195 Govee Smart Bulb PAR38 (#2925) --- .../matter-switch/fingerprints.yml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 623dad6806..06070f0484 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -783,6 +783,36 @@ matterManufacturer: vendorId: 0x1387 productId: 0x6056 deviceProfileName: light-color-level + - id: "4999/5281" + deviceLabel: Govee Smart Bulb PAR38 + vendorId: 0x1387 + productId: 0x14A1 + deviceProfileName: light-color-level + - id: "4999/5121" + deviceLabel: Govee Smart Bulb A21 1600lm + vendorId: 0x1387 + productId: 0x1401 + deviceProfileName: light-color-level + - id: "4999/5313" + deviceLabel: Govee Edison Bulb + vendorId: 0x1387 + productId: 0x14C1 + deviceProfileName: light-color-level + - id: "4999/5312" + deviceLabel: Govee Edison Bulb + vendorId: 0x1387 + productId: 0x14C0 + deviceProfileName: light-color-level + - id: "4999/5680" + deviceLabel: Govee Lantern Floor Lamp + vendorId: 0x1387 + productId: 0x1630 + deviceProfileName: light-color-level + - id: "4999/5953" + deviceLabel: Govee Table Lamp Classic + vendorId: 0x1387 + productId: 0x1741 + deviceProfileName: light-color-level # Hager - id: "4741/8" deviceLabel: Hager matter 2 buttons (battery) From b889ef99ddc80dbc23a3d4074d80009c05c7ce97 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 29 Apr 2026 14:24:29 -0500 Subject: [PATCH 298/538] Matter Switch: Fix transition time in new parent/child tests (#2933) --- .../src/test/test_matter_on_off_parent_child.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua index 624edc6e49..1531632d24 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua @@ -345,7 +345,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColor(mock_device, extended_color_ep_id, 15182, 21547, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) + clusters.ColorControl.server.commands.MoveToColor(mock_device, extended_color_ep_id, 15182, 21547, fields.ZERO_TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) } }, { @@ -420,7 +420,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, extended_color_ep_id, 555, fields.TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, extended_color_ep_id, 555, fields.ZERO_TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) } }, -- 555 is expected since it is re-bounded by the given range @@ -446,7 +446,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.ColorControl.server.commands.StepColorTemperature(mock_device, extended_color_ep_id, clusters.ColorControl.types.StepModeEnum.DOWN, 80, fields.TRANSITION_TIME_FAST, 153, 555, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) + clusters.ColorControl.server.commands.StepColorTemperature(mock_device, extended_color_ep_id, clusters.ColorControl.types.StepModeEnum.DOWN, 80, fields.DEFAULT_STEP_TRANSITION_TIME, 153, 555, fields.OPTIONS_MASK, fields.IGNORE_COMMAND_IF_OFF) }, }, }, From b42cf0d720153da0afc553188900a708a2b82138 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:30 -0500 Subject: [PATCH 299/538] CHAD-17566: zigbee-water-leak-sensor enable shared_device_thread --- drivers/SmartThings/zigbee-water-leak-sensor/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/init.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/init.lua index 4ded4e195c..6f4b879108 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/init.lua @@ -81,6 +81,7 @@ local zigbee_water_driver_template = { ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, sub_drivers = require("sub_drivers"), health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_water_driver_template, From 183b17634db2cf3999543c25b7de7d88fbd50a41 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 300/538] CHAD-17566: zigbee-humidity-sensor enable shared_device_thread --- drivers/SmartThings/zigbee-humidity-sensor/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/init.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/init.lua index 9ca7cd734d..6bde23ed25 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/init.lua @@ -70,6 +70,7 @@ local zigbee_humidity_driver = { }, sub_drivers = require("sub_drivers"), health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_humidity_driver, zigbee_humidity_driver.supported_capabilities, {native_capability_attrs_enabled = true}) From 4f16bc5bb6959b736ae49cbc68c76870522f6358 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 301/538] CHAD-17566: zwave-thermostat enable shared_device_thread --- drivers/SmartThings/zwave-thermostat/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zwave-thermostat/src/init.lua b/drivers/SmartThings/zwave-thermostat/src/init.lua index 3668085b1a..6a79c1f4e1 100755 --- a/drivers/SmartThings/zwave-thermostat/src/init.lua +++ b/drivers/SmartThings/zwave-thermostat/src/init.lua @@ -106,6 +106,7 @@ local driver_template = { added = device_added }, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities, {native_capability_attrs_enabled = true}) From d2ef6ac7a11c3d8cc3ff4402993b13639cdb33a0 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 302/538] CHAD-17566: zigbee-button enable shared_device_thread --- drivers/SmartThings/zigbee-button/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-button/src/init.lua b/drivers/SmartThings/zigbee-button/src/init.lua index 8ed0db27db..cb1565ea74 100644 --- a/drivers/SmartThings/zigbee-button/src/init.lua +++ b/drivers/SmartThings/zigbee-button/src/init.lua @@ -136,6 +136,7 @@ local zigbee_button_driver_template = { }, ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_button_driver_template, zigbee_button_driver_template.supported_capabilities) From 8b4d80d1f2d2475cb666954801c0b7f23adce13a Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 303/538] CHAD-17566: zigbee-thermostat enable shared_device_thread --- drivers/SmartThings/zigbee-thermostat/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-thermostat/src/init.lua b/drivers/SmartThings/zigbee-thermostat/src/init.lua index b1766e7892..a72b3c9107 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/init.lua @@ -354,6 +354,7 @@ local zigbee_thermostat_driver = { }, sub_drivers = require("sub_drivers"), health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_thermostat_driver, zigbee_thermostat_driver.supported_capabilities) From fbde0acd0bccf6e894858fcc64a323804d816e63 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 304/538] CHAD-17566: matter-energy enable shared_device_thread --- drivers/SmartThings/matter-energy/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/matter-energy/src/init.lua b/drivers/SmartThings/matter-energy/src/init.lua index 51d361753a..c6e44008d6 100644 --- a/drivers/SmartThings/matter-energy/src/init.lua +++ b/drivers/SmartThings/matter-energy/src/init.lua @@ -750,6 +750,7 @@ matter_driver_template = { capabilities.battery, capabilities.chargingState }, + shared_device_thread_enabled = true, } local matter_driver = MatterDriver("matter-energy", matter_driver_template) From 7a0fd009129bfa58aa682ac38a608f14df68f910 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 305/538] CHAD-17566: zwave-electric-meter enable shared_device_thread --- drivers/SmartThings/zwave-electric-meter/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zwave-electric-meter/src/init.lua b/drivers/SmartThings/zwave-electric-meter/src/init.lua index 851de3096f..53c91cfa90 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/init.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/init.lua @@ -43,6 +43,7 @@ local driver_template = { added = device_added }, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) From ed4028d92090e3cb0468c32cbbc1bdbcdfbad2e5 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 306/538] CHAD-17566: zwave-switch enable shared_device_thread --- drivers/SmartThings/zwave-switch/src/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zwave-switch/src/init.lua b/drivers/SmartThings/zwave-switch/src/init.lua index 9f40fd84e4..7d622e586f 100644 --- a/drivers/SmartThings/zwave-switch/src/init.lua +++ b/drivers/SmartThings/zwave-switch/src/init.lua @@ -125,7 +125,8 @@ local driver_template = { infoChanged = info_changed, doConfigure = do_configure, added = device_added - } + }, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, From d61e48afea4f543492ab5e60ed6b0bc173a3215b Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 307/538] CHAD-17566: zigbee-watering-kit enable shared_device_thread --- drivers/SmartThings/zigbee-watering-kit/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-watering-kit/src/init.lua b/drivers/SmartThings/zigbee-watering-kit/src/init.lua index 7dd35e6f09..7e23c2fef9 100644 --- a/drivers/SmartThings/zigbee-watering-kit/src/init.lua +++ b/drivers/SmartThings/zigbee-watering-kit/src/init.lua @@ -15,6 +15,7 @@ local zigbee_water_driver_template = { }, sub_drivers = require("sub_drivers"), health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_water_driver_template, zigbee_water_driver_template.supported_capabilities) From ae23ad323275d95b283f360da27695e7ba9fa2aa Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 308/538] CHAD-17566: zigbee-lock enable shared_device_thread --- drivers/SmartThings/zigbee-lock/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-lock/src/init.lua b/drivers/SmartThings/zigbee-lock/src/init.lua index 94f5adc0c4..1ac2598e2f 100644 --- a/drivers/SmartThings/zigbee-lock/src/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/init.lua @@ -442,6 +442,7 @@ local zigbee_lock_driver = { init = init, }, health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_lock_driver, zigbee_lock_driver.supported_capabilities) From 1425d3625198de18090aa5b4e1632c27c4358bca Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 309/538] CHAD-17566: zwave-valve enable shared_device_thread --- drivers/SmartThings/zwave-valve/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zwave-valve/src/init.lua b/drivers/SmartThings/zwave-valve/src/init.lua index cea5b877c1..f430c19a4a 100644 --- a/drivers/SmartThings/zwave-valve/src/init.lua +++ b/drivers/SmartThings/zwave-valve/src/init.lua @@ -17,6 +17,7 @@ local driver_template = { capabilities.valve, }, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) From 8a10af8e2172a5463b627296e7e118c1f3936006 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 310/538] CHAD-17566: zwave-smoke-alarm enable shared_device_thread --- drivers/SmartThings/zwave-smoke-alarm/src/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/init.lua b/drivers/SmartThings/zwave-smoke-alarm/src/init.lua index 7968983f63..1ee506f453 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/init.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/init.lua @@ -79,7 +79,8 @@ local driver_template = { infoChanged = info_changed, doConfigure = do_configure, added = device_added - } + }, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) From 8b21db6acab0f5428a7d7ca62c67c45a478fbd4f Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 311/538] CHAD-17566: zigbee-range-extender enable shared_device_thread --- drivers/SmartThings/zigbee-range-extender/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-range-extender/src/init.lua b/drivers/SmartThings/zigbee-range-extender/src/init.lua index 565aa74a28..f6ba3c1ffc 100644 --- a/drivers/SmartThings/zigbee-range-extender/src/init.lua +++ b/drivers/SmartThings/zigbee-range-extender/src/init.lua @@ -23,6 +23,7 @@ local zigbee_range_driver_template = { }, health_check = false, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_range_driver_template, zigbee_range_driver_template.supported_capabilities) From c7e5776c63cdc14aa22100a812b9259689e092eb Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 312/538] CHAD-17566: zigbee-air-quality-detector enable shared_device_thread --- drivers/SmartThings/zigbee-air-quality-detector/src/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-air-quality-detector/src/init.lua b/drivers/SmartThings/zigbee-air-quality-detector/src/init.lua index 993ba2a96d..49e2f79a88 100755 --- a/drivers/SmartThings/zigbee-air-quality-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-air-quality-detector/src/init.lua @@ -33,7 +33,8 @@ local zigbee_air_quality_detector_template = { capabilities.tvocMeasurement, capabilities.tvocHealthConcern }, - sub_drivers = { require("MultiIR") } + sub_drivers = { require("MultiIR") }, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_air_quality_detector_template, zigbee_air_quality_detector_template.supported_capabilities) From ab4503eeac1c484ba78c85ff0d3bcb69e9cdcb85 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 313/538] CHAD-17566: zigbee-illuminance-sensor enable shared_device_thread --- drivers/SmartThings/zigbee-illuminance-sensor/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-illuminance-sensor/src/init.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/init.lua index 7f84eb68dc..6f9d3c5554 100644 --- a/drivers/SmartThings/zigbee-illuminance-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-illuminance-sensor/src/init.lua @@ -21,6 +21,7 @@ local zigbee_illuminance_driver = { }, sub_drivers = require("sub_drivers"), health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_illuminance_driver, zigbee_illuminance_driver.supported_capabilities) From 14cfb09ca30bafd1974664447c9ab5f6c9c00dbb Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 314/538] CHAD-17566: matter-appliance enable shared_device_thread --- drivers/SmartThings/matter-appliance/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/matter-appliance/src/init.lua b/drivers/SmartThings/matter-appliance/src/init.lua index 044da51e7c..8f91ebcd0c 100644 --- a/drivers/SmartThings/matter-appliance/src/init.lua +++ b/drivers/SmartThings/matter-appliance/src/init.lua @@ -297,6 +297,7 @@ local matter_driver_template = { capabilities.windMode }, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } local matter_driver = MatterDriver("matter-appliance", matter_driver_template) From d21c079f9bb5e01d577fe981462895f0d7df3d23 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 315/538] CHAD-17566: matter-pump enable shared_device_thread --- drivers/SmartThings/matter-pump/src/init.lua | 657 ++++++++++--------- 1 file changed, 329 insertions(+), 328 deletions(-) diff --git a/drivers/SmartThings/matter-pump/src/init.lua b/drivers/SmartThings/matter-pump/src/init.lua index db43d3b1df..0e79f66cbb 100644 --- a/drivers/SmartThings/matter-pump/src/init.lua +++ b/drivers/SmartThings/matter-pump/src/init.lua @@ -1,328 +1,329 @@ --- 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 log = require "log" -local clusters = require "st.matter.clusters" -local embedded_cluster_utils = require "embedded-cluster-utils" -local MatterDriver = require "st.matter.driver" - -local IS_LOCAL_OVERRIDE = "__is_local_override" --- Per matter spec, the pump level is in steps of 0.5% and the --- max level value is 200. Anything above is considered 100% -local MAX_PUMP_ATTR_LEVEL = 200 -local MAX_CAP_SWITCH_LEVEL = 100 - --- Include driver-side definitions when lua libs api version is < 10 -local version = require "version" -if version.api < 10 then - clusters.PumpConfigurationAndControl = require "PumpConfigurationAndControl" -end - -local pumpOperationMode = capabilities.pumpOperationMode -local pumpControlMode = capabilities.pumpControlMode - -local PUMP_OPERATION_MODE_MAP = { - [clusters.PumpConfigurationAndControl.types.OperationModeEnum.NORMAL] = pumpOperationMode.operationMode.normal, - [clusters.PumpConfigurationAndControl.types.OperationModeEnum.MINIMUM] = pumpOperationMode.operationMode.minimum, - [clusters.PumpConfigurationAndControl.types.OperationModeEnum.MAXIMUM] = pumpOperationMode.operationMode.maximum, - [clusters.PumpConfigurationAndControl.types.OperationModeEnum.LOCAL] = pumpOperationMode.operationMode.localSetting, -} - -local PUMP_CONTROL_MODE_MAP = { - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_SPEED] = pumpControlMode.controlMode.constantSpeed, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_PRESSURE] = pumpControlMode.controlMode.constantPressure, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.PROPORTIONAL_PRESSURE] = pumpControlMode.controlMode.proportionalPressure, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_FLOW] = pumpControlMode.controlMode.constantFlow, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_TEMPERATURE] = pumpControlMode.controlMode.constantTemperature, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.AUTOMATIC] = pumpControlMode.controlMode.automatic, -} - -local PUMP_CURRENT_CONTROL_MODE_MAP = { - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_SPEED] = pumpControlMode.currentControlMode.constantSpeed, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_PRESSURE] = pumpControlMode.currentControlMode.constantPressure, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.PROPORTIONAL_PRESSURE] = pumpControlMode.currentControlMode.proportionalPressure, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_FLOW] = pumpControlMode.currentControlMode.constantFlow, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_TEMPERATURE] = pumpControlMode.currentControlMode.constantTemperature, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.AUTOMATIC] = pumpControlMode.currentControlMode.automatic, -} - -local subscribed_attributes = { - [capabilities.switch.ID] = { - clusters.OnOff.attributes.OnOff, - }, - [capabilities.switchLevel.ID] = { - clusters.LevelControl.attributes.CurrentLevel - }, - [capabilities.pumpOperationMode.ID]={ - clusters.PumpConfigurationAndControl.attributes.OperationMode, - clusters.PumpConfigurationAndControl.attributes.EffectiveOperationMode, - clusters.PumpConfigurationAndControl.attributes.PumpStatus, - }, - [capabilities.pumpControlMode.ID]={ - clusters.PumpConfigurationAndControl.attributes.EffectiveControlMode, - }, -} - -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) - -- Use the find_default_endpoint function to return the first endpoint that - -- supports a given cluster. - return find_default_endpoint(device, clusters.PumpConfigurationAndControl.ID) -end - -local function device_init(driver, device) - device:subscribe() - device:set_component_to_endpoint_fn(component_to_endpoint) -end - -local function info_changed(driver, device, event, args) - --Note this is needed because device:subscribe() does not recalculate - -- the subscribed attributes each time it is run, that only happens at init. - -- This will change in the 0.48.x release of the lua libs. - for cap_id, attributes in pairs(subscribed_attributes) do - if device:supports_capability_by_id(cap_id) then - for _, attr in ipairs(attributes) do - device:add_subscribed_attribute(attr) - end - end - end - device:subscribe() -end - -local function set_supported_op_mode(driver, device) - local spd_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_SPEED}) - local local_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.LOCAL_OPERATION}) - local supported_op_modes = {pumpOperationMode.operationMode.normal.NAME} - if #spd_eps > 0 then - table.insert(supported_op_modes, pumpOperationMode.operationMode.minimum.NAME) - table.insert(supported_op_modes, pumpOperationMode.operationMode.maximum.NAME) - end - if #local_eps > 0 then - table.insert(supported_op_modes, pumpOperationMode.operationMode.localSetting.NAME) - end - device:emit_event(pumpOperationMode.supportedOperationModes(supported_op_modes)) -end - -local function set_supported_control_mode(driver, device) - local spd_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_SPEED}) - local prsconst_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_PRESSURE}) - local prscomp_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.COMPENSATED_PRESSURE}) - local flw_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_FLOW}) - local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_TEMPERATURE}) - local auto_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.AUTOMATIC}) - local supported_control_modes = {} - if #spd_eps > 0 then - table.insert(supported_control_modes, pumpControlMode.controlMode.constantSpeed.NAME) - end - if #prsconst_eps > 0 then - table.insert(supported_control_modes, pumpControlMode.controlMode.constantPressure.NAME) - end - if #prscomp_eps > 0 then - table.insert(supported_control_modes, pumpControlMode.controlMode.proportionalPressure.NAME) - end - if #flw_eps > 0 then - table.insert(supported_control_modes, pumpControlMode.controlMode.constantFlow.NAME) - end - if #temp_eps > 0 then - table.insert(supported_control_modes, pumpControlMode.controlMode.constantTemperature.NAME) - end - if #auto_eps > 0 then - table.insert(supported_control_modes, pumpControlMode.controlMode.automatic.NAME) - end - device:emit_event(pumpControlMode.supportedControlModes(supported_control_modes)) -end - -local function do_configure(driver, device) - local pump_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID) - local level_eps = embedded_cluster_utils.get_endpoints(device, clusters.LevelControl.ID) - local profile_name = "pump" - if #pump_eps == 1 then - if #level_eps > 0 then - profile_name = profile_name .. "-level" - else - profile_name = profile_name .. "-only" - end - device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) - device:try_update_metadata({profile = profile_name}) - else - device.log.warn_with({hub_logs=true}, "Device does not support pump configuration and control cluster") - end - set_supported_op_mode(driver, device) - set_supported_control_mode(driver, device) -end - --- Matter Handlers -- -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 level_attr_handler(driver, device, ib, response) - if ib.data.value ~= nil then - local level = math.floor((ib.data.value / MAX_PUMP_ATTR_LEVEL * MAX_CAP_SWITCH_LEVEL) + 0.5) - level = math.min(level, MAX_CAP_SWITCH_LEVEL) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switchLevel.level(level)) - end -end - -local function effective_operation_mode_handler(driver, device, ib, response) - local modeEnum = clusters.PumpConfigurationAndControl.types.OperationModeEnum - local supported_control_modes = {} - local local_override = device:get_field(IS_LOCAL_OVERRIDE) - if not local_override then - set_supported_op_mode(driver, device) - end - if ib.data.value == modeEnum.NORMAL then - device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.normal()) - set_supported_control_mode(driver, device) - elseif ib.data.value == modeEnum.MINIMUM then - device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.minimum()) - device:emit_event_for_endpoint(ib.endpoint_id, pumpControlMode.supportedControlModes(supported_control_modes)) - elseif ib.data.value == modeEnum.MAXIMUM then - device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.maximum()) - device:emit_event_for_endpoint(ib.endpoint_id, pumpControlMode.supportedControlModes(supported_control_modes)) - elseif ib.data.value == modeEnum.LOCAL then - device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.localSetting()) - device:emit_event_for_endpoint(ib.endpoint_id, pumpControlMode.supportedControlModes(supported_control_modes)) - end -end - -local function effective_control_mode_handler(driver, device, ib, response) - device:emit_event_for_endpoint(ib.endpoint_id, PUMP_CURRENT_CONTROL_MODE_MAP[ib.data.value]()) -end - -local function pump_status_handler(driver, device, ib, response) - if ib.data.value == clusters.PumpConfigurationAndControl.types.PumpStatusBitmap.LOCAL_OVERRIDE then - device:set_field(IS_LOCAL_OVERRIDE, true, {persist = true}) - device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.localSetting()) - local supported_op_modes = {} - local supported_control_modes = {} - device:emit_event(pumpOperationMode.supportedOperationModes(supported_op_modes)) - device:emit_event(pumpControlMode.supportedControlModes(supported_control_modes)) - elseif ib.data.value == clusters.PumpConfigurationAndControl.types.PumpStatusBitmap.RUNNING then - device:set_field(IS_LOCAL_OVERRIDE, false, {persist = true}) - device:send(clusters.PumpConfigurationAndControl.attributes.EffectiveOperationMode:read(device)) - end -end - --- Capability Handlers -- -local function handle_switch_on(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) - 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 = device:component_to_endpoint(cmd.component) - local req = clusters.OnOff.server.commands.Off(device, endpoint_id) - device:send(req) -end - -local function handle_set_level(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) - local level = math.floor(cmd.args.level / MAX_CAP_SWITCH_LEVEL * MAX_PUMP_ATTR_LEVEL) - local req = clusters.LevelControl.server.commands.MoveToLevelWithOnOff(device, endpoint_id, level, cmd.args.rate or 0, 0 ,0) - device:send(req) -end - -local function set_operation_mode(driver, device, cmd) - local mode_id = nil - for id, mode in pairs(PUMP_OPERATION_MODE_MAP) do - if mode.NAME == cmd.args.operationMode then - mode_id = id - break - end - end - if mode_id then - device:send(clusters.PumpConfigurationAndControl.attributes.OperationMode:write(device, device:component_to_endpoint(cmd.component), mode_id)) - end -end - -local function set_control_mode(driver, device, cmd) - local mode_id = nil - for id, mode in pairs(PUMP_CONTROL_MODE_MAP) do - if mode.NAME == cmd.args.controlMode then - mode_id = id - break - end - end - if mode_id then - device:send(clusters.PumpConfigurationAndControl.attributes.ControlMode:write(device, device:component_to_endpoint(cmd.component), mode_id)) - end -end - -local matter_driver_template = { - lifecycle_handlers = { - init = device_init, - doConfigure = do_configure, - infoChanged = info_changed, - }, - matter_handlers = { - attr = { - [clusters.OnOff.ID] = { - [clusters.OnOff.attributes.OnOff.ID] = on_off_attr_handler, - }, - [clusters.LevelControl.ID] = { - [clusters.LevelControl.attributes.CurrentLevel.ID] = level_attr_handler - }, - [clusters.PumpConfigurationAndControl.ID] = { - [clusters.PumpConfigurationAndControl.attributes.EffectiveOperationMode.ID] = effective_operation_mode_handler, - [clusters.PumpConfigurationAndControl.attributes.EffectiveControlMode.ID] = effective_control_mode_handler, - [clusters.PumpConfigurationAndControl.attributes.PumpStatus.ID] = pump_status_handler, - }, - }, - }, - 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.switchLevel.ID] = { - [capabilities.switchLevel.commands.setLevel.NAME] = handle_set_level, - }, - [capabilities.pumpOperationMode.ID] = { - [capabilities.pumpOperationMode.commands.setOperationMode.NAME] = set_operation_mode, - }, - [capabilities.pumpControlMode.ID] = { - [capabilities.pumpControlMode.commands.setControlMode.NAME] = set_control_mode, - }, - }, - supported_capabilities = { - capabilities.switch, - capabilities.switchLevel, - capabilities.pumpOperationMode, - capabilities.pumpControlMode, - }, -} - -local matter_driver = MatterDriver("matter-pump", matter_driver_template) -log.info_with({hub_logs=true}, string.format("Starting %s driver, with dispatcher: %s", matter_driver.NAME, matter_driver.matter_dispatcher)) -matter_driver:run() \ No newline at end of file +-- 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 log = require "log" +local clusters = require "st.matter.clusters" +local embedded_cluster_utils = require "embedded-cluster-utils" +local MatterDriver = require "st.matter.driver" + +local IS_LOCAL_OVERRIDE = "__is_local_override" +-- Per matter spec, the pump level is in steps of 0.5% and the +-- max level value is 200. Anything above is considered 100% +local MAX_PUMP_ATTR_LEVEL = 200 +local MAX_CAP_SWITCH_LEVEL = 100 + +-- Include driver-side definitions when lua libs api version is < 10 +local version = require "version" +if version.api < 10 then + clusters.PumpConfigurationAndControl = require "PumpConfigurationAndControl" +end + +local pumpOperationMode = capabilities.pumpOperationMode +local pumpControlMode = capabilities.pumpControlMode + +local PUMP_OPERATION_MODE_MAP = { + [clusters.PumpConfigurationAndControl.types.OperationModeEnum.NORMAL] = pumpOperationMode.operationMode.normal, + [clusters.PumpConfigurationAndControl.types.OperationModeEnum.MINIMUM] = pumpOperationMode.operationMode.minimum, + [clusters.PumpConfigurationAndControl.types.OperationModeEnum.MAXIMUM] = pumpOperationMode.operationMode.maximum, + [clusters.PumpConfigurationAndControl.types.OperationModeEnum.LOCAL] = pumpOperationMode.operationMode.localSetting, +} + +local PUMP_CONTROL_MODE_MAP = { + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_SPEED] = pumpControlMode.controlMode.constantSpeed, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_PRESSURE] = pumpControlMode.controlMode.constantPressure, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.PROPORTIONAL_PRESSURE] = pumpControlMode.controlMode.proportionalPressure, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_FLOW] = pumpControlMode.controlMode.constantFlow, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_TEMPERATURE] = pumpControlMode.controlMode.constantTemperature, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.AUTOMATIC] = pumpControlMode.controlMode.automatic, +} + +local PUMP_CURRENT_CONTROL_MODE_MAP = { + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_SPEED] = pumpControlMode.currentControlMode.constantSpeed, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_PRESSURE] = pumpControlMode.currentControlMode.constantPressure, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.PROPORTIONAL_PRESSURE] = pumpControlMode.currentControlMode.proportionalPressure, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_FLOW] = pumpControlMode.currentControlMode.constantFlow, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_TEMPERATURE] = pumpControlMode.currentControlMode.constantTemperature, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.AUTOMATIC] = pumpControlMode.currentControlMode.automatic, +} + +local subscribed_attributes = { + [capabilities.switch.ID] = { + clusters.OnOff.attributes.OnOff, + }, + [capabilities.switchLevel.ID] = { + clusters.LevelControl.attributes.CurrentLevel + }, + [capabilities.pumpOperationMode.ID]={ + clusters.PumpConfigurationAndControl.attributes.OperationMode, + clusters.PumpConfigurationAndControl.attributes.EffectiveOperationMode, + clusters.PumpConfigurationAndControl.attributes.PumpStatus, + }, + [capabilities.pumpControlMode.ID]={ + clusters.PumpConfigurationAndControl.attributes.EffectiveControlMode, + }, +} + +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) + -- Use the find_default_endpoint function to return the first endpoint that + -- supports a given cluster. + return find_default_endpoint(device, clusters.PumpConfigurationAndControl.ID) +end + +local function device_init(driver, device) + device:subscribe() + device:set_component_to_endpoint_fn(component_to_endpoint) +end + +local function info_changed(driver, device, event, args) + --Note this is needed because device:subscribe() does not recalculate + -- the subscribed attributes each time it is run, that only happens at init. + -- This will change in the 0.48.x release of the lua libs. + for cap_id, attributes in pairs(subscribed_attributes) do + if device:supports_capability_by_id(cap_id) then + for _, attr in ipairs(attributes) do + device:add_subscribed_attribute(attr) + end + end + end + device:subscribe() +end + +local function set_supported_op_mode(driver, device) + local spd_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_SPEED}) + local local_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.LOCAL_OPERATION}) + local supported_op_modes = {pumpOperationMode.operationMode.normal.NAME} + if #spd_eps > 0 then + table.insert(supported_op_modes, pumpOperationMode.operationMode.minimum.NAME) + table.insert(supported_op_modes, pumpOperationMode.operationMode.maximum.NAME) + end + if #local_eps > 0 then + table.insert(supported_op_modes, pumpOperationMode.operationMode.localSetting.NAME) + end + device:emit_event(pumpOperationMode.supportedOperationModes(supported_op_modes)) +end + +local function set_supported_control_mode(driver, device) + local spd_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_SPEED}) + local prsconst_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_PRESSURE}) + local prscomp_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.COMPENSATED_PRESSURE}) + local flw_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_FLOW}) + local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_TEMPERATURE}) + local auto_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.AUTOMATIC}) + local supported_control_modes = {} + if #spd_eps > 0 then + table.insert(supported_control_modes, pumpControlMode.controlMode.constantSpeed.NAME) + end + if #prsconst_eps > 0 then + table.insert(supported_control_modes, pumpControlMode.controlMode.constantPressure.NAME) + end + if #prscomp_eps > 0 then + table.insert(supported_control_modes, pumpControlMode.controlMode.proportionalPressure.NAME) + end + if #flw_eps > 0 then + table.insert(supported_control_modes, pumpControlMode.controlMode.constantFlow.NAME) + end + if #temp_eps > 0 then + table.insert(supported_control_modes, pumpControlMode.controlMode.constantTemperature.NAME) + end + if #auto_eps > 0 then + table.insert(supported_control_modes, pumpControlMode.controlMode.automatic.NAME) + end + device:emit_event(pumpControlMode.supportedControlModes(supported_control_modes)) +end + +local function do_configure(driver, device) + local pump_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID) + local level_eps = embedded_cluster_utils.get_endpoints(device, clusters.LevelControl.ID) + local profile_name = "pump" + if #pump_eps == 1 then + if #level_eps > 0 then + profile_name = profile_name .. "-level" + else + profile_name = profile_name .. "-only" + end + device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({profile = profile_name}) + else + device.log.warn_with({hub_logs=true}, "Device does not support pump configuration and control cluster") + end + set_supported_op_mode(driver, device) + set_supported_control_mode(driver, device) +end + +-- Matter Handlers -- +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 level_attr_handler(driver, device, ib, response) + if ib.data.value ~= nil then + local level = math.floor((ib.data.value / MAX_PUMP_ATTR_LEVEL * MAX_CAP_SWITCH_LEVEL) + 0.5) + level = math.min(level, MAX_CAP_SWITCH_LEVEL) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switchLevel.level(level)) + end +end + +local function effective_operation_mode_handler(driver, device, ib, response) + local modeEnum = clusters.PumpConfigurationAndControl.types.OperationModeEnum + local supported_control_modes = {} + local local_override = device:get_field(IS_LOCAL_OVERRIDE) + if not local_override then + set_supported_op_mode(driver, device) + end + if ib.data.value == modeEnum.NORMAL then + device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.normal()) + set_supported_control_mode(driver, device) + elseif ib.data.value == modeEnum.MINIMUM then + device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.minimum()) + device:emit_event_for_endpoint(ib.endpoint_id, pumpControlMode.supportedControlModes(supported_control_modes)) + elseif ib.data.value == modeEnum.MAXIMUM then + device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.maximum()) + device:emit_event_for_endpoint(ib.endpoint_id, pumpControlMode.supportedControlModes(supported_control_modes)) + elseif ib.data.value == modeEnum.LOCAL then + device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.localSetting()) + device:emit_event_for_endpoint(ib.endpoint_id, pumpControlMode.supportedControlModes(supported_control_modes)) + end +end + +local function effective_control_mode_handler(driver, device, ib, response) + device:emit_event_for_endpoint(ib.endpoint_id, PUMP_CURRENT_CONTROL_MODE_MAP[ib.data.value]()) +end + +local function pump_status_handler(driver, device, ib, response) + if ib.data.value == clusters.PumpConfigurationAndControl.types.PumpStatusBitmap.LOCAL_OVERRIDE then + device:set_field(IS_LOCAL_OVERRIDE, true, {persist = true}) + device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.localSetting()) + local supported_op_modes = {} + local supported_control_modes = {} + device:emit_event(pumpOperationMode.supportedOperationModes(supported_op_modes)) + device:emit_event(pumpControlMode.supportedControlModes(supported_control_modes)) + elseif ib.data.value == clusters.PumpConfigurationAndControl.types.PumpStatusBitmap.RUNNING then + device:set_field(IS_LOCAL_OVERRIDE, false, {persist = true}) + device:send(clusters.PumpConfigurationAndControl.attributes.EffectiveOperationMode:read(device)) + end +end + +-- Capability Handlers -- +local function handle_switch_on(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + 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 = device:component_to_endpoint(cmd.component) + local req = clusters.OnOff.server.commands.Off(device, endpoint_id) + device:send(req) +end + +local function handle_set_level(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local level = math.floor(cmd.args.level / MAX_CAP_SWITCH_LEVEL * MAX_PUMP_ATTR_LEVEL) + local req = clusters.LevelControl.server.commands.MoveToLevelWithOnOff(device, endpoint_id, level, cmd.args.rate or 0, 0 ,0) + device:send(req) +end + +local function set_operation_mode(driver, device, cmd) + local mode_id = nil + for id, mode in pairs(PUMP_OPERATION_MODE_MAP) do + if mode.NAME == cmd.args.operationMode then + mode_id = id + break + end + end + if mode_id then + device:send(clusters.PumpConfigurationAndControl.attributes.OperationMode:write(device, device:component_to_endpoint(cmd.component), mode_id)) + end +end + +local function set_control_mode(driver, device, cmd) + local mode_id = nil + for id, mode in pairs(PUMP_CONTROL_MODE_MAP) do + if mode.NAME == cmd.args.controlMode then + mode_id = id + break + end + end + if mode_id then + device:send(clusters.PumpConfigurationAndControl.attributes.ControlMode:write(device, device:component_to_endpoint(cmd.component), mode_id)) + end +end + +local matter_driver_template = { + lifecycle_handlers = { + init = device_init, + doConfigure = do_configure, + infoChanged = info_changed, + }, + matter_handlers = { + attr = { + [clusters.OnOff.ID] = { + [clusters.OnOff.attributes.OnOff.ID] = on_off_attr_handler, + }, + [clusters.LevelControl.ID] = { + [clusters.LevelControl.attributes.CurrentLevel.ID] = level_attr_handler + }, + [clusters.PumpConfigurationAndControl.ID] = { + [clusters.PumpConfigurationAndControl.attributes.EffectiveOperationMode.ID] = effective_operation_mode_handler, + [clusters.PumpConfigurationAndControl.attributes.EffectiveControlMode.ID] = effective_control_mode_handler, + [clusters.PumpConfigurationAndControl.attributes.PumpStatus.ID] = pump_status_handler, + }, + }, + }, + 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.switchLevel.ID] = { + [capabilities.switchLevel.commands.setLevel.NAME] = handle_set_level, + }, + [capabilities.pumpOperationMode.ID] = { + [capabilities.pumpOperationMode.commands.setOperationMode.NAME] = set_operation_mode, + }, + [capabilities.pumpControlMode.ID] = { + [capabilities.pumpControlMode.commands.setControlMode.NAME] = set_control_mode, + }, + }, + supported_capabilities = { + capabilities.switch, + capabilities.switchLevel, + capabilities.pumpOperationMode, + capabilities.pumpControlMode, + }, + shared_device_thread_enabled = true, +} + +local matter_driver = MatterDriver("matter-pump", matter_driver_template) +log.info_with({hub_logs=true}, string.format("Starting %s driver, with dispatcher: %s", matter_driver.NAME, matter_driver.matter_dispatcher)) +matter_driver:run() From b243985d8e697ea66f1f07c783970dbc9aba751e Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 316/538] CHAD-17566: zigbee-motion-sensor enable shared_device_thread --- drivers/SmartThings/zigbee-motion-sensor/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/init.lua index 660948720b..9c3f33ad14 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/init.lua @@ -125,6 +125,7 @@ local zigbee_motion_driver = { }, ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_motion_driver, zigbee_motion_driver.supported_capabilities, {native_capability_attrs_enabled = true}) From 038ff511573bc291965880def6a9512c41dcba17 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 317/538] CHAD-17566: matter-switch enable shared_device_thread --- drivers/SmartThings/matter-switch/src/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index b1c6a8df8b..1a409f0787 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -345,7 +345,8 @@ local matter_driver_template = { 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") - } + }, + shared_device_thread_enabled = true, } local matter_driver = MatterDriver("matter-switch", matter_driver_template) From d669470d80db97c42ef4fe92cb90aff1dd4ca6e1 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 318/538] CHAD-17566: zigbee-valve enable shared_device_thread --- drivers/SmartThings/zigbee-valve/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-valve/src/init.lua b/drivers/SmartThings/zigbee-valve/src/init.lua index 1840b55be0..717012e2bb 100644 --- a/drivers/SmartThings/zigbee-valve/src/init.lua +++ b/drivers/SmartThings/zigbee-valve/src/init.lua @@ -43,6 +43,7 @@ local zigbee_valve_driver_template = { }, sub_drivers = require("sub_drivers"), health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_valve_driver_template, zigbee_valve_driver_template.supported_capabilities) From 2db674506757a36479e487d53f481723dd34c2f4 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 319/538] CHAD-17566: zigbee-carbon-monoxide-detector enable shared_device_thread --- drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua index 4ddb66aa4a..8a4f5b5c01 100644 --- a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua @@ -21,6 +21,7 @@ local zigbee_carbon_monoxide_driver_template = { ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, health_check = false, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_carbon_monoxide_driver_template, From 4e1567795a10ea70b437e4d93c25ad8bb67b3005 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 320/538] CHAD-17566: zigbee-switch enable shared_device_thread --- drivers/SmartThings/zigbee-switch/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-switch/src/init.lua b/drivers/SmartThings/zigbee-switch/src/init.lua index 062ac68782..9fbb09119e 100644 --- a/drivers/SmartThings/zigbee-switch/src/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/init.lua @@ -86,6 +86,7 @@ local zigbee_switch_driver_template = { doConfigure = lazy_handler("lifecycle_handlers.do_configure"), }, health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_switch_driver_template, zigbee_switch_driver_template.supported_capabilities, From a1c48cbaee1034960187dd0e8aaf86e58d18206b Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 321/538] CHAD-17566: zigbee-smoke-detector enable shared_device_thread --- drivers/SmartThings/zigbee-smoke-detector/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua index fe64260c11..0f1ba0720b 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua @@ -18,6 +18,7 @@ local zigbee_smoke_driver_template = { sub_drivers = require("sub_drivers"), ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_smoke_driver_template, From b02f6cba04a6d21a7a85022d401d66e5a93f8191 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 322/538] CHAD-17566: zwave-siren enable shared_device_thread --- drivers/SmartThings/zwave-siren/src/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zwave-siren/src/init.lua b/drivers/SmartThings/zwave-siren/src/init.lua index 52ccaba6b9..94e8b723b9 100644 --- a/drivers/SmartThings/zwave-siren/src/init.lua +++ b/drivers/SmartThings/zwave-siren/src/init.lua @@ -88,7 +88,8 @@ local driver_template = { infoChanged = info_changed, doConfigure = do_configure, added = added_handler - } + }, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) From 953c28678a0f957f8fb1636a12019b517c5b8a2f Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:31 -0500 Subject: [PATCH 323/538] CHAD-17566: zwave-lock enable shared_device_thread --- drivers/SmartThings/zwave-lock/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zwave-lock/src/init.lua b/drivers/SmartThings/zwave-lock/src/init.lua index 925452c431..c3506c5005 100644 --- a/drivers/SmartThings/zwave-lock/src/init.lua +++ b/drivers/SmartThings/zwave-lock/src/init.lua @@ -172,6 +172,7 @@ local driver_template = { } }, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) From 1d0871e35417cda2f66166494566556beb3bf4c5 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 324/538] CHAD-17566: zigbee-bed enable shared_device_thread --- drivers/SmartThings/zigbee-bed/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-bed/src/init.lua b/drivers/SmartThings/zigbee-bed/src/init.lua index 9f464c38ce..9d3e798b8b 100755 --- a/drivers/SmartThings/zigbee-bed/src/init.lua +++ b/drivers/SmartThings/zigbee-bed/src/init.lua @@ -12,6 +12,7 @@ local zigbee_bed_template = { }, sub_drivers = require("sub_drivers"), health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_bed_template, zigbee_bed_template.supported_capabilities) From c6c730f90e6519ffbc118c46ca8e1719163262e7 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 325/538] CHAD-17566: zwave-bulb enable shared_device_thread --- drivers/SmartThings/zwave-bulb/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zwave-bulb/src/init.lua b/drivers/SmartThings/zwave-bulb/src/init.lua index 58e2f32a7e..fd6c9a9b8f 100644 --- a/drivers/SmartThings/zwave-bulb/src/init.lua +++ b/drivers/SmartThings/zwave-bulb/src/init.lua @@ -21,6 +21,7 @@ local driver_template = { capabilities.powerMeter }, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities, {native_capability_cmds_enabled = true}) From daabdf94bf778d94dc9935dc0806b4a11ae41aa1 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 326/538] CHAD-17566: zigbee-presence-sensor enable shared_device_thread --- drivers/SmartThings/zigbee-presence-sensor/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/init.lua b/drivers/SmartThings/zigbee-presence-sensor/src/init.lua index 261bc90922..d797a09b62 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/init.lua @@ -196,6 +196,7 @@ local zigbee_presence_driver = { zigbee_message_handler = all_zigbee_message_handler, sub_drivers = require("sub_drivers"), health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_presence_driver, zigbee_presence_driver.supported_capabilities) From 210e9771edeff102afeab49f21546dfe17f55193 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 327/538] CHAD-17566: matter-sensor enable shared_device_thread --- drivers/SmartThings/matter-sensor/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/matter-sensor/src/init.lua b/drivers/SmartThings/matter-sensor/src/init.lua index 73e34fcd56..297493c652 100644 --- a/drivers/SmartThings/matter-sensor/src/init.lua +++ b/drivers/SmartThings/matter-sensor/src/init.lua @@ -296,6 +296,7 @@ local matter_driver_template = { capabilities.flowMeasurement, }, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } local matter_driver = MatterDriver("matter-sensor", matter_driver_template) From e0d42eece6ce78f32690cafcbbde0fcb97bd8fb8 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 328/538] CHAD-17566: zigbee-fan enable shared_device_thread --- drivers/SmartThings/zigbee-fan/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-fan/src/init.lua b/drivers/SmartThings/zigbee-fan/src/init.lua index c4a7185838..33af1cb320 100644 --- a/drivers/SmartThings/zigbee-fan/src/init.lua +++ b/drivers/SmartThings/zigbee-fan/src/init.lua @@ -27,6 +27,7 @@ local zigbee_fan_driver = { init = device_init }, health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_fan_driver,zigbee_fan_driver.supported_capabilities) From c70dbe1bf5c33eca140a95e6fda938bcc2467590 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 329/538] CHAD-17566: zigbee-contact enable shared_device_thread --- drivers/SmartThings/zigbee-contact/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-contact/src/init.lua b/drivers/SmartThings/zigbee-contact/src/init.lua index 63a7bf7565..0258d5360a 100644 --- a/drivers/SmartThings/zigbee-contact/src/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/init.lua @@ -90,6 +90,7 @@ local zigbee_contact_driver_template = { sub_drivers = require("sub_drivers"), ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_contact_driver_template, From d6254b3addc948441835d8eec6a650d4d4f1f3fa Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 330/538] CHAD-17566: zigbee-dimmer-remote enable shared_device_thread --- drivers/SmartThings/zigbee-dimmer-remote/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/init.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/init.lua index be7f8ad147..ed6969cbc8 100644 --- a/drivers/SmartThings/zigbee-dimmer-remote/src/init.lua +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/init.lua @@ -32,6 +32,7 @@ local zigbee_dimmer_remote_driver_template = { }, sub_drivers = require("sub_drivers"), health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_dimmer_remote_driver_template, zigbee_dimmer_remote_driver_template.supported_capabilities) From 28b70d0c4c348911375270335cfbb562b15d6851 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 331/538] CHAD-17566: zigbee-power-meter enable shared_device_thread --- drivers/SmartThings/zigbee-power-meter/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-power-meter/src/init.lua b/drivers/SmartThings/zigbee-power-meter/src/init.lua index 6aa3d4b8c1..4f69f0e72e 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/init.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/init.lua @@ -60,6 +60,7 @@ local zigbee_power_meter_driver_template = { doConfigure = do_configure, }, health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_power_meter_driver_template, zigbee_power_meter_driver_template.supported_capabilities) From f4420589b543dd4421c7837ee376d2dcf983cae2 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 332/538] CHAD-17566: zigbee-window-treatment enable shared_device_thread --- drivers/SmartThings/zigbee-window-treatment/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/init.lua index 16783a8726..0a093645f8 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/init.lua @@ -48,6 +48,7 @@ local zigbee_window_treatment_driver_template = { }, sub_drivers = require("sub_drivers"), health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_window_treatment_driver_template, zigbee_window_treatment_driver_template.supported_capabilities) From f1a4443724dabdf87419ff4d79a598d7b169856b Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 333/538] CHAD-17566: zigbee-sensor enable shared_device_thread --- drivers/SmartThings/zigbee-sensor/src/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-sensor/src/init.lua b/drivers/SmartThings/zigbee-sensor/src/init.lua index 487c19a733..348d186613 100644 --- a/drivers/SmartThings/zigbee-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-sensor/src/init.lua @@ -103,8 +103,9 @@ local zigbee_generic_sensor_template = { }, ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_generic_sensor_template, zigbee_generic_sensor_template.supported_capabilities) local zigbee_sensor = ZigbeeDriver("zigbee-sensor", zigbee_generic_sensor_template) -zigbee_sensor:run() \ No newline at end of file +zigbee_sensor:run() From b449802bbdb07318c60b3581db505de0dbd70a9d Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 334/538] CHAD-17566: zigbee-siren enable shared_device_thread --- drivers/SmartThings/zigbee-siren/src/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-siren/src/init.lua b/drivers/SmartThings/zigbee-siren/src/init.lua index b1ad81c9c6..8e4812c3bd 100644 --- a/drivers/SmartThings/zigbee-siren/src/init.lua +++ b/drivers/SmartThings/zigbee-siren/src/init.lua @@ -197,8 +197,9 @@ local zigbee_siren_driver_template = { } }, health_check = false, + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_siren_driver_template, zigbee_siren_driver_template.supported_capabilities) local zigbee_siren = ZigbeeDriver("zigbee-siren", zigbee_siren_driver_template) -zigbee_siren:run() \ No newline at end of file +zigbee_siren:run() From 2132c636d551463a9e5f945c55eb185a05d59cec Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 335/538] CHAD-17566: zwave-window-treatment enable shared_device_thread --- drivers/SmartThings/zwave-window-treatment/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zwave-window-treatment/src/init.lua b/drivers/SmartThings/zwave-window-treatment/src/init.lua index b2597d9f61..8b8f32c49b 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/init.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/init.lua @@ -75,6 +75,7 @@ local driver_template = { } }, sub_drivers = require("sub_drivers"), + shared_device_thread_enabled = true, } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) From 375e86276a08406f83b1e1168f094fcb683c84ab Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 28 Apr 2026 12:25:32 -0500 Subject: [PATCH 336/538] CHAD-17566: matter-lock enable shared_device_thread --- drivers/SmartThings/matter-lock/src/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/matter-lock/src/init.lua b/drivers/SmartThings/matter-lock/src/init.lua index b3403863ec..4f58e960e9 100755 --- a/drivers/SmartThings/matter-lock/src/init.lua +++ b/drivers/SmartThings/matter-lock/src/init.lua @@ -714,6 +714,7 @@ local matter_lock_driver = { doConfigure = do_configure, infoChanged = info_changed, }, + shared_device_thread_enabled = true, } ----------------------------------------------------------------------------------------------------------------------------- From 7f042d1bcfc63f0e64666946de61fda401d1701e Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 4 May 2026 16:09:48 -0500 Subject: [PATCH 337/538] Matter Lock: Re-profile a Lock if it requires an unlatch embedded config (#2945) --- .../matter-lock/src/new-matter-lock/init.lua | 3 +- .../src/test/test_matter_lock_modular.lua | 73 +++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) 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 fbea3ebfb5..adcbc04283 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -259,7 +259,8 @@ local function match_profile_modular(driver, device) end table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) - if lock_utils.optional_capabilities_list_changed(enabled_optional_component_capability_pairs, device.profile.components) then + if modular_profile_name == "lock-modular-embedded-unlatch" -- the embedded config that may be needed is not checked by an optional capability comparison + or lock_utils.optional_capabilities_list_changed(enabled_optional_component_capability_pairs, device.profile.components) then device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) end end diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index cb22abdf7d..4b875fd1bc 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -623,4 +623,77 @@ test.register_coroutine_test( } ) + +local mock_nuki_smart_lock_ultra = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("lock-nocodes-notamper.yml"), + manufacturer_info = { + vendor_id = 0x135D, + product_id = 0x00A1, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x1000, -- UNBOLT + }, + { + cluster_id = clusters.PowerSource.ID, + cluster_type = "SERVER", + feature_map = 10 + }, + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + +local battery_support = { + NO_BATTERY = "NO_BATTERY", + BATTERY_LEVEL = "BATTERY_LEVEL", + BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE" +} + +local profiling_data = { + BATTERY_SUPPORT = "__BATTERY_SUPPORT", +} + +test.register_coroutine_test( + "Test Nuki Smart Lock Ultra profile change with user and pin supported", + function() + -- technically, since power source attributes must be read, this wouldn't be running via doConfigure, but this is straightforward. + test.socket.device_lifecycle:__queue_receive({ mock_nuki_smart_lock_ultra.id, "doConfigure" }) + test.socket.capability:__expect_send( + mock_nuki_smart_lock_ultra:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "unlatched", "not fully locked"}, {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_nuki_smart_lock_ultra:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) + ) + mock_nuki_smart_lock_ultra:expect_metadata_update({ profile = "lock-modular-embedded-unlatch", optional_component_capabilities = {{"main", {"battery"}}}}) + mock_nuki_smart_lock_ultra:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + test_init = function() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_nuki_smart_lock_ultra) + mock_nuki_smart_lock_ultra:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_PERCENTAGE, {persist = true}) -- assume this has been set previously + end, + min_api_version = 17 + } +) + test.run_registered_tests() From 7503f883cdf5d2dd284495c5670be4e0827c3333 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 5 May 2026 15:56:28 -0500 Subject: [PATCH 338/538] Revert "WWSTCERT-11168 Smart Radiator Thermostat X" (#2948) This reverts commit d384819cabdab8dd5308e765970a1e8f517bd7e3. --- drivers/SmartThings/matter-thermostat/fingerprints.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/drivers/SmartThings/matter-thermostat/fingerprints.yml b/drivers/SmartThings/matter-thermostat/fingerprints.yml index b33e03f791..cd1e7c5cbd 100644 --- a/drivers/SmartThings/matter-thermostat/fingerprints.yml +++ b/drivers/SmartThings/matter-thermostat/fingerprints.yml @@ -89,16 +89,6 @@ matterManufacturer: vendorId: 0x134E productId: 0x0002 deviceProfileName: thermostat-humidity-heating-only-nostate-nobattery - - id: "4942/9" - deviceLabel: Smart Radiator Thermostat X - vendorId: 0x134E - productId: 0x0009 - deviceProfileName: thermostat-humidity-heating-only-nostate-batteryLevel - - id: "4942/8" - deviceLabel: Wireless Temperature Sensor X (2nd Gen) - vendorId: 0x134E - productId: 0x0008 - deviceProfileName: thermostat-humidity-heating-only-nostate-batteryLevel #Taruie - id: "5151/4101" deviceLabel: TARUIE AC Remote From 9687c2daa3021d48ffdcecbdea47a37d962fd39d Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Wed, 6 May 2026 09:32:00 -0500 Subject: [PATCH 339/538] Add support for Irrigation System device type (#2684) This adds support for the Irrigation System device type, introduced with Matter 1.5. --- .../matter-switch/fingerprints.yml | 5 + .../profiles/irrigation-system.yml | 26 + .../SmartThings/matter-switch/src/init.lua | 39 +- .../switch_handlers/attribute_handlers.lua | 75 +++ .../switch_handlers/capability_handlers.lua | 17 + .../src/switch_utils/device_configuration.lua | 65 ++- .../matter-switch/src/switch_utils/fields.lua | 11 + .../test/test_matter_irrigation_system.lua | 477 ++++++++++++++++++ 8 files changed, 704 insertions(+), 11 deletions(-) create mode 100644 drivers/SmartThings/matter-switch/profiles/irrigation-system.yml create mode 100644 drivers/SmartThings/matter-switch/src/test/test_matter_irrigation_system.lua diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 8f0566ec7f..b73ac0e98f 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -4165,6 +4165,11 @@ matterGeneric: deviceTypes: - id: 0x0110 # Mounted Dimmable Load Control deviceProfileName: switch-level + - id: "matter/irrigation-system" + deviceLabel: Matter Irrigation System + deviceTypes: + - id: 0x0040 # Irrigation System + deviceProfileName: irrigation-system - id: "matter/water-valve" deviceLabel: Matter Water Valve deviceTypes: diff --git a/drivers/SmartThings/matter-switch/profiles/irrigation-system.yml b/drivers/SmartThings/matter-switch/profiles/irrigation-system.yml new file mode 100644 index 0000000000..15896fbfee --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/irrigation-system.yml @@ -0,0 +1,26 @@ +name: irrigation-system +components: +- id: main + capabilities: + - id: valve + version: 1 + - id: level + version: 1 + config: + values: + - key: "level.value" + range: [0, 100] + optional: true + - id: flowMeasurement + version: 1 + optional: true + - id: operationalState + version: 1 + optional: true + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Irrigation + diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 1a409f0787..d560013a17 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -8,9 +8,9 @@ local clusters = require "st.matter.clusters" local log = require "log" local version = require "version" local cfg = require "switch_utils.device_configuration" +local button_cfg = cfg.ButtonCfg local device_cfg = cfg.DeviceCfg local switch_cfg = cfg.SwitchCfg -local button_cfg = cfg.ButtonCfg local fields = require "switch_utils.fields" local switch_utils = require "switch_utils.utils" local attribute_handlers = require "switch_handlers.attribute_handlers" @@ -47,6 +47,7 @@ function SwitchLifecycleHandlers.do_configure(driver, device) switch_cfg.set_device_control_options(device) device_cfg.match_profile(driver, device) elseif device.network_type == device_lib.NETWORK_TYPE_CHILD then + device_cfg.match_child_profile(driver, device) -- 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() @@ -148,6 +149,11 @@ local matter_driver_template = { [clusters.FanControl.attributes.FanModeSequence.ID] = attribute_handlers.fan_mode_sequence_handler, [clusters.FanControl.attributes.PercentCurrent.ID] = attribute_handlers.percent_current_handler }, + [clusters.FlowMeasurement.ID] = { + [clusters.FlowMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.flow_attr_handler, + [clusters.FlowMeasurement.attributes.MinMeasuredValue.ID] = attribute_handlers.flow_attr_handler_factory(fields.FLOW_MIN), + [clusters.FlowMeasurement.attributes.MaxMeasuredValue.ID] = attribute_handlers.flow_attr_handler_factory(fields.FLOW_MAX) + }, [clusters.IlluminanceMeasurement.ID] = { [clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.illuminance_measured_value_handler }, @@ -159,6 +165,11 @@ local matter_driver_template = { [clusters.OccupancySensing.ID] = { [clusters.OccupancySensing.attributes.Occupancy.ID] = attribute_handlers.occupancy_handler, }, + [clusters.OperationalState.ID] = { + [clusters.OperationalState.attributes.AcceptedCommandList.ID] = attribute_handlers.operational_state_accepted_command_list_attr_handler, + [clusters.OperationalState.attributes.OperationalState.ID] = attribute_handlers.operational_state_attr_handler, + [clusters.OperationalState.attributes.OperationalError.ID] = attribute_handlers.operational_error_attr_handler + }, [clusters.OnOff.ID] = { [clusters.OnOff.attributes.OnOff.ID] = attribute_handlers.on_off_attr_handler, }, @@ -226,17 +237,24 @@ local matter_driver_template = { [capabilities.fanSpeedPercent.ID] = { clusters.FanControl.attributes.PercentCurrent }, + [capabilities.flowMeasurement.ID] = { + clusters.FlowMeasurement.attributes.MeasuredValue, + clusters.FlowMeasurement.attributes.MinMeasuredValue, + clusters.FlowMeasurement.attributes.MaxMeasuredValue + }, [capabilities.illuminanceMeasurement.ID] = { clusters.IlluminanceMeasurement.attributes.MeasuredValue }, - [capabilities.motionSensor.ID] = { - clusters.OccupancySensing.attributes.Occupancy - }, [capabilities.level.ID] = { clusters.ValveConfigurationAndControl.attributes.CurrentLevel }, - [capabilities.switch.ID] = { - clusters.OnOff.attributes.OnOff + [capabilities.motionSensor.ID] = { + clusters.OccupancySensing.attributes.Occupancy + }, + [capabilities.operationalState.ID] = { + clusters.OperationalState.attributes.AcceptedCommandList, + clusters.OperationalState.attributes.OperationalState, + clusters.OperationalState.attributes.OperationalError }, [capabilities.powerMeter.ID] = { clusters.ElectricalPowerMeasurement.attributes.ActivePower @@ -244,6 +262,9 @@ local matter_driver_template = { [capabilities.relativeHumidityMeasurement.ID] = { clusters.RelativeHumidityMeasurement.attributes.MeasuredValue }, + [capabilities.switch.ID] = { + clusters.OnOff.attributes.OnOff + }, [capabilities.switchLevel.ID] = { clusters.LevelControl.attributes.CurrentLevel, clusters.LevelControl.attributes.MaxLevel, @@ -287,6 +308,10 @@ local matter_driver_template = { [capabilities.level.ID] = { [capabilities.level.commands.setLevel.NAME] = capability_handlers.handle_set_level }, + [capabilities.operationalState.ID] = { + [capabilities.operationalState.commands.pause.NAME] = capability_handlers.handle_operational_state_pause, + [capabilities.operationalState.commands.resume.NAME] = capability_handlers.handle_operational_state_resume + }, [capabilities.statelessColorTemperatureStep.ID] = { [capabilities.statelessColorTemperatureStep.commands.stepColorTemperatureByPercent.NAME] = capability_handlers.handle_step_color_temperature_by_percent, }, @@ -319,6 +344,7 @@ local matter_driver_template = { capabilities.energyMeter, capabilities.fanMode, capabilities.fanSpeedPercent, + capabilities.flowMeasurement, capabilities.hdr, capabilities.illuminanceMeasurement, capabilities.imageControl, @@ -327,6 +353,7 @@ local matter_driver_template = { capabilities.mechanicalPanTiltZoom, capabilities.motionSensor, capabilities.nightVision, + capabilities.operationalState, capabilities.powerMeter, capabilities.powerConsumptionReport, capabilities.relativeHumidityMeasurement, diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index 20bd92881e..0fdc9cc822 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -550,4 +550,79 @@ function AttributeHandlers.percent_current_handler(driver, device, ib, response) device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanSpeedPercent.percent(ib.data.value)) end + +-- [[ OPERATIONAL STATE CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.operational_state_accepted_command_list_attr_handler(driver, device, ib, response) + if ib.data.elements == nil then return end + local accepted_command_list = {} + for _, accepted_command in ipairs(ib.data.elements) do + local accepted_command_id = accepted_command.value + if fields.operational_state_command_map[accepted_command_id] ~= nil then + table.insert(accepted_command_list, fields.operational_state_command_map[accepted_command_id]) + end + end + local event = capabilities.operationalState.supportedCommands(accepted_command_list, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +function AttributeHandlers.operational_state_attr_handler(driver, device, ib, response) + if ib.data.value == clusters.OperationalState.types.OperationalStateEnum.STOPPED then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.operationalState.operationalState.stopped()) + elseif ib.data.value == clusters.OperationalState.types.OperationalStateEnum.RUNNING then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.operationalState.operationalState.running()) + elseif ib.data.value == clusters.OperationalState.types.OperationalStateEnum.PAUSED then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.operationalState.operationalState.paused()) + end +end + +function AttributeHandlers.operational_error_attr_handler(driver, device, ib, response) + if ib.data.elements == nil or ib.data.elements.error_state_id == nil or ib.data.elements.error_state_id.value == nil then return end + if version.api < 10 then + clusters.OperationalState.types.ErrorStateStruct:augment_type(ib.data) + end + local operationalError = ib.data.elements.error_state_id.value + if operationalError == clusters.OperationalState.types.ErrorStateEnum.UNABLE_TO_START_OR_RESUME then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.operationalState.operationalState.unableToStartOrResume()) + elseif operationalError == clusters.OperationalState.types.ErrorStateEnum.UNABLE_TO_COMPLETE_OPERATION then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.operationalState.operationalState.unableToCompleteOperation()) + elseif operationalError == clusters.OperationalState.types.ErrorStateEnum.COMMAND_INVALID_IN_STATE then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.operationalState.operationalState.commandInvalidInCurrentState()) + end +end + + +-- [[ FLOW MEASUREMENT CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.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 + +function AttributeHandlers.flow_attr_handler_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" + switch_utils.set_field_for_endpoint(device, fields.FLOW_BOUND_RECEIVED..minOrMax, ib.endpoint_id, flow_bound) + local min = switch_utils.get_field_for_endpoint(device, fields.FLOW_BOUND_RECEIVED..fields.FLOW_MIN, ib.endpoint_id) + local max = switch_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 })) + switch_utils.set_field_for_endpoint(device, fields.FLOW_BOUND_RECEIVED..fields.FLOW_MIN, ib.endpoint_id, nil) + switch_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 diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua index 030a54a751..6d035c3dc7 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua @@ -232,4 +232,21 @@ function CapabilityHandlers.handle_reset_energy_meter(driver, device, cmd) end end + +-- [[ OPERATIONAL STATE CAPABILITY COMMANDS ]] -- + +function CapabilityHandlers.handle_operational_state_resume(driver, device, cmd) + local endpoint_id = device:get_endpoints(clusters.OperationalState.ID)[1] + device:send(clusters.OperationalState.server.commands.Resume(device, endpoint_id)) + device:send(clusters.OperationalState.attributes.OperationalState:read(device, endpoint_id)) + device:send(clusters.OperationalState.attributes.OperationalError:read(device, endpoint_id)) +end + +function CapabilityHandlers.handle_operational_state_pause(driver, device, cmd) + local endpoint_id = device:get_endpoints(clusters.OperationalState.ID)[1] + device:send(clusters.OperationalState.server.commands.Pause(device, endpoint_id)) + device:send(clusters.OperationalState.attributes.OperationalState:read(device, endpoint_id)) + device:send(clusters.OperationalState.attributes.OperationalError:read(device, endpoint_id)) +end + return CapabilityHandlers diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 8f1f1311fa..085de2d351 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -20,6 +20,7 @@ local ChildConfiguration = {} local SwitchDeviceConfiguration = {} local ButtonDeviceConfiguration = {} local FanDeviceConfiguration = {} +local ValveDeviceConfiguration = {} 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 @@ -30,7 +31,7 @@ function ChildConfiguration.create_or_update_child_devices(driver, device, serve 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 child_profile, optional_component_capabilities = 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({ @@ -43,7 +44,8 @@ function ChildConfiguration.create_or_update_child_devices(driver, device, serve }) else existing_child_device:try_update_metadata({ - profile = child_profile + profile = child_profile, + optional_component_capabilities = optional_component_capabilities }) end end @@ -74,7 +76,6 @@ function FanDeviceConfiguration.assign_profile_for_fan_ep(device, server_fan_ep_ 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) @@ -190,9 +191,56 @@ function ButtonDeviceConfiguration.configure_buttons(device, momentary_switch_ep end end +function ValveDeviceConfiguration.assign_profile_for_irrigation_system_ep(device, irrigation_system_ep_id, is_child_device) + local main_component_capabilities = {} + local profile_name = "irrigation-system" + + local valve_ep_ids = switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.WATER_VALVE) + table.sort(valve_ep_ids) + local supports_level = switch_utils.find_cluster_on_ep( + switch_utils.get_endpoint_info(device, is_child_device and irrigation_system_ep_id or valve_ep_ids[1]), + clusters.ValveConfigurationAndControl.ID, + {feature_bitmap = clusters.ValveConfigurationAndControl.types.Feature.LEVEL} + ) + if supports_level then + table.insert(main_component_capabilities, capabilities.level.ID) + end + + if is_child_device then + return profile_name, {{"main", main_component_capabilities}} + end + + local irrigation_system_ep_info = switch_utils.get_endpoint_info(device, irrigation_system_ep_id) + if switch_utils.find_cluster_on_ep(irrigation_system_ep_info, clusters.FlowMeasurement.ID) then + table.insert(main_component_capabilities, capabilities.flowMeasurement.ID) + end + if switch_utils.find_cluster_on_ep(irrigation_system_ep_info, clusters.OperationalState.ID) then + table.insert(main_component_capabilities, capabilities.operationalState.ID) + end + + return profile_name, {{"main", main_component_capabilities}} +end + -- [[ PROFILE MATCHING AND CONFIGURATIONS ]] -- +function DeviceConfiguration.match_child_profile(driver, device) + local parent_device = device:get_parent_device() + local irrigation_system_ep_ids = switch_utils.get_endpoints_by_device_type( + parent_device, + fields.DEVICE_TYPE_ID.IRRIGATION_SYSTEM + ) + if #irrigation_system_ep_ids > 0 then + ChildConfiguration.create_or_update_child_devices( + driver, + parent_device, + {device:get_endpoint()}, + nil, + ValveDeviceConfiguration.assign_profile_for_irrigation_system_ep + ) + end +end + local function profiling_data_still_required(device) for _, field in pairs(fields.profiling_data) do if device:get_field(field) == nil then @@ -230,7 +278,12 @@ function DeviceConfiguration.match_profile(driver, device) end end - if #switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.WATER_VALVE) > 0 then + local irrigation_system_ep_ids = switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.IRRIGATION_SYSTEM) + local valve_ep_ids = switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.WATER_VALVE) + if #irrigation_system_ep_ids > 0 then + updated_profile, optional_component_capabilities = ValveDeviceConfiguration.assign_profile_for_irrigation_system_ep(device, irrigation_system_ep_ids[1], false) + ChildConfiguration.create_or_update_child_devices(driver, device, valve_ep_ids, default_endpoint_id, ValveDeviceConfiguration.assign_profile_for_irrigation_system_ep) + elseif #valve_ep_ids > 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 @@ -255,7 +308,9 @@ function DeviceConfiguration.match_profile(driver, device) end return { + ButtonCfg = ButtonDeviceConfiguration, + ChildCfg = ChildConfiguration, DeviceCfg = DeviceConfiguration, SwitchCfg = SwitchDeviceConfiguration, - ButtonCfg = ButtonDeviceConfiguration + ValveCfg = ValveDeviceConfiguration } diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index cb93b6331a..875589f227 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -1,6 +1,8 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local clusters = require "st.matter.clusters" + local SwitchFields = {} SwitchFields.MOST_RECENT_TEMP = "mostRecentTemp" @@ -32,6 +34,7 @@ SwitchFields.DEVICE_TYPE_ID = { ELECTRICAL_SENSOR = 0x0510, FAN = 0x002B, GENERIC_SWITCH = 0x000F, + IRRIGATION_SYSTEM = 0x0040, MOUNTED_ON_OFF_CONTROL = 0x010F, MOUNTED_DIMMABLE_LOAD_CONTROL = 0x0110, ON_OFF_PLUG_IN_UNIT = 0x010A, @@ -85,6 +88,9 @@ SwitchFields.LEVEL_BOUND_RECEIVED = "__level_bound_received" SwitchFields.LEVEL_MIN = "__level_min" SwitchFields.LEVEL_MAX = "__level_max" SwitchFields.COLOR_MODE = "__color_mode" +SwitchFields.FLOW_BOUND_RECEIVED = "__flow_bound_received" +SwitchFields.FLOW_MIN = "__flow_min" +SwitchFields.FLOW_MAX = "__flow_max" SwitchFields.SUBSCRIBED_ATTRIBUTES_KEY = "__subscribed_attributes" @@ -141,6 +147,11 @@ SwitchFields.switch_category_vendor_overrides = { {0xEEE2, 0xAB08, 0xAB31, 0xAB04, 0xAB01, 0xAB43, 0xAB02, 0xAB03, 0xAB05} } +SwitchFields.operational_state_command_map = { + [clusters.OperationalState.commands.Pause.ID] = "pause", + [clusters.OperationalState.commands.Resume.ID] = "resume" +} + --- 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" diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_irrigation_system.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_irrigation_system.lua new file mode 100644 index 0000000000..241f17c53e --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_irrigation_system.lua @@ -0,0 +1,477 @@ +-- Copyright © 2026 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 version = require "version" + +if version.api < 11 then + clusters.ValveConfigurationAndControl = require "embedded_clusters.ValveConfigurationAndControl" +end + +local endpoints = { + ROOT_EP = 0, + IRRIGATION_SYSTEM_EP = 1, + VALVE_1_EP = 2, + VALVE_2_EP = 3, + VALVE_3_EP = 4 +} + +-- Mock device representing an irrigation system with 3 valve endpoints +local mock_irrigation_system = test.mock_device.build_test_matter_device({ + label = "Matter Irrigation System", + profile = t_utils.get_profile_definition("irrigation-system.yml"), + manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, + matter_version = {hardware = 1, software = 1}, + endpoints = { + { + endpoint_id = endpoints.ROOT_EP, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0016, device_type_revision = 1} -- RootNode + } + }, + { + endpoint_id = endpoints.IRRIGATION_SYSTEM_EP, + clusters = { + {cluster_id = clusters.Descriptor.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.FlowMeasurement.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.OperationalState.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0040, device_type_revision = 1} -- Irrigation System + } + }, + { + endpoint_id = endpoints.VALVE_1_EP, + clusters = { + { + cluster_id = clusters.ValveConfigurationAndControl.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 2 -- LEVEL feature + }, + }, + device_types = { + {device_type_id = 0x0042, device_type_revision = 1} -- Water Valve + } + }, + { + endpoint_id = endpoints.VALVE_2_EP, + clusters = { + { + cluster_id = clusters.ValveConfigurationAndControl.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 2 -- LEVEL feature + }, + }, + device_types = { + {device_type_id = 0x0042, device_type_revision = 1} -- Water Valve + } + }, + { + endpoint_id = endpoints.VALVE_3_EP, + clusters = { + { + cluster_id = clusters.ValveConfigurationAndControl.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 2 -- LEVEL feature + }, + }, + device_types = { + {device_type_id = 0x0042, device_type_revision = 1} -- Water Valve + } + } + } +}) + +local mock_children = {} +for _, endpoint in ipairs(mock_irrigation_system.endpoints) do + if endpoint.endpoint_id == 3 or endpoint.endpoint_id == 4 then + local child_data = { + profile = t_utils.get_profile_definition("water-valve-level.yml"), + device_network_id = string.format("%s:%d", mock_irrigation_system.id, endpoint.endpoint_id), + parent_device_id = mock_irrigation_system.id, + parent_assigned_child_key = string.format("%d", endpoint.endpoint_id) + } + mock_children[endpoint.endpoint_id] = test.mock_device.build_test_child_device(child_data) + end +end + +local subscribe_request + +local expected_metadata = { + optional_component_capabilities = { { "main", { "level", "flowMeasurement", "operationalState", } } }, + profile = "irrigation-system" +} + +local function test_init() + test.mock_device.add_test_device(mock_irrigation_system) + local cluster_subscribe_list = { + clusters.ValveConfigurationAndControl.attributes.CurrentState, + clusters.ValveConfigurationAndControl.attributes.CurrentLevel, + } + subscribe_request = cluster_subscribe_list[1]:subscribe(mock_irrigation_system) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_irrigation_system)) + end + end + test.socket.device_lifecycle:__queue_receive({ mock_irrigation_system.id, "added" }) + test.socket.matter:__expect_send({mock_irrigation_system.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_irrigation_system.id, "init" }) + test.socket.matter:__expect_send({mock_irrigation_system.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_irrigation_system.id, "doConfigure" }) + mock_irrigation_system:expect_metadata_update(expected_metadata) + mock_irrigation_system:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + for _, child in pairs(mock_children) do + test.mock_device.add_test_device(child) + end + for i = 3,4 do + mock_irrigation_system:expect_device_create({ + type = "EDGE_CHILD", + label = string.format("Matter Irrigation System %d", i - 1), + profile = "irrigation-system", + parent_device_id = mock_irrigation_system.id, + parent_assigned_child_key = string.format("%d", i) + }) + end + test.socket.matter:__expect_send({mock_irrigation_system.id, subscribe_request}) +end +test.set_test_init_function(test_init) + + +local additional_subscribed_attributes = { + clusters.FlowMeasurement.attributes.MeasuredValue, + clusters.FlowMeasurement.attributes.MaxMeasuredValue, + clusters.FlowMeasurement.attributes.MinMeasuredValue, + clusters.OperationalState.attributes.AcceptedCommandList, + clusters.OperationalState.attributes.OperationalError, + clusters.OperationalState.attributes.OperationalState, +} + +local function update_device_profile() + local updated_device_profile = t_utils.get_profile_definition( + "irrigation-system.yml", { enabled_optional_capabilities = expected_metadata.optional_component_capabilities } + ) + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive(mock_irrigation_system:generate_info_changed({ profile = updated_device_profile })) + for _, attr in ipairs(additional_subscribed_attributes) do + subscribe_request:merge(attr:subscribe(mock_irrigation_system)) + end + test.socket.matter:__expect_send({mock_irrigation_system.id, subscribe_request}) +end + +test.register_coroutine_test( + "Parent device: Open command should send the appropriate commands", + function() + test.socket.capability:__queue_receive({ + mock_irrigation_system.id, + { capability = "valve", component = "main", command = "open", args = { } } + }) + test.socket.matter:__expect_send({ + mock_irrigation_system.id, + clusters.ValveConfigurationAndControl.server.commands.Open(mock_irrigation_system, endpoints.VALVE_1_EP) + }) + end +) + +test.register_coroutine_test( + "Parent device: Close command should send the appropriate commands", + function() + test.socket.capability:__queue_receive({ + mock_irrigation_system.id, + { capability = "valve", component = "main", command = "close", args = { } } + }) + test.socket.matter:__expect_send({ + mock_irrigation_system.id, + clusters.ValveConfigurationAndControl.server.commands.Close(mock_irrigation_system, endpoints.VALVE_1_EP) + }) + end +) + +test.register_coroutine_test( + "Parent device: Set level command should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + + test.socket.capability:__queue_receive({ + mock_irrigation_system.id, + { capability = "level", component = "main", command = "setLevel", args = { 75 } } + }) + test.socket.matter:__expect_send({ + mock_irrigation_system.id, + clusters.ValveConfigurationAndControl.server.commands.Open(mock_irrigation_system, endpoints.VALVE_1_EP, nil, 75) + }) + end +) + +test.register_coroutine_test( + "Parent device: Current state closed should generate closed event", + function() + test.socket.matter:__queue_receive({ + mock_irrigation_system.id, + clusters.ValveConfigurationAndControl.server.attributes.CurrentState:build_test_report_data( + mock_irrigation_system, + endpoints.VALVE_1_EP, + 0 + ) + }) + test.socket.capability:__expect_send( + mock_irrigation_system:generate_test_message("main", capabilities.valve.valve.closed()) + ) + end +) + +test.register_coroutine_test( + "Parent device: Current level reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + + test.socket.matter:__queue_receive({ + mock_irrigation_system.id, + clusters.ValveConfigurationAndControl.server.attributes.CurrentLevel:build_test_report_data( + mock_irrigation_system, + endpoints.VALVE_1_EP, + 60 + ) + }) + test.socket.capability:__expect_send( + mock_irrigation_system:generate_test_message("main", capabilities.level.level(60)) + ) + end +) + +test.register_coroutine_test( + "Flow reports should generate correct messages", + function() + update_device_profile() + test.wait_for_events() + + test.socket.matter:__queue_receive({ + mock_irrigation_system.id, + clusters.FlowMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_irrigation_system, 1, 20 * 10) + }) + test.socket.capability:__expect_send( + mock_irrigation_system:generate_test_message( + "main", + capabilities.flowMeasurement.flow({ value = 20.0, unit = "m^3/h" }) + ) + ) + end +) + +test.register_coroutine_test( + "Min and max flow attributes set capability constraint", + function() + update_device_profile() + test.wait_for_events() + + test.socket.matter:__queue_receive({ + mock_irrigation_system.id, + clusters.FlowMeasurement.attributes.MinMeasuredValue:build_test_report_data(mock_irrigation_system, 1, 20) + }) + test.socket.matter:__queue_receive({ + mock_irrigation_system.id, + clusters.FlowMeasurement.attributes.MaxMeasuredValue:build_test_report_data(mock_irrigation_system, 1, 5000) + }) + test.socket.capability:__expect_send( + mock_irrigation_system:generate_test_message( + "main", + capabilities.flowMeasurement.flowRange({ + value = { minimum = 2.0, maximum = 500.0 }, + unit = "m^3/h" + }) + ) + ) + end +) + +test.register_coroutine_test( + "Child device valve 2: Open command should send the appropriate commands", + function() + test.socket.capability:__queue_receive({ + mock_children[endpoints.VALVE_2_EP].id, + { capability = "valve", component = "main", command = "open", args = { } } + }) + test.socket.matter:__expect_send({ + mock_irrigation_system.id, + clusters.ValveConfigurationAndControl.server.commands.Open(mock_irrigation_system, endpoints.VALVE_2_EP) + }) + end +) + +test.register_coroutine_test( + "Child device valve 2: Set level command should send the appropriate commands", + function() + test.socket.capability:__queue_receive({ + mock_children[endpoints.VALVE_2_EP].id, + { capability = "level", component = "main", command = "setLevel", args = { 40 } } + }) + test.socket.matter:__expect_send({ + mock_irrigation_system.id, + clusters.ValveConfigurationAndControl.server.commands.Open(mock_irrigation_system, endpoints.VALVE_2_EP, nil, 40) + }) + end +) + +test.register_coroutine_test( + "Child device valve 2: Current state closed should generate closed event", + function() + test.socket.matter:__queue_receive({ + mock_irrigation_system.id, + clusters.ValveConfigurationAndControl.server.attributes.CurrentState:build_test_report_data( + mock_irrigation_system, + endpoints.VALVE_2_EP, + 0 + ) + }) + test.socket.capability:__expect_send( + mock_children[endpoints.VALVE_2_EP]:generate_test_message("main", capabilities.valve.valve.closed()) + ) + end +) + +test.register_coroutine_test( + "Child device valve 3: Close command should send the appropriate commands", + function() + test.socket.capability:__queue_receive({ + mock_children[endpoints.VALVE_3_EP].id, + { capability = "valve", component = "main", command = "close", args = { } } + }) + test.socket.matter:__expect_send({ + mock_irrigation_system.id, + clusters.ValveConfigurationAndControl.server.commands.Close(mock_irrigation_system, endpoints.VALVE_3_EP) + }) + end +) + +test.register_coroutine_test( + "Child device valve 3: Current level reports should generate appropriate events", + function() + test.socket.matter:__queue_receive({ + mock_irrigation_system.id, + clusters.ValveConfigurationAndControl.server.attributes.CurrentLevel:build_test_report_data( + mock_irrigation_system, + endpoints.VALVE_3_EP, + 100 + ) + }) + test.socket.capability:__expect_send( + mock_children[endpoints.VALVE_3_EP]:generate_test_message("main", capabilities.level.level(100)) + ) + end +) + +test.register_coroutine_test( + "OperationalState attribute running should generate running event", + function() + update_device_profile() + test.wait_for_events() + + test.socket.matter:__queue_receive({ + mock_irrigation_system.id, + clusters.OperationalState.attributes.OperationalState:build_test_report_data( + mock_irrigation_system, + endpoints.IRRIGATION_SYSTEM_EP, + clusters.OperationalState.types.OperationalStateEnum.RUNNING + ) + }) + test.socket.capability:__expect_send( + mock_irrigation_system:generate_test_message("main", capabilities.operationalState.operationalState.running()) + ) + end +) + +test.register_coroutine_test( + "OperationalState OperationalError UNABLE_TO_COMPLETE_OPERATION should generate unableToCompleteOperation event", + function() + update_device_profile() + test.wait_for_events() + + test.socket.matter:__queue_receive({ + mock_irrigation_system.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data( + mock_irrigation_system, + endpoints.IRRIGATION_SYSTEM_EP, { error_state_id = clusters.OperationalState.types.ErrorStateEnum.UNABLE_TO_COMPLETE_OPERATION } + ) + }) + test.socket.capability:__expect_send( + mock_irrigation_system:generate_test_message("main", capabilities.operationalState.operationalState.unableToCompleteOperation()) + ) + end +) + +test.register_coroutine_test( + "OperationalState resume command should send Resume and re-read state/error to irrigation system EP", + function() + update_device_profile() + test.wait_for_events() + + test.socket.capability:__queue_receive({ + mock_irrigation_system.id, + { capability = "operationalState", component = "main", command = "resume", args = { } } + }) + test.socket.matter:__expect_send({ + mock_irrigation_system.id, + clusters.OperationalState.server.commands.Resume(mock_irrigation_system, endpoints.IRRIGATION_SYSTEM_EP) + }) + test.socket.matter:__expect_send({ + mock_irrigation_system.id, + clusters.OperationalState.attributes.OperationalState:read(mock_irrigation_system, endpoints.IRRIGATION_SYSTEM_EP) + }) + test.socket.matter:__expect_send({ + mock_irrigation_system.id, + clusters.OperationalState.attributes.OperationalError:read(mock_irrigation_system, endpoints.IRRIGATION_SYSTEM_EP) + }) + end +) + +test.register_coroutine_test( + "OperationalState pause command should send Pause and re-read state/error to irrigation system EP", + function() + update_device_profile() + test.wait_for_events() + + test.socket.capability:__queue_receive({ + mock_irrigation_system.id, + { capability = "operationalState", component = "main", command = "pause", args = { } } + }) + test.socket.matter:__expect_send({ + mock_irrigation_system.id, + clusters.OperationalState.server.commands.Pause(mock_irrigation_system, endpoints.IRRIGATION_SYSTEM_EP) + }) + test.socket.matter:__expect_send({ + mock_irrigation_system.id, + clusters.OperationalState.attributes.OperationalState:read(mock_irrigation_system, endpoints.IRRIGATION_SYSTEM_EP) + }) + test.socket.matter:__expect_send({ + mock_irrigation_system.id, + clusters.OperationalState.attributes.OperationalError:read(mock_irrigation_system, endpoints.IRRIGATION_SYSTEM_EP) + }) + end +) + +test.register_coroutine_test( + "Child device doConfigure: update child profile based on its endpoint configuration", + function() + test.socket.device_lifecycle:__queue_receive({ mock_children[endpoints.VALVE_2_EP].id, "doConfigure" }) + mock_children[endpoints.VALVE_2_EP]:expect_metadata_update({ + profile = "irrigation-system", + optional_component_capabilities = {{"main", {"level"}}} + }) + mock_children[endpoints.VALVE_2_EP]:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.matter:__expect_send({mock_irrigation_system.id, subscribe_request}) + end +) + +test.run_registered_tests() + From 90c253e0d3ef7f2cc9b25079e28e169498c29efd Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Wed, 6 May 2026 09:32:54 -0500 Subject: [PATCH 340/538] Support Soil Sensor device type (#2757) --- .../matter-sensor/fingerprints.yml | 5 + .../profiles/soil-sensor-battery.yml | 17 ++ .../profiles/soil-sensor-batteryLevel.yml | 17 ++ .../matter-sensor/profiles/soil-sensor.yml | 15 ++ .../temperature-soil-sensor-battery.yml | 21 ++ .../temperature-soil-sensor-batteryLevel.yml | 21 ++ .../profiles/temperature-soil-sensor.yml | 19 ++ .../src/embedded_clusters/Global/init.lua | 5 + .../Global/types/LevelValueEnum.lua | 36 +++ .../types/MeasurementAccuracyRangeStruct.lua | 119 ++++++++++ .../types/MeasurementAccuracyStruct.lua | 99 ++++++++ .../Global/types/MeasurementTypeEnum.lua | 75 ++++++ .../embedded_clusters/Global/types/init.lua | 14 ++ .../SoilMeasurement/init.lua | 46 ++++ .../attributes/SoilMoistureMeasuredValue.lua | 67 ++++++ .../SoilMoistureMeasurementLimits.lua | 67 ++++++ .../server/attributes/init.lua | 19 ++ .../SmartThings/matter-sensor/src/init.lua | 17 +- .../sensor_handlers/attribute_handlers.lua | 37 ++- .../src/sensor_utils/device_configuration.lua | 31 ++- .../sensor_utils/embedded_cluster_utils.lua | 68 +++--- .../matter-sensor/src/sensor_utils/fields.lua | 2 + .../matter-sensor/src/sensor_utils/utils.lua | 4 + .../src/test/test_matter_soil_sensor.lua | 221 ++++++++++++++++++ 24 files changed, 993 insertions(+), 49 deletions(-) create mode 100644 drivers/SmartThings/matter-sensor/profiles/soil-sensor-battery.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/soil-sensor-batteryLevel.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/soil-sensor.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/temperature-soil-sensor-battery.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/temperature-soil-sensor-batteryLevel.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/temperature-soil-sensor.yml create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/LevelValueEnum.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementAccuracyRangeStruct.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementAccuracyStruct.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementTypeEnum.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/server/attributes/SoilMoistureMeasuredValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/server/attributes/SoilMoistureMeasurementLimits.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/test/test_matter_soil_sensor.lua diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index e3b483cad5..483b92fc21 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -350,3 +350,8 @@ matterGeneric: deviceTypes: - id: 0x0306 # Flow Sensor deviceProfileName: flow-battery + - id: "matter/soil/sensor" + deviceLabel: Matter Soil Sensor + deviceTypes: + - id: 0x0045 # Soil Sensor + deviceProfileName: soil-sensor-battery diff --git a/drivers/SmartThings/matter-sensor/profiles/soil-sensor-battery.yml b/drivers/SmartThings/matter-sensor/profiles/soil-sensor-battery.yml new file mode 100644 index 0000000000..07c9162e4d --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/soil-sensor-battery.yml @@ -0,0 +1,17 @@ +name: soil-sensor-battery +components: +- id: main + capabilities: + - id: relativeHumidityMeasurement + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: HumiditySensor +preferences: + - preferenceId: humidityOffset + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/soil-sensor-batteryLevel.yml b/drivers/SmartThings/matter-sensor/profiles/soil-sensor-batteryLevel.yml new file mode 100644 index 0000000000..cfda9b9e95 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/soil-sensor-batteryLevel.yml @@ -0,0 +1,17 @@ +name: soil-sensor-batteryLevel +components: +- id: main + capabilities: + - id: relativeHumidityMeasurement + version: 1 + - id: batteryLevel + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: HumiditySensor +preferences: + - preferenceId: humidityOffset + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/soil-sensor.yml b/drivers/SmartThings/matter-sensor/profiles/soil-sensor.yml new file mode 100644 index 0000000000..280edc80c4 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/soil-sensor.yml @@ -0,0 +1,15 @@ +name: soil-sensor +components: +- id: main + capabilities: + - id: relativeHumidityMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: HumiditySensor +preferences: + - preferenceId: humidityOffset + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/temperature-soil-sensor-battery.yml b/drivers/SmartThings/matter-sensor/profiles/temperature-soil-sensor-battery.yml new file mode 100644 index 0000000000..73db53e7c1 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/temperature-soil-sensor-battery.yml @@ -0,0 +1,21 @@ +name: temperature-soil-sensor-battery +components: +- id: main + capabilities: + - id: relativeHumidityMeasurement + version: 1 + - id: temperatureMeasurement + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: HumiditySensor +preferences: + - preferenceId: tempOffset + explicit: true + - preferenceId: humidityOffset + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/temperature-soil-sensor-batteryLevel.yml b/drivers/SmartThings/matter-sensor/profiles/temperature-soil-sensor-batteryLevel.yml new file mode 100644 index 0000000000..55ccc8fb25 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/temperature-soil-sensor-batteryLevel.yml @@ -0,0 +1,21 @@ +name: temperature-soil-sensor-batteryLevel +components: +- id: main + capabilities: + - id: relativeHumidityMeasurement + version: 1 + - id: temperatureMeasurement + version: 1 + - id: batteryLevel + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: HumiditySensor +preferences: + - preferenceId: tempOffset + explicit: true + - preferenceId: humidityOffset + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/temperature-soil-sensor.yml b/drivers/SmartThings/matter-sensor/profiles/temperature-soil-sensor.yml new file mode 100644 index 0000000000..bc327f01b0 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/temperature-soil-sensor.yml @@ -0,0 +1,19 @@ +name: temperature-soil-sensor +components: +- id: main + capabilities: + - id: relativeHumidityMeasurement + version: 1 + - id: temperatureMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: HumiditySensor +preferences: + - preferenceId: tempOffset + explicit: true + - preferenceId: humidityOffset + explicit: true diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/init.lua new file mode 100644 index 0000000000..98c23abef4 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/init.lua @@ -0,0 +1,5 @@ +local GlobalTypes = require "embedded_clusters.Global.types" + +local Global = {} +Global.types = GlobalTypes +return Global diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/LevelValueEnum.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/LevelValueEnum.lua new file mode 100644 index 0000000000..ed8b969b49 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/LevelValueEnum.lua @@ -0,0 +1,36 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local LevelValueEnum = {} +local new_mt = UintABC.new_mt({NAME = "Uint8", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.UNKNOWN] = "UNKNOWN", + [self.LOW] = "LOW", + [self.MEDIUM] = "MEDIUM", + [self.HIGH] = "HIGH", + [self.CRITICAL] = "CRITICAL", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.UNKNOWN = 0x00 +new_mt.__index.LOW = 0x01 +new_mt.__index.MEDIUM = 0x02 +new_mt.__index.HIGH = 0x03 +new_mt.__index.CRITICAL = 0x04 + +LevelValueEnum.UNKNOWN = 0x00 +LevelValueEnum.LOW = 0x01 +LevelValueEnum.MEDIUM = 0x02 +LevelValueEnum.HIGH = 0x03 +LevelValueEnum.CRITICAL = 0x04 + +LevelValueEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(LevelValueEnum, new_mt) + +return LevelValueEnum diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementAccuracyRangeStruct.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementAccuracyRangeStruct.lua new file mode 100644 index 0000000000..b298a66961 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementAccuracyRangeStruct.lua @@ -0,0 +1,119 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local MeasurementAccuracyRangeStruct = {} +local new_mt = StructureABC.new_mt({NAME = "MeasurementAccuracyRangeStruct", ID = data_types.name_to_id_map["Structure"]}) + +MeasurementAccuracyRangeStruct.field_defs = { + { + name = "range_min", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Int64", + }, + { + name = "range_max", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Int64", + }, + { + name = "percent_max", + field_id = 2, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint16", + }, + { + name = "percent_min", + field_id = 3, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint16", + }, + { + name = "percent_typical", + field_id = 4, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint16", + }, + { + name = "fixed_max", + field_id = 5, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint64", + }, + { + name = "fixed_min", + field_id = 6, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint64", + }, + { + name = "fixed_typical", + field_id = 7, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint64", + }, +} + +MeasurementAccuracyRangeStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for _idx, field_def in ipairs(cls.field_defs) do + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif not (field_def.is_optional and tbl[field_def.name] == nil) then + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +MeasurementAccuracyRangeStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = MeasurementAccuracyRangeStruct.init +new_mt.__index.serialize = MeasurementAccuracyRangeStruct.serialize + +MeasurementAccuracyRangeStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + 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 + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(MeasurementAccuracyRangeStruct, new_mt) + +return MeasurementAccuracyRangeStruct diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementAccuracyStruct.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementAccuracyStruct.lua new file mode 100644 index 0000000000..4da8857164 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementAccuracyStruct.lua @@ -0,0 +1,99 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local MeasurementAccuracyStruct = {} +local new_mt = StructureABC.new_mt({NAME = "MeasurementAccuracyStruct", ID = data_types.name_to_id_map["Structure"]}) + +MeasurementAccuracyStruct.field_defs = { + { + name = "measurement_type", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "embedded_clusters.Global.types.MeasurementTypeEnum", + }, + { + name = "measured", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Boolean", + }, + { + name = "min_measured_value", + field_id = 2, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Int64", + }, + { + name = "max_measured_value", + field_id = 3, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Int64", + }, + { + name = "accuracy_ranges", + field_id = 4, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Array", + element_type = require "embedded_clusters.Global.types.MeasurementAccuracyRangeStruct", + }, +} + +MeasurementAccuracyStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for _idx, field_def in ipairs(cls.field_defs) do + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif not (field_def.is_optional and tbl[field_def.name] == nil) then + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +MeasurementAccuracyStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = MeasurementAccuracyStruct.init +new_mt.__index.serialize = MeasurementAccuracyStruct.serialize + +MeasurementAccuracyStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + 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 + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(MeasurementAccuracyStruct, new_mt) + +return MeasurementAccuracyStruct diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementTypeEnum.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementTypeEnum.lua new file mode 100644 index 0000000000..2e749ebacd --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementTypeEnum.lua @@ -0,0 +1,75 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local MeasurementTypeEnum = {} +local new_mt = UintABC.new_mt({NAME = "MeasurementTypeEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.UNSPECIFIED] = "UNSPECIFIED", + [self.VOLTAGE] = "VOLTAGE", + [self.ACTIVE_CURRENT] = "ACTIVE_CURRENT", + [self.REACTIVE_CURRENT] = "REACTIVE_CURRENT", + [self.APPARENT_CURRENT] = "APPARENT_CURRENT", + [self.ACTIVE_POWER] = "ACTIVE_POWER", + [self.REACTIVE_POWER] = "REACTIVE_POWER", + [self.APPARENT_POWER] = "APPARENT_POWER", + [self.RMS_VOLTAGE] = "RMS_VOLTAGE", + [self.RMS_CURRENT] = "RMS_CURRENT", + [self.RMS_POWER] = "RMS_POWER", + [self.FREQUENCY] = "FREQUENCY", + [self.POWER_FACTOR] = "POWER_FACTOR", + [self.NEUTRAL_CURRENT] = "NEUTRAL_CURRENT", + [self.ELECTRICAL_ENERGY] = "ELECTRICAL_ENERGY", + [self.REACTIVE_ENERGY] = "REACTIVE_ENERGY", + [self.APPARENT_ENERGY] = "APPARENT_ENERGY", + [self.SOIL_MOISTURE] = "SOIL_MOISTURE", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.UNSPECIFIED = 0x00 +new_mt.__index.VOLTAGE = 0x01 +new_mt.__index.ACTIVE_CURRENT = 0x02 +new_mt.__index.REACTIVE_CURRENT = 0x03 +new_mt.__index.APPARENT_CURRENT = 0x04 +new_mt.__index.ACTIVE_POWER = 0x05 +new_mt.__index.REACTIVE_POWER = 0x06 +new_mt.__index.APPARENT_POWER = 0x07 +new_mt.__index.RMS_VOLTAGE = 0x08 +new_mt.__index.RMS_CURRENT = 0x09 +new_mt.__index.RMS_POWER = 0x0A +new_mt.__index.FREQUENCY = 0x0B +new_mt.__index.POWER_FACTOR = 0x0C +new_mt.__index.NEUTRAL_CURRENT = 0x0D +new_mt.__index.ELECTRICAL_ENERGY = 0x0E +new_mt.__index.REACTIVE_ENERGY = 0x0F +new_mt.__index.APPARENT_ENERGY = 0x10 +new_mt.__index.SOIL_MOISTURE = 0x11 + +MeasurementTypeEnum.UNSPECIFIED = 0x00 +MeasurementTypeEnum.VOLTAGE = 0x01 +MeasurementTypeEnum.ACTIVE_CURRENT = 0x02 +MeasurementTypeEnum.REACTIVE_CURRENT = 0x03 +MeasurementTypeEnum.APPARENT_CURRENT = 0x04 +MeasurementTypeEnum.ACTIVE_POWER = 0x05 +MeasurementTypeEnum.REACTIVE_POWER = 0x06 +MeasurementTypeEnum.APPARENT_POWER = 0x07 +MeasurementTypeEnum.RMS_VOLTAGE = 0x08 +MeasurementTypeEnum.RMS_CURRENT = 0x09 +MeasurementTypeEnum.RMS_POWER = 0x0A +MeasurementTypeEnum.FREQUENCY = 0x0B +MeasurementTypeEnum.POWER_FACTOR = 0x0C +MeasurementTypeEnum.NEUTRAL_CURRENT = 0x0D +MeasurementTypeEnum.ELECTRICAL_ENERGY = 0x0E +MeasurementTypeEnum.REACTIVE_ENERGY = 0x0F +MeasurementTypeEnum.APPARENT_ENERGY = 0x10 +MeasurementTypeEnum.SOIL_MOISTURE = 0x11 + +MeasurementTypeEnum.augment_type = function(_cls, val) + setmetatable(val, new_mt) +end + +setmetatable(MeasurementTypeEnum, new_mt) + +return MeasurementTypeEnum diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/init.lua new file mode 100644 index 0000000000..984c5e02d8 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/init.lua @@ -0,0 +1,14 @@ +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.Global.types." .. key) + end + return types_mt.__types_cache[key] +end + +local GlobalTypes = {} + +setmetatable(GlobalTypes, types_mt) + +return GlobalTypes diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/init.lua new file mode 100644 index 0000000000..a7db91daea --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/init.lua @@ -0,0 +1,46 @@ +local cluster_base = require "st.matter.cluster_base" +local SoilMeasurementServerAttributes = require "embedded_clusters.SoilMeasurement.server.attributes" + +local SoilMeasurement = {} + +SoilMeasurement.ID = 0x0430 +SoilMeasurement.NAME = "SoilMeasurement" +SoilMeasurement.server = {} +SoilMeasurement.client = {} +SoilMeasurement.server.attributes = SoilMeasurementServerAttributes:set_parent_cluster(SoilMeasurement) + +function SoilMeasurement:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "SoilMoistureMeasurementLimits", + [0x0001] = "SoilMoistureMeasuredValue", + [0xFFF9] = "AcceptedCommandList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +SoilMeasurement.attribute_direction_map = { + ["SoilMoistureMeasurementLimits"] = "server", + ["SoilMoistureMeasuredValue"] = "server", + ["AcceptedCommandList"] = "server", + ["AttributeList"] = "server", +} + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = SoilMeasurement.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, SoilMeasurement.NAME)) + end + return SoilMeasurement[direction].attributes[key] +end +SoilMeasurement.attributes = {} +setmetatable(SoilMeasurement.attributes, attribute_helper_mt) + +setmetatable(SoilMeasurement, {__index = cluster_base}) + +return SoilMeasurement diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/server/attributes/SoilMoistureMeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/server/attributes/SoilMoistureMeasuredValue.lua new file mode 100644 index 0000000000..5df92ec62a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/server/attributes/SoilMoistureMeasuredValue.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SoilMoistureMeasuredValue = { + ID = 0x0001, + NAME = "SoilMoistureMeasuredValue", + base_type = require "st.matter.data_types.Uint8", +} + +function SoilMoistureMeasuredValue:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function SoilMoistureMeasuredValue:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function SoilMoistureMeasuredValue:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function SoilMoistureMeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SoilMoistureMeasuredValue: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 SoilMoistureMeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(SoilMoistureMeasuredValue, {__call = SoilMoistureMeasuredValue.new_value, __index = SoilMoistureMeasuredValue.base_type}) +return SoilMoistureMeasuredValue diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/server/attributes/SoilMoistureMeasurementLimits.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/server/attributes/SoilMoistureMeasurementLimits.lua new file mode 100644 index 0000000000..6fbf1a8c8a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/server/attributes/SoilMoistureMeasurementLimits.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SoilMoistureMeasurementLimits = { + ID = 0x0000, + NAME = "SoilMoistureMeasurementLimits", + base_type = require "embedded_clusters.Global.types.MeasurementAccuracyStruct", +} + +function SoilMoistureMeasurementLimits:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function SoilMoistureMeasurementLimits:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function SoilMoistureMeasurementLimits:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function SoilMoistureMeasurementLimits:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SoilMoistureMeasurementLimits: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 SoilMoistureMeasurementLimits:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(SoilMoistureMeasurementLimits, {__call = SoilMoistureMeasurementLimits.new_value, __index = SoilMoistureMeasurementLimits.base_type}) +return SoilMoistureMeasurementLimits diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/server/attributes/init.lua new file mode 100644 index 0000000000..3741efd72a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SoilMeasurement/server/attributes/init.lua @@ -0,0 +1,19 @@ +local attr_mt = {} +attr_mt.__index = function(self, key) + local req_loc = string.format("embedded_clusters.SoilMeasurement.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 SoilMeasurementServerAttributes = {} + +function SoilMeasurementServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(SoilMeasurementServerAttributes, attr_mt) + +return SoilMeasurementServerAttributes diff --git a/drivers/SmartThings/matter-sensor/src/init.lua b/drivers/SmartThings/matter-sensor/src/init.lua index 297493c652..6dbd6d9811 100644 --- a/drivers/SmartThings/matter-sensor/src/init.lua +++ b/drivers/SmartThings/matter-sensor/src/init.lua @@ -37,6 +37,12 @@ if version.api < 11 then clusters.BooleanStateConfiguration = require "embedded_clusters.BooleanStateConfiguration" end +-- Include driver-side definitions when lua libs api version is < 21 +-- TODO: change this to < 20 once the lua libs have been updated for hub-core 61 +if version.api < 21 then + clusters.SoilMeasurement = require "embedded_clusters.SoilMeasurement" +end + local SensorLifecycleHandlers = {} function SensorLifecycleHandlers.do_configure(driver, device) @@ -117,6 +123,10 @@ local matter_driver_template = { [clusters.RelativeHumidityMeasurement.ID] = { [clusters.RelativeHumidityMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.humidity_measured_value_handler }, + [clusters.SoilMeasurement.ID] = { + [clusters.SoilMeasurement.attributes.SoilMoistureMeasuredValue.ID] = attribute_handlers.soil_moisture_measured_value_handler, + [clusters.SoilMeasurement.attributes.SoilMoistureMeasurementLimits.ID] = attribute_handlers.soil_moisture_measurement_limits_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), @@ -164,7 +174,9 @@ local matter_driver_template = { clusters.BooleanState.attributes.StateValue, }, [capabilities.relativeHumidityMeasurement.ID] = { - clusters.RelativeHumidityMeasurement.attributes.MeasuredValue + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue, + clusters.SoilMeasurement.attributes.SoilMoistureMeasuredValue, + clusters.SoilMeasurement.attributes.SoilMoistureMeasurementLimits }, [capabilities.temperatureAlarm.ID] = { clusters.BooleanState.attributes.StateValue, @@ -243,9 +255,6 @@ local matter_driver_template = { clusters.RadonConcentrationMeasurement.attributes.MeasuredValue, clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit, }, - [capabilities.relativeHumidityMeasurement.ID] = { - clusters.RelativeHumidityMeasurement.attributes.MeasuredValue - }, [capabilities.tvocHealthConcern.ID] = { clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue }, diff --git a/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua index 9bba480855..a81a03b4a6 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua @@ -9,6 +9,10 @@ local fields = require "sensor_utils.fields" local device_cfg = require "sensor_utils.device_configuration" local version = require "version" +if version.api < 13 then + clusters.Global = require "embedded_clusters.Global" +end + local AttributeHandlers = {} @@ -69,16 +73,39 @@ function AttributeHandlers.humidity_measured_value_handler(driver, device, ib, r end +-- [[ SOIL MEASUREMENT CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.soil_moisture_measured_value_handler(driver, device, ib, response) + if ib.data.value == nil then return end + local min = sensor_utils.get_field_for_endpoint(device, fields.SOIL_LIMIT_MIN, ib.endpoint_id) or sensor_utils.SOIL_MOISTURE_MIN + local max = sensor_utils.get_field_for_endpoint(device, fields.SOIL_LIMIT_MAX, ib.endpoint_id) or sensor_utils.SOIL_MOISTURE_MAX + local soil_moisture = st_utils.clamp_value(ib.data.value, min, max) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.relativeHumidityMeasurement.humidity(soil_moisture)) +end + +function AttributeHandlers.soil_moisture_measurement_limits_handler(driver, device, ib, response) + local MeasurementAccuracyStruct = require "embedded_clusters.Global.types.MeasurementAccuracyStruct" + MeasurementAccuracyStruct:augment_type(ib.data) + local min_val = ib.data.elements and ib.data.elements.min_measured_value and ib.data.elements.min_measured_value.value + local max_val = ib.data.elements and ib.data.elements.max_measured_value and ib.data.elements.max_measured_value.value + if not (min_val and max_val) or (min_val >= max_val) or (min_val < sensor_utils.SOIL_MOISTURE_MIN) or (max_val > sensor_utils.SOIL_MOISTURE_MAX) then + device.log.warn_with({hub_logs = true}, string.format("Device reported invalid soil moisture limits: min=%d, max=%d", min_val, max_val)) + end + sensor_utils.set_field_for_endpoint(device, fields.SOIL_LIMIT_MIN, ib.endpoint_id, min_val) + sensor_utils.set_field_for_endpoint(device, fields.SOIL_LIMIT_MAX, ib.endpoint_id, max_val) +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 + 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]) diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/device_configuration.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/device_configuration.lua index ea56ab710e..4690a3e356 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/device_configuration.lua @@ -11,18 +11,22 @@ if version.api < 11 then clusters.BooleanStateConfiguration = require "embedded_clusters.BooleanStateConfiguration" end +if version.api < 21 then + clusters.SoilMeasurement = require "embedded_clusters.SoilMeasurement" +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 + 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 @@ -53,12 +57,18 @@ function DeviceConfiguration.match_profile(driver, device, battery_supported) profile_name = profile_name .. "-illuminance" end - if device:supports_capability(capabilities.temperatureMeasurement) then + if device:supports_capability(capabilities.temperatureMeasurement) or + #device:get_endpoints(clusters.TemperatureMeasurement.ID) > 0 then profile_name = profile_name .. "-temperature" end if device:supports_capability(capabilities.relativeHumidityMeasurement) then - profile_name = profile_name .. "-humidity" + if #embedded_cluster_utils.get_endpoints(device, clusters.SoilMeasurement.ID) > 0 then + -- TODO: Update soil sensor profiles to use the SoilSensor category once it is available. + profile_name = profile_name .. "-soil-sensor" + else + profile_name = profile_name .. "-humidity" + end end if device:supports_capability(capabilities.atmosphericPressureMeasurement) then @@ -117,8 +127,7 @@ function DeviceConfiguration.match_profile(driver, device, battery_supported) -- 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 +return DeviceConfiguration diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/embedded_cluster_utils.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/embedded_cluster_utils.lua index e2384098d9..37623d57d2 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_utils/embedded_cluster_utils.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/embedded_cluster_utils.lua @@ -25,6 +25,10 @@ if version.api < 11 then clusters.BooleanStateConfiguration = require "embedded_clusters.BooleanStateConfiguration" end +if version.api < 21 then + clusters.SoilMeasurement = require "embedded_clusters.SoilMeasurement" +end + local embedded_cluster_utils = {} local embedded_clusters_api_10 = { @@ -46,38 +50,44 @@ local embedded_clusters_api_11 = { [clusters.BooleanStateConfiguration.ID] = clusters.BooleanStateConfiguration } +local embedded_clusters_api_21 = { + [clusters.SoilMeasurement.ID] = clusters.SoilMeasurement +} + function embedded_cluster_utils.get_endpoints(device, cluster_id, opts) - -- If using older lua libs and need to check for an embedded cluster feature, - -- we must use the embedded cluster definitions here - if version.api < 10 and embedded_clusters_api_10[cluster_id] ~= nil or - version.api < 11 and embedded_clusters_api_11[cluster_id] ~= nil then - local embedded_cluster = embedded_clusters_api_10[cluster_id] or embedded_clusters_api_11[cluster_id] - local opts = opts or {} - if utils.table_size(opts) > 1 then - device.log.warn_with({hub_logs = true}, "Invalid options for get_endpoints") - return - end - local clus_has_features = function(clus, feature_bitmap) - if not feature_bitmap or not clus then return false end - return embedded_cluster.are_features_supported(feature_bitmap, clus.feature_map) - end - local eps = {} - for _, ep in ipairs(device.endpoints) do - for _, clus in ipairs(ep.clusters) do - if ((clus.cluster_id == cluster_id) - and (opts.feature_bitmap == nil or clus_has_features(clus, opts.feature_bitmap)) - and ((opts.cluster_type == nil and clus.cluster_type == "SERVER" or clus.cluster_type == "BOTH") - or (opts.cluster_type == clus.cluster_type)) - or (cluster_id == nil)) then - table.insert(eps, ep.endpoint_id) - if cluster_id == nil then break end - end + -- If using older lua libs and need to check for an embedded cluster feature, + -- we must use the embedded cluster definitions here + if version.api < 10 and embedded_clusters_api_10[cluster_id] ~= nil or + version.api < 11 and embedded_clusters_api_11[cluster_id] ~= nil or + version.api < 21 and embedded_clusters_api_21[cluster_id] ~= nil then + local embedded_cluster = embedded_clusters_api_10[cluster_id] or embedded_clusters_api_11[cluster_id] or + embedded_clusters_api_21[cluster_id] + local opts = opts or {} + if utils.table_size(opts) > 1 then + device.log.warn_with({hub_logs = true}, "Invalid options for get_endpoints") + return + end + local clus_has_features = function(clus, feature_bitmap) + if not feature_bitmap or not clus then return false end + return embedded_cluster.are_features_supported(feature_bitmap, clus.feature_map) + end + local eps = {} + for _, ep in ipairs(device.endpoints) do + for _, clus in ipairs(ep.clusters) do + if ((clus.cluster_id == cluster_id) + and (opts.feature_bitmap == nil or clus_has_features(clus, opts.feature_bitmap)) + and ((opts.cluster_type == nil and clus.cluster_type == "SERVER" or clus.cluster_type == "BOTH") + or (opts.cluster_type == clus.cluster_type)) + or (cluster_id == nil)) then + table.insert(eps, ep.endpoint_id) + if cluster_id == nil then break end end end - return eps - else - return device:get_endpoints(cluster_id, opts) end + return eps + else + return device:get_endpoints(cluster_id, opts) end +end - return embedded_cluster_utils \ No newline at end of file +return 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 index f0b2a5c7a6..b31b1b5c5b 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua @@ -11,6 +11,8 @@ SensorFields.TEMP_MAX = "__temp_max" SensorFields.FLOW_BOUND_RECEIVED = "__flow_bound_received" SensorFields.FLOW_MIN = "__flow_min" SensorFields.FLOW_MAX = "__flow_max" +SensorFields.SOIL_LIMIT_MIN = "__soil_limit_min" +SensorFields.SOIL_LIMIT_MAX = "__soil_limit_max" SensorFields.battery_support = { NO_BATTERY = "NO_BATTERY", diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua index 5a0421fb0c..d5437410a5 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua @@ -3,6 +3,10 @@ local utils = {} +-- Sanity check bounds for soil moisture measurement limits (percent) +utils.SOIL_MOISTURE_MIN = 0 +utils.SOIL_MOISTURE_MAX = 100 + function utils.get_field_for_endpoint(device, field, endpoint) return device:get_field(string.format("%s_%d", field, endpoint)) end diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_soil_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_soil_sensor.lua new file mode 100644 index 0000000000..0c845fa47e --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_soil_sensor.lua @@ -0,0 +1,221 @@ +-- Copyright © 2026 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 version = require "version" + +clusters.Global = require "embedded_clusters.Global" + +if version.api < 21 then + clusters.SoilMeasurement = require "embedded_clusters.SoilMeasurement" +end + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("temperature-soil-sensor.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.SoilMeasurement.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0045, device_type_revision = 1 } -- Soil Sensor + } + }, + } +}) + +local subscribe_request + +local cluster_subscribe_list = { + clusters.SoilMeasurement.attributes.SoilMoistureMeasuredValue, + clusters.SoilMeasurement.attributes.SoilMoistureMeasurementLimits, + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue +} + +local function test_init() + 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.disable_startup_messages() + 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" }) + 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 = "temperature-soil-sensor" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Test infoChanged lifecycle event", + function() + local updated_device_profile = t_utils.get_profile_definition("temperature-soil-sensor.yml") + 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.register_coroutine_test( + "Relative humidity reports should generate correct messages", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.RelativeHumidityMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 4049) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 40 })) + ) + + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.RelativeHumidityMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 4050) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 41 })) + ) + end +) + +test.register_coroutine_test( + "Temperature reports should generate correct messages", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.TemperatureMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 40*100) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) + ) + end +) + +test.register_coroutine_test( + "Min and max temperature attributes set capability constraint", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue:build_test_report_data(mock_device, 1, 500) + } + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue:build_test_report_data(mock_device, 1, 4000) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 5.00, maximum = 40.00 }, unit = "C" }) + ) + ) + end +) + +test.register_coroutine_test( + "Soil moisture is reported raw when no limits are set", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.SoilMeasurement.attributes.SoilMoistureMeasuredValue:build_test_report_data(mock_device, 1, 55) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 55 })) + ) + end +) + +local function build_soil_moisture_limits(min_value, max_value) + return clusters.Global.types.MeasurementAccuracyStruct({ + measurement_type = clusters.Global.types.MeasurementTypeEnum.SOIL_MOISTURE, + measured = true, + min_measured_value = min_value, + max_measured_value = max_value, + accuracy_ranges = {clusters.Global.types.MeasurementAccuracyRangeStruct({range_min = min_value, range_max = max_value})} + }) +end + +test.register_coroutine_test( + "Soil moisture is scaled 0-100% when min and max limits are set", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.SoilMeasurement.attributes.SoilMoistureMeasurementLimits:build_test_report_data(mock_device, 1, build_soil_moisture_limits(0, 50)) + } + ) + -- Receive a measured value of 25, which is 50% when scaled between 0 and 50 + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.SoilMeasurement.attributes.SoilMoistureMeasuredValue:build_test_report_data(mock_device, 1, 25) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 25 })) + ) + end +) + +test.register_coroutine_test( + "Soil moisture scaling rounds correctly", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.SoilMeasurement.attributes.SoilMoistureMeasurementLimits:build_test_report_data(mock_device, 1, build_soil_moisture_limits(10, 90)) + } + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.SoilMeasurement.attributes.SoilMoistureMeasuredValue:build_test_report_data(mock_device, 1, 10) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 10 })) + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.SoilMeasurement.attributes.SoilMoistureMeasuredValue:build_test_report_data(mock_device, 1, 90) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 90 })) + ) + end +) + +test.run_registered_tests() From ba2b1e3a2cd430231bb3daacf06ee88d2e8270d4 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu, 7 May 2026 14:50:58 -0500 Subject: [PATCH 341/538] Matter Switch: Version gate the extra init event on added (#2938) --- .../SmartThings/matter-switch/src/init.lua | 7 +- .../test/test_aqara_climate_sensor_w100.lua | 1 - .../src/test/test_aqara_light_switch_h2.lua | 3 - .../src/test/test_electrical_sensor_set.lua | 72 - .../src/test/test_electrical_sensor_tree.lua | 68 - .../src/test/test_eve_energy.lua | 33 +- .../test/test_light_illuminance_motion.lua | 564 +------- .../src/test/test_matter_button.lua | 361 ++--- .../test/test_matter_irrigation_system.lua | 2 - .../src/test/test_matter_light_fan.lua | 3 - .../src/test/test_matter_multi_button.lua | 7 - .../test/test_matter_multi_button_motion.lua | 2 - .../test_matter_multi_button_switch_mcd.lua | 4 - ...est_matter_on_off_device_configuration.lua | 3 - .../test_matter_sensor_offset_preferences.lua | 45 +- .../src/test/test_matter_switch.lua | 1215 ++++++++--------- .../src/test/test_matter_water_valve.lua | 64 +- .../src/test/test_multi_switch_mcd.lua | 6 - 18 files changed, 813 insertions(+), 1647 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index d560013a17..dea56b4a21 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -38,8 +38,11 @@ function SwitchLifecycleHandlers.device_added(driver, device) device:send(clusters.OnOff.attributes.OnOff:read(device)) end - -- call device init in case init is not called after added due to device caching - SwitchLifecycleHandlers.device_init(driver, device) + -- The device init event is guaranteed in FW versions 58+, so this is only needed for older hubs + if version.rpc < 10 then + -- call device init in case init is not called after added due to device caching + SwitchLifecycleHandlers.device_init(driver, device) + end end function SwitchLifecycleHandlers.do_configure(driver, device) 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 305c7e682e..1e23ffff4a 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 @@ -130,7 +130,6 @@ local function test_init() end test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "added" }) - test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "init" }) test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) 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 667e68ec16..345afd08ce 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 @@ -181,15 +181,12 @@ local function test_init() -- Test added -> doConfigure logic test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "added" }) - test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "init" }) test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "doConfigure" }) configure_buttons() aqara_mock_device:expect_metadata_update({ profile = "4-button" }) aqara_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - -- to test powerConsumptionReport - test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") for _, child in pairs(aqara_mock_children) do test.mock_device.add_test_device(child) 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 index 180604431b..2c53a14ada 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -181,8 +181,6 @@ local function test_init() 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 }) test.socket.matter:__expect_send({ mock_device.id, subscribe_request }) end test.set_test_init_function(test_init) @@ -195,79 +193,9 @@ local function test_init_periodic() subscribe_request:merge(cluster:subscribe(mock_device_periodic)) end end - 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 }) test.socket.matter:__expect_send({ mock_device_periodic.id, subscribe_request }) end -test.register_message_test( - "On command should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "switch", component = "main", command = "on", args = { } } - } - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "on" } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.On(mock_device, 2) - } - } - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Off command should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "switch", component = "main", command = "off", args = { } } - } - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "off" } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.Off(mock_device, 2) - } - } - }, - { - min_api_version = 17 - } -) - test.register_message_test( "Active power measurement should generate correct messages", { diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua index f618389ba6..d691f2af27 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua @@ -128,78 +128,10 @@ local function test_init() 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 }) test.socket.matter:__expect_send({ mock_device.id, subscribe_request }) end test.set_test_init_function(test_init) -test.register_message_test( - "On command should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "switch", component = "main", command = "on", args = { } } - } - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "on" } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.On(mock_device, 2) - } - } - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Off command should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "switch", component = "main", command = "off", args = { } } - } - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "off" } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.Off(mock_device, 2) - } - } - }, - { - min_api_version = 17 - } -) - test.register_message_test( "Active power measurement should generate correct messages", { 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 5fa58ce8d3..1be1e16980 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -54,7 +54,7 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) -local mock_device_electrical_sensor = test.mock_device.build_test_matter_device({ +local mock_eve_device_using_electrical_sensor = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("plug-energy-powerConsumption.yml"), manufacturer_info = { vendor_id = 0x130A, @@ -113,30 +113,6 @@ local mock_device_electrical_sensor = test.mock_device.build_test_matter_device( } }) -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, @@ -523,7 +499,7 @@ local cumulative_report_val_39 = { test.register_coroutine_test( "Cumulative Energy measurement should generate correct messages", function() - local mock_device = mock_device_electrical_sensor + mock_device = mock_eve_device_using_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( @@ -579,7 +555,10 @@ test.register_coroutine_test( ) end, { - test_init = test_init_electrical_sensor, + test_init = function() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_eve_device_using_electrical_sensor) + end, min_api_version = 17 } ) 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 02feaa245b..b22498eaf7 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 @@ -4,12 +4,8 @@ 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 -local OPTIONS_MASK = 0x01 -local HANDLE_COMMAND_IF_OFF = 0x01 local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("light-color-level-illuminance-motion.yml"), @@ -40,7 +36,7 @@ local mock_device = test.mock_device.build_test_matter_device({ {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER"} }, device_types = { - {device_type_id = 0x010C, device_type_revision = 1} -- ColorTemperatureLight + {device_type_id = 0x010D, device_type_revision = 1} -- Extended Color Light } }, { @@ -64,539 +60,57 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) -local function set_color_mode(device, endpoint, color_mode) - test.socket.matter:__queue_receive({ - device.id, - clusters.ColorControl.attributes.ColorMode:build_test_report_data( - device, endpoint, color_mode) - }) - local read_req - if color_mode == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then - read_req = clusters.ColorControl.attributes.CurrentHue:read() - read_req:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) - else -- color_mode = clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY - read_req = clusters.ColorControl.attributes.CurrentX:read() - read_req:merge(clusters.ColorControl.attributes.CurrentY:read()) - end - test.socket.matter:__expect_send({device.id, read_req}) -end - -local cluster_subscribe_list = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel, - clusters.ColorControl.attributes.CurrentHue, - clusters.ColorControl.attributes.CurrentSaturation, - clusters.ColorControl.attributes.CurrentX, - clusters.ColorControl.attributes.CurrentY, - clusters.ColorControl.attributes.ColorMode, - clusters.ColorControl.attributes.ColorTemperatureMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, - clusters.IlluminanceMeasurement.attributes.MeasuredValue, - clusters.OccupancySensing.attributes.Occupancy -} - -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 - local function test_init() - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - - -- the following subscribe is due to the init event sent by the test framework. - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.disable_startup_messages() test.mock_device.add_test_device(mock_device) - set_color_mode(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) end test.set_test_init_function(test_init) -local function test_init_x_y_color_mode() - 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}) - test.mock_device.add_test_device(mock_device) - set_color_mode(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) -end - -test.register_message_test( - "On command should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "switch", component = "main", command = "on", args = { } } - } - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "on" } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.On(mock_device, 1) - } - } - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Off command should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "switch", component = "main", command = "off", args = { } } - } - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "off" } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.Off(mock_device, 1) - } - } - }, - { - min_api_version = 17 - } -) - -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, 1, 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, 1) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.LevelControl.attributes.CurrentLevel:build_test_report_data(mock_device, 1, 50) - } - }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.switchLevel.level(20)) - }, - { - channel = "devices", - direction = "send", - message = { - "register_native_capability_attr_handler", - { device_uuid = mock_device.id, capability_id = "switchLevel", capability_attr_id = "level" } - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, 1, 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" } - } - }, - }, - { - min_api_version = 17 - } -) - -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, 1, 50) - } - }, - { - channel = "capability", - direction = "send", - message = mock_device: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" } - } - }, - }, - { - min_api_version = 17 - } -) - -local hue = math.floor((50 * 0xFE) / 100.0 + 0.5) -local sat = math.floor((50 * 0xFE) / 100.0 + 0.5) - -test.register_message_test( - "Set color command should send huesat commands when supported", - { - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.attributes.ColorCapabilities:build_test_report_data(mock_device, 1, 0x01) - } - }, - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "colorControl", component = "main", command = "setColor", args = { { hue = 50, saturation = 50 } } } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToHueAndSaturation(mock_device, 1, hue, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToHueAndSaturation:build_test_command_response(mock_device, 1) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, hue) - } - }, - { - channel = "capability", - 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", - message = { - mock_device.id, - clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, sat) - } - }, - { - 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" } - } - }, - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Set Hue command should send MoveToHue", - { - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "colorControl", component = "main", command = "setHue", args = { 50 } } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToHue(mock_device, 1, hue, 0, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) - } - }, - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Set Saturation command should send MoveToSaturation", - { - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "colorControl", component = "main", command = "setSaturation", args = { 50 } } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToSaturation(mock_device, 1, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) - } - }, - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Set color temperature should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {1800} } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 556, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature:build_test_command_response(mock_device, 1) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 556) - } - }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) - }, - }, - { - min_api_version = 17 - } -) +local switch_fields = require "switch_utils.fields" test.register_coroutine_test( - "X and Y color values should report hue and saturation once both have been received", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) - } - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorControl.hue(50) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorControl.saturation(72) - ) - ) - end, - { - test_init = test_init_x_y_color_mode, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "X and Y color values have 0 value", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 0) - } - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 0) - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorControl.hue(33) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorControl.saturation(100) - ) - ) - end, - { - test_init = test_init_x_y_color_mode, - min_api_version = 17 - } + "doConfigure lifecycle event should not re-configure the device profile", + function () + mock_device:set_field(switch_fields.profiling_data.BATTERY_SUPPORT, false, {persist = true}) + mock_device:set_field(switch_fields.profiling_data.POWER_TOPOLOGY, false, {persist = true}) + 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, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, 1, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end ) test.register_coroutine_test( - "Y and X color values should report hue and saturation once both have been received", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) - } - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorControl.hue(50) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorControl.saturation(72) - ) - ) - end, - { - test_init = test_init_x_y_color_mode, - min_api_version = 17 + "init should cause device to subscribe to all appropriate clusters", function() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation, + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorMode, + clusters.ColorControl.attributes.ColorTemperatureMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, + clusters.IlluminanceMeasurement.attributes.MeasuredValue, + clusters.OccupancySensing.attributes.Occupancy } -) + 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.register_message_test( - "Do not report when receiving a color temperature of 0 mireds", - { - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 0) - } - } - }, - { + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + end, + { min_api_version = 17 - } + } ) test.register_message_test( 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 86ce8185bf..775f391e8e 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua @@ -6,9 +6,10 @@ local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" local button_attr = capabilities.button.button -local utils = require "st.utils" -local dkjson = require "dkjson" local uint32 = require "st.matter.data_types.Uint32" +local fields = require "switch_utils.fields" +local switch_utils = require "switch_utils.utils" +local st_utils = require "st.utils" local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("button.yml"), @@ -40,162 +41,75 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) -local mock_device_battery = 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, - 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, - cluster_type = "SERVER", - }, - { - cluster_id = clusters.PowerSource.ID, - cluster_type = "SERVER", - feature_map = clusters.PowerSource.types.Feature.BATTERY - }, - }, - device_types = { - {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch - } - } - } -}) -local function expect_configure_buttons(device) +local expected_component_to_endpoint_map = { + ["main"] = 1 +} +local expected_initial_press_only_state = true + +local function expect_configure_button(device) test.socket.capability:__expect_send(device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) test.socket.capability:__expect_send(device:generate_test_message("main", button_attr.pushed({state_change = false}))) end -local function update_profile() - test.socket.matter:__queue_receive({mock_device_battery.id, clusters.PowerSource.attributes.AttributeList:build_test_report_data( - mock_device_battery, 1, {uint32(clusters.PowerSource.attributes.BatPercentRemaining.ID)} - )}) - expect_configure_buttons(mock_device_battery) - mock_device_battery:expect_metadata_update({ profile = "button-battery" }) -end - -local function test_init() - 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) - for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do - if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end - end - +local function test_init_for_lifecycle_tests() test.disable_startup_messages() test.mock_device.add_test_device(mock_device) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - - 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" }) - expect_configure_buttons(mock_device) - mock_device:expect_metadata_update({ profile = "button" }) - mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end -local function test_init_battery() - local CLUSTER_SUBSCRIBE_LIST_BATTERY = { - clusters.PowerSource.server.attributes.AttributeList, - clusters.PowerSource.server.attributes.BatPercentRemaining, - 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_BATTERY[1]:subscribe(mock_device_battery) - for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST_BATTERY) do - if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_battery)) end - end - +local function test_init_for_message_tests() test.disable_startup_messages() - test.mock_device.add_test_device(mock_device_battery) - test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "added" }) - test.socket.matter:__expect_send({mock_device_battery.id, subscribe_request}) - - test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "init" }) - test.socket.matter:__expect_send({mock_device_battery.id, subscribe_request}) - - test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "doConfigure" }) - mock_device_battery:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.mock_device.add_test_device(mock_device) + mock_device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, expected_component_to_endpoint_map, { persist = true }) + switch_utils.set_field_for_endpoint(mock_device, fields.INITIAL_PRESS_ONLY, 1, expected_initial_press_only_state, { persist = true }) end - -test.set_test_init_function(test_init) +test.set_test_init_function(test_init_for_message_tests) test.register_coroutine_test( - "Simulate the profile change update taking affect and the device info changing", - function() - test.socket.matter:__set_channel_ordering("relaxed") - update_profile() - test.wait_for_events() - local device_info_copy = utils.deep_copy(mock_device_battery.raw_st_data) - device_info_copy.profile.id = "buttons-battery" - local device_info_json = dkjson.encode(device_info_copy) - test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "infoChanged", device_info_json }) - -- due to the AttributeList being processed in update_profile, setting profiling_data.BATTERY_SUPPORT, - -- subsequent subscriptions will not include AttributeList. - local UPDATED_CLUSTER_SUBSCRIBE_LIST = { - clusters.PowerSource.server.attributes.BatPercentRemaining, + "Handle initial init lifecycle event", + function () + 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 updated_subscribe_request = UPDATED_CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device_battery) - for i, clus in ipairs(UPDATED_CLUSTER_SUBSCRIBE_LIST) do - if i > 1 then updated_subscribe_request:merge(clus:subscribe(mock_device_battery)) end + + 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_battery.id, updated_subscribe_request}) - expect_configure_buttons(mock_device_battery) + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.wait_for_events() + assert(mock_device:get_field(fields.profiling_data.BATTERY_SUPPORT) == fields.battery_support.NO_BATTERY, "Device should be marked as not needing battery support") + assert(mock_device:get_field(fields.profiling_data.POWER_TOPOLOGY) == false, "Device should be marked as not needing to configure power topology") end, { - test_init = test_init_battery, + test_init = test_init_for_lifecycle_tests, min_api_version = 17 } ) test.register_coroutine_test( - "Handle received BatPercentRemaining from device.", - function() - update_profile() - test.socket.matter:__queue_receive( - { - mock_device_battery.id, - clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data( - mock_device_battery, 1, 150 - ) - } - ) - test.socket.capability:__expect_send( - mock_device_battery:generate_test_message( - "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) - ) - ) + "Handle initial doConfigure lifecycle event, where profiling should be skipped", + function () + mock_device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, { persist = true }) + mock_device:set_field(fields.profiling_data.POWER_TOPOLOGY, false, { persist = true }) + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + expect_configure_button(mock_device) + mock_device:expect_metadata_update({ profile = "button" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + assert(switch_utils.deep_equals(st_utils.deep_copy(mock_device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP)), expected_component_to_endpoint_map), "Component to endpoint map should be set in doConfigure") + assert(switch_utils.get_field_for_endpoint(mock_device, fields.INITIAL_PRESS_ONLY, 1) == expected_initial_press_only_state, "InitialPressOnly should be true") + assert(switch_utils.get_field_for_endpoint(mock_device, fields.SUPPORTS_MULTI_PRESS, 1) == nil, "MultiPress should be nil") + assert(switch_utils.get_field_for_endpoint(mock_device, fields.EMULATE_HELD, 1) == nil, "EmulateHeld should be nil") end, { - test_init = test_init_battery, + test_init = test_init_for_lifecycle_tests, min_api_version = 17 } ) @@ -498,67 +412,190 @@ test.register_message_test( } ) -local function reset_battery_profiling_info() - local fields = require "switch_utils.fields" - mock_device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, {persist=true}) +local mock_device_battery = 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, + 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, + cluster_type = "SERVER", + }, + { + cluster_id = clusters.PowerSource.ID, + cluster_type = "SERVER", + feature_map = clusters.PowerSource.types.Feature.BATTERY + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + } + } +}) + +local function test_init_battery() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device_battery) +end + +local function test_init_profile_change_with_battery() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device_battery) + -- by this point, init will have run and this will have been set to false + mock_device_battery:set_field(fields.profiling_data.POWER_TOPOLOGY, false, { persist = true }) end test.register_coroutine_test( - "Test profile does not change to button-battery when battery percent remaining attribute (attribute ID 12) is not available", + "Handle initial init lifecycle event for device with battery", + function () + local CLUSTER_SUBSCRIBE_LIST_BATTERY = { + clusters.PowerSource.server.attributes.AttributeList, + clusters.PowerSource.server.attributes.BatPercentRemaining, + 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_BATTERY[1]:subscribe(mock_device_battery) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST_BATTERY) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_battery)) end + end + + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "init" }) + test.socket.matter:__expect_send({mock_device_battery.id, subscribe_request}) + assert(mock_device_battery:get_field(fields.profiling_data.BATTERY_SUPPORT) == nil, "Device should not have specified battery support yet") + assert(mock_device_battery:get_field(fields.profiling_data.POWER_TOPOLOGY) == nil, "Device should be marked as not needing to configure power topology") + end, + { + test_init = test_init_battery, + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Following init events, after battery has been configured, device should not create subscriptions to AttributeList", function() - reset_battery_profiling_info() + mock_device_battery:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.BATTERY_PERCENTAGE, {persist = true}) test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, {uint32(10)}) - } - ) + local CLUSTER_SUBSCRIBE_LIST_BATTERY = { + clusters.PowerSource.server.attributes.BatPercentRemaining, + 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_BATTERY[1]:subscribe(mock_device_battery) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST_BATTERY) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_battery)) end + end + + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "init" }) + test.socket.matter:__expect_send({mock_device_battery.id, subscribe_request}) end, { - min_api_version = 17 + test_init = test_init_profile_change_with_battery, + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Handle initial doConfigure lifecycle event for device with battery, where profiling should be skipped", + function () + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "doConfigure" }) + mock_device_battery:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + test_init = test_init_profile_change_with_battery, + min_api_version = 17 } ) test.register_coroutine_test( - "Test profile change to button-batteryLevel when battery percent remaining attribute (attribute ID 14) is available", + "PowerSource AttributeList with BatPercentRemaining update should trigger profile update to button-battery", + function() + test.socket.matter:__queue_receive({mock_device_battery.id, clusters.PowerSource.attributes.AttributeList:build_test_report_data( + mock_device_battery, 1, {uint32(clusters.PowerSource.attributes.BatPercentRemaining.ID)} + )}) + expect_configure_button(mock_device_battery) + mock_device_battery:expect_metadata_update({ profile = "button-battery" }) + end, + { + test_init = test_init_profile_change_with_battery, + min_api_version = 17 + } +) + +test.register_coroutine_test( + "PowerSource AttributeList with BatChargeLevel update should trigger profile update to button-batteryLevel", + function() + test.socket.matter:__queue_receive({mock_device_battery.id, clusters.PowerSource.attributes.AttributeList:build_test_report_data( + mock_device_battery, 1, {uint32(clusters.PowerSource.attributes.BatChargeLevel.ID)} + )}) + expect_configure_button(mock_device_battery) + mock_device_battery:expect_metadata_update({ profile = "button-batteryLevel" }) + end, + { + test_init = test_init_profile_change_with_battery, + min_api_version = 17 + } +) + +test.register_coroutine_test( + "PowerSource AttributeList with no attributes update should set battery support to false and trigger profile update to button", function() - reset_battery_profiling_info() - test.wait_for_events() test.socket.matter:__queue_receive( { - mock_device.id, - clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, {uint32( - clusters.PowerSource.attributes.BatChargeLevel.ID - )}) + mock_device_battery.id, + clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device_battery, 1, {uint32(2)}) } ) - expect_configure_buttons(mock_device) - mock_device:expect_metadata_update({ profile = "button-batteryLevel" }) + expect_configure_button(mock_device_battery) + mock_device_battery:expect_metadata_update({ profile = "button" }) + test.wait_for_events() + assert(mock_device_battery:get_field(fields.profiling_data.BATTERY_SUPPORT) == fields.battery_support.NO_BATTERY, "Device should be marked as not supporting battery") end, { - min_api_version = 17 + test_init = test_init_profile_change_with_battery, + min_api_version = 17 } ) test.register_coroutine_test( - "Test profile change to button-battery when battery percent remaining attribute (attribute ID 12) is available", + "BatPercentRemaining attribute event should generate a battery capability report", function() - reset_battery_profiling_info() - test.wait_for_events() test.socket.matter:__queue_receive( { - mock_device.id, - clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device, 1, {uint32( - clusters.PowerSource.attributes.BatPercentRemaining.ID - )}) + mock_device_battery.id, + clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data( + mock_device_battery, 1, 150 + ) } ) - expect_configure_buttons(mock_device) - mock_device:expect_metadata_update({ profile = "button-battery" }) + test.socket.capability:__expect_send( + mock_device_battery:generate_test_message( + "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) + ) + ) end, { - min_api_version = 17 + test_init = test_init_battery, + min_api_version = 17 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_irrigation_system.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_irrigation_system.lua index 241f17c53e..3acf4129f9 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_irrigation_system.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_irrigation_system.lua @@ -123,8 +123,6 @@ local function test_init() subscribe_request:merge(cluster:subscribe(mock_irrigation_system)) end end - test.socket.device_lifecycle:__queue_receive({ mock_irrigation_system.id, "added" }) - test.socket.matter:__expect_send({mock_irrigation_system.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_irrigation_system.id, "init" }) test.socket.matter:__expect_send({mock_irrigation_system.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_irrigation_system.id, "doConfigure" }) 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 6a8ab5657a..3dad8bf56d 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 @@ -127,9 +127,6 @@ local function test_init() 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}) -- 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}) 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 2225ed129b..2392800019 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 @@ -221,10 +221,6 @@ local function test_init() 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 - 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" }) @@ -254,9 +250,6 @@ local function test_init_battery() test.disable_startup_messages() test.mock_device.add_test_device(mock_device_battery) - test.socket.matter:__expect_send({mock_device_battery.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "added" }) - test.socket.matter:__expect_send({mock_device_battery.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "init" }) 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 index 68fc7e8339..5500eca24e 100644 --- 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 @@ -155,8 +155,6 @@ local function test_init() 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}) 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 0c4b314791..1c7cccea48 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 @@ -216,8 +216,6 @@ local function test_init() 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" }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) @@ -411,8 +409,6 @@ test.register_coroutine_test( for _, cluster in ipairs(CLUSTER_SUBSCRIBE_LIST) do subscribe_request:merge(cluster:subscribe(unsup_mock_device)) end - 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({ unsup_mock_device.id, "init" }) test.socket.matter:__expect_send({unsup_mock_device.id, subscribe_request}) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_device_configuration.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_device_configuration.lua index 6c3d168c91..d27ee2c383 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_device_configuration.lua @@ -151,7 +151,6 @@ test.register_coroutine_test( local mock_device = mock_device_plug_with_switch_profile_vendor_override local subscribe_request = clusters.OnOff.attributes.OnOff:subscribe(mock_device) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) 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" }) @@ -228,7 +227,6 @@ test.register_coroutine_test( local subscribe_request = clusters.OnOff.attributes.OnOff:subscribe(mock_device_mounted_on_off_control) test.socket.device_lifecycle:__queue_receive({ mock_device_mounted_on_off_control.id, "added" }) - 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, "init" }) test.socket.matter:__expect_send({mock_device_mounted_on_off_control.id, subscribe_request}) @@ -284,7 +282,6 @@ test.register_coroutine_test( end end test.socket.device_lifecycle:__queue_receive({ mock_device_mounted_dimmable_load_control.id, "added" }) - 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, "init" }) test.socket.matter:__expect_send({mock_device_mounted_dimmable_load_control.id, subscribe_request}) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua index 3b3cefd8e3..eabc9246f7 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua @@ -4,8 +4,6 @@ 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" local mock_device = test.mock_device.build_test_matter_device({ @@ -40,50 +38,11 @@ local mock_device = test.mock_device.build_test_matter_device({ local function test_init() test.disable_startup_messages() test.mock_device.add_test_device(mock_device) - - local cluster_subscribe_list = { - clusters.Switch.events.InitialPress, - clusters.Switch.events.LongPress, - clusters.Switch.events.ShortRelease, - clusters.Switch.events.MultiPressComplete, - - clusters.TemperatureMeasurement.attributes.MeasuredValue, - clusters.TemperatureMeasurement.attributes.MinMeasuredValue, - clusters.TemperatureMeasurement.attributes.MaxMeasuredValue, - - clusters.RelativeHumidityMeasurement.attributes.MeasuredValue, - clusters.PowerSource.attributes.BatPercentRemaining - } - - 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}) - - 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" }) - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - - mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - - local device_info_copy = utils.deep_copy(mock_device.raw_st_data) - device_info_copy.profile.id = "3-button-battery-temperature-humidity" - 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}) - end test.register_coroutine_test("Read appropriate attribute values after tempOffset preference change", function() local report = clusters.TemperatureMeasurement.attributes.MeasuredValue:build_test_report_data(mock_device,1, 2000) - mock_device.st_store.preferences = {tempOffset = "0"} + test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({ preferences = { tempOffset = "2" } })) test.socket.matter:__queue_receive({mock_device.id, report}) test.socket.capability:__expect_send(mock_device:generate_test_message("main",capabilities.temperatureMeasurement.temperature({ @@ -108,7 +67,7 @@ end, test.register_coroutine_test("Read appropriate attribute values after humidityOffset preference change", function() local report = clusters.RelativeHumidityMeasurement.attributes.MeasuredValue:build_test_report_data(mock_device,2, 2000) - mock_device.st_store.preferences = {humidityOffset = "0"} + test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({ preferences = { humidityOffset = "0" } })) test.socket.matter:__queue_receive({mock_device.id, report}) test.socket.capability:__expect_send(mock_device:generate_test_message("main",capabilities.relativeHumidityMeasurement.humidity({ 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 a1b60be357..c34cbb8ed6 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -4,7 +4,7 @@ local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" -local st_utils = require "st.utils" +local fields = require "switch_utils.fields" local clusters = require "st.matter.clusters" local TRANSITION_TIME = 0 @@ -27,87 +27,18 @@ local mock_device = test.mock_device.build_test_matter_device({ {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 = clusters.ColorControl.ID, cluster_type = "BOTH", feature_map = 31}, - {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2} - }, - device_types = { - {device_type_id = 0x0100, device_type_revision = 1} -- On/Off Light - } - } - } -}) - -local mock_device_no_hue_sat = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("switch-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"} - }, - device_types = { - {device_type_id = 0x0100, device_type_revision = 1} -- On/Off Light - } - } - } -}) - -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.ColorControl.ID, cluster_type = "BOTH", feature_map = 31}, {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 + {device_type_id = fields.DEVICE_TYPE_ID.LIGHT.ON_OFF, device_type_revision = 1}, + {device_type_id = fields.DEVICE_TYPE_ID.LIGHT.DIMMABLE, device_type_revision = 1}, + {device_type_id = fields.DEVICE_TYPE_ID.LIGHT.COLOR_TEMPERATURE, device_type_revision = 1}, + {device_type_id = fields.DEVICE_TYPE_ID.LIGHT.EXTENDED_COLOR, device_type_revision = 1} } } } @@ -129,151 +60,56 @@ local cluster_subscribe_list = { clusters.ColorControl.attributes.ColorMode, } -local function set_color_mode(device, endpoint, color_mode) - test.socket.matter:__queue_receive({ - device.id, - clusters.ColorControl.attributes.ColorMode:build_test_report_data( - device, endpoint, color_mode) - }) - local read_req - if color_mode == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then - read_req = clusters.ColorControl.attributes.CurrentHue:read() - read_req:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) - else -- color_mode = clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY - read_req = clusters.ColorControl.attributes.CurrentX:read() - read_req:merge(clusters.ColorControl.attributes.CurrentY:read()) - end - test.socket.matter:__expect_send({device.id, read_req}) -end - local function test_init() - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) - for i, cluster in ipairs(cluster_subscribe_list) do - if i > 1 then - subscribe_request:merge(cluster:subscribe(mock_device)) - end - end - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - - -- note that since disable_startup_messages is not explicitly called here, - -- the following subscribe is due to the init event sent by the test framework. - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) - set_color_mode(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) -end -test.set_test_init_function(test_init) -local function test_init_x_y_color_mode() 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}) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - test.mock_device.add_test_device(mock_device) - set_color_mode(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) -end - -local function test_init_no_hue_sat() - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_no_hue_sat) - for i, cluster in ipairs(cluster_subscribe_list) do - if i > 1 then - subscribe_request:merge(cluster:subscribe(mock_device_no_hue_sat)) - end - end - test.socket.device_lifecycle:__queue_receive({ mock_device_no_hue_sat.id, "added" }) - test.socket.matter:__expect_send({mock_device_no_hue_sat.id, subscribe_request}) - - test.socket.matter:__expect_send({mock_device_no_hue_sat.id, subscribe_request}) - test.mock_device.add_test_device(mock_device_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.set_test_init_function(test_init) -test.register_message_test( - "Test that Color Temperature Light device does not switch profiles", - {}, +test.register_coroutine_test( + "doConfigure properly sets Extended Color Light and does not attempt to switch profiles", + function() + mock_device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, { persist = true }) + mock_device:set_field(fields.profiling_data.POWER_TOPOLOGY, false, { persist = true }) + test.wait_for_events() + 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, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ColorControl.attributes.Options:write(mock_device, 1, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, { - test_init = test_init_color_temp, min_api_version = 17 } ) -test.register_message_test( - "Test that Extended Color Light device does not switch profiles", - {}, +test.register_coroutine_test( + "init creates accurate subscription for Extended Color Light", + function() + 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, "init" }) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + end, { - test_init = test_init_extended_color, min_api_version = 17 } ) @@ -344,60 +180,84 @@ test.register_message_test( } ) + +local hue = math.floor((50 * 0xFE) / 100.0 + 0.5) +local sat = math.floor((50 * 0xFE) / 100.0 + 0.5) test.register_message_test( - "Set level command should send the appropriate commands", + "Set Hue command should send MoveToHue", { { channel = "capability", direction = "receive", message = { mock_device.id, - { capability = "switchLevel", component = "main", command = "setLevel", args = {20,20} } + { capability = "colorControl", component = "main", command = "setHue", args = { 50 } } } }, { - channel = "devices", + channel = "matter", direction = "send", message = { - "register_native_capability_cmd_handler", - { device_uuid = mock_device.id, capability_id = "switchLevel", capability_cmd_id = "setLevel" } + mock_device.id, + clusters.ColorControl.server.commands.MoveToHue(mock_device, 1, hue, 0, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Set Saturation command should send MoveToSaturation", + { { - channel = "matter", - direction = "send", + channel = "capability", + direction = "receive", message = { mock_device.id, - clusters.LevelControl.server.commands.MoveToLevelWithOnOff(mock_device, 1, st_utils.round(20/100.0 * 254), 20, 0 ,0) + { capability = "colorControl", component = "main", command = "setSaturation", args = { 50 } } } }, { channel = "matter", - direction = "receive", + direction = "send", message = { mock_device.id, - clusters.LevelControl.server.commands.MoveToLevelWithOnOff:build_test_command_response(mock_device, 1) + clusters.ColorControl.server.commands.MoveToSaturation(mock_device, 1, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Set color temperature should send the appropriate commands", + { { - channel = "matter", + channel = "capability", direction = "receive", message = { mock_device.id, - clusters.LevelControl.attributes.CurrentLevel:build_test_report_data(mock_device, 1, 50) + { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {1800} } } }, { - channel = "capability", + channel = "matter", direction = "send", - message = mock_device:generate_test_message("main", capabilities.switchLevel.level(20)) + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 556, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) + } }, { - channel = "devices", - direction = "send", + channel = "matter", + direction = "receive", message = { - "register_native_capability_attr_handler", - { device_uuid = mock_device.id, capability_id = "switchLevel", capability_attr_id = "level" } + mock_device.id, + clusters.ColorControl.server.commands.MoveToColorTemperature:build_test_command_response(mock_device, 1) } }, { @@ -405,23 +265,31 @@ test.register_message_test( direction = "receive", message = { mock_device.id, - clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, 1, true) + clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 556) } }, { channel = "capability", direction = "send", - message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) + message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) }, + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Do not report when receiving a color temperature of 0 mireds", + { { - channel = "devices", - direction = "send", + channel = "matter", + direction = "receive", message = { - "register_native_capability_attr_handler", - { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } + mock_device.id, + clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 0) } - }, - + } }, { min_api_version = 17 @@ -429,276 +297,202 @@ test.register_message_test( ) test.register_message_test( - "Current level reports should generate appropriate events", + "Min and max color temperature attributes set capability constraint", { { channel = "matter", direction = "receive", message = { mock_device.id, - clusters.LevelControl.server.attributes.CurrentLevel:build_test_report_data(mock_device, 1, 50) + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, 1, 153) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 555) } }, { channel = "capability", direction = "send", - message = mock_device:generate_test_message("main", capabilities.switchLevel.level(st_utils.round((50 / 254.0 * 100) + 0.5))) + message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 1800, maximum = 6500})) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Min and max color temperature attributes set capability constraint using improved temperature conversion rounding", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, 1, 165) + } }, { - channel = "devices", - direction = "send", + channel = "matter", + direction = "receive", message = { - "register_native_capability_attr_handler", - { device_uuid = mock_device.id, capability_id = "switchLevel", capability_attr_id = "level" } + mock_device.id, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 365) } }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 2800, maximum = 6000})) + } }, { min_api_version = 17 } ) -test.register_coroutine_test( - "Set color command should send the appropriate commands", function() - test.socket.capability:__queue_receive( - { - mock_device_no_hue_sat.id, - { capability = "colorControl", component = "main", command = "setColor", args = { { hue = 50, saturation = 72 } }}, - } - ) - test.socket.matter:__expect_send( - { - mock_device_no_hue_sat.id, - clusters.ColorControl.server.commands.MoveToColor(mock_device_no_hue_sat, 1, 15182, 21547, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) - } - ) - test.socket.matter:__queue_receive( - { - mock_device_no_hue_sat.id, - clusters.ColorControl.server.commands.MoveToColor:build_test_command_response(mock_device_no_hue_sat, 1) - } - ) - test.socket.matter:__queue_receive( - { - mock_device_no_hue_sat.id, - clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device_no_hue_sat, 1, 15091) - } - ) - test.socket.matter:__queue_receive( - { - mock_device_no_hue_sat.id, - clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device_no_hue_sat, 1, 21547) - } - ) - test.socket.capability:__expect_send( - mock_device_no_hue_sat:generate_test_message( - "main", capabilities.colorControl.hue(50) - ) - ) - test.socket.capability:__expect_send( - mock_device_no_hue_sat:generate_test_message( - "main", capabilities.colorControl.saturation(72) - ) - ) - end, - { - test_init = test_init_no_hue_sat, - min_api_version = 17 - } -) - -local hue = math.floor((50 * 0xFE) / 100.0 + 0.5) -local sat = math.floor((50 * 0xFE) / 100.0 + 0.5) - test.register_message_test( - "Set color command should send huesat commands when supported", + "Device reports mireds outside of supported range, set capability to min/max value in kelvin", { { channel = "matter", direction = "receive", message = { mock_device.id, - clusters.ColorControl.attributes.ColorCapabilities:build_test_report_data(mock_device, 1, 0x01) + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, 1, 165) } }, { - channel = "capability", + channel = "matter", direction = "receive", message = { mock_device.id, - { capability = "colorControl", component = "main", command = "setColor", args = { { hue = 50, saturation = 50 } } } + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 365) } }, { - channel = "matter", + channel = "capability", direction = "send", - message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToHueAndSaturation(mock_device, 1, hue, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToHueAndSaturation:build_test_command_response(mock_device, 1) - } + message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 2800, maximum = 6000})) }, { channel = "matter", direction = "receive", message = { mock_device.id, - clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, hue) + clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 160) } }, { channel = "capability", 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" } - } + message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(6000)) }, { channel = "matter", direction = "receive", message = { mock_device.id, - clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, sat) + clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 370) } }, { 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" } - } - }, + message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(2800)) + } }, { min_api_version = 17 } ) -hue = 0xFE -sat = 0xFE test.register_message_test( - "Set color command should clamp invalid huesat values", + "Capability sets color temp outside of supported range, value sent to device is limited to min/max value in mireds", { { channel = "matter", direction = "receive", message = { mock_device.id, - clusters.ColorControl.attributes.ColorCapabilities:build_test_report_data(mock_device, 1, 0x01) + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, 1, 165) } }, { - channel = "capability", + channel = "matter", direction = "receive", message = { mock_device.id, - { capability = "colorControl", component = "main", command = "setColor", args = { { hue = 110, saturation = 110 } } } + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 365) } }, { - channel = "matter", + channel = "capability", direction = "send", - message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToHueAndSaturation(mock_device, 1, hue, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) - } + message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 2800, maximum = 6000})) }, { - channel = "matter", + channel = "capability", direction = "receive", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToHueAndSaturation:build_test_command_response(mock_device, 1) + { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {6100} } } }, { channel = "matter", - direction = "receive", + direction = "send", message = { mock_device.id, - clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, hue) + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 165, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { channel = "capability", - 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", message = { mock_device.id, - clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, sat) + { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {2700} } } }, { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(100)) - }, - { - channel = "devices", + channel = "matter", direction = "send", message = { - "register_native_capability_attr_handler", - { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "saturation" } + mock_device.id, + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 365, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } - }, + } }, { min_api_version = 17 } ) -hue = math.floor((50 * 0xFE) / 100.0 + 0.5) -sat = math.floor((50 * 0xFE) / 100.0 + 0.5) test.register_message_test( - "Set Hue command should send MoveToHue", + "Min color temperature outside of range, capability not sent", { { - channel = "capability", + channel = "matter", direction = "receive", message = { mock_device.id, - { capability = "colorControl", component = "main", command = "setHue", args = { 50 } } + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, 1, 50) } }, { channel = "matter", - direction = "send", + direction = "receive", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToHue(mock_device, 1, hue, 0, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 555) } - }, + } }, { min_api_version = 17 @@ -706,24 +500,24 @@ test.register_message_test( ) test.register_message_test( - "Set Saturation command should send MoveToSaturation", + "Max color temperature outside of range, capability not sent", { { - channel = "capability", + channel = "matter", direction = "receive", message = { mock_device.id, - { capability = "colorControl", component = "main", command = "setSaturation", args = { 50 } } + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, 1, 153) } }, { channel = "matter", - direction = "send", + direction = "receive", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToSaturation(mock_device, 1, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 1100) } - }, + } }, { min_api_version = 17 @@ -731,30 +525,44 @@ test.register_message_test( ) test.register_message_test( - "Set color temperature should send the appropriate commands", + "Min and max level attributes set capability constraint", { { - channel = "capability", + channel = "matter", direction = "receive", message = { mock_device.id, - { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {1800} } + clusters.LevelControl.attributes.MinLevel:build_test_report_data(mock_device, 1, 5) } }, { channel = "matter", - direction = "send", + direction = "receive", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 556, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) + clusters.LevelControl.attributes.MaxLevel:build_test_report_data(mock_device, 1, 10) } }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.switchLevel.levelRange({minimum = 2, maximum = 4})) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Min level attribute outside of range for lighting feature device (min level = 1), capability not sent", + { { channel = "matter", direction = "receive", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature:build_test_command_response(mock_device, 1) + clusters.LevelControl.attributes.MinLevel:build_test_report_data(mock_device, 1, 0) } }, { @@ -762,14 +570,9 @@ test.register_message_test( direction = "receive", message = { mock_device.id, - clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 556) + clusters.LevelControl.attributes.MaxLevel:build_test_report_data(mock_device, 1, 10) } - }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) - }, + } }, { min_api_version = 17 @@ -777,83 +580,115 @@ test.register_message_test( ) test.register_coroutine_test( - "X and Y color values should report hue and saturation once both have been received", + "colorControl capability sent based on CurrentX and CurrentY due to ColorMode", function() test.socket.matter:__queue_receive( { mock_device.id, - clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) + clusters.ColorControl.attributes.ColorMode:build_test_report_data(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) } ) - test.socket.matter:__queue_receive( + local read_x_y = clusters.ColorControl.attributes.CurrentX:read() + read_x_y:merge(clusters.ColorControl.attributes.CurrentY:read()) + test.socket.matter:__expect_send( { mock_device.id, - clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) + read_x_y } ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorControl.hue(50) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorControl.saturation(72) - ) - ) - end, - { - test_init = test_init_x_y_color_mode, - min_api_version = 17 - } -) - -test.register_coroutine_test( - "X and Y color values have 0 value", - function() test.socket.matter:__queue_receive( { mock_device.id, - clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 0) + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) } ) test.socket.matter:__queue_receive( { mock_device.id, - clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 0) + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) } ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.colorControl.hue(33) + "main", capabilities.colorControl.hue(50) ) ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.colorControl.saturation(100) + "main", capabilities.colorControl.saturation(72) ) ) end, { - test_init = test_init_x_y_color_mode, - min_api_version = 17 + min_api_version = 17 } ) test.register_coroutine_test( - "Y and X color values should report hue and saturation once both have been received", + "Refresh necessary attributes", function() - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) - } + test.socket.capability:__queue_receive( + {mock_device.id, {capability = "refresh", component = "main", command = "refresh", args = {}}} ) - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) - } + 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, + { + min_api_version = 17 + } +) + + +local function set_color_mode(device, endpoint, color_mode) + test.socket.matter:__queue_receive({ + device.id, + clusters.ColorControl.attributes.ColorMode:build_test_report_data( + device, endpoint, color_mode) + }) + local read_req + if color_mode == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then + read_req = clusters.ColorControl.attributes.CurrentHue:read() + read_req:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) + else -- color_mode = clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY + read_req = clusters.ColorControl.attributes.CurrentX:read() + read_req:merge(clusters.ColorControl.attributes.CurrentY:read()) + end + test.socket.matter:__expect_send({device.id, read_req}) +end + + + +local function test_init_x_y_color_mode() + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device)) + end + end + + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.mock_device.add_test_device(mock_device) + set_color_mode(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) +end + +test.register_coroutine_test( + "X and Y color values should report hue and saturation once both have been received", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) + } + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) + } ) test.socket.capability:__expect_send( mock_device:generate_test_message( @@ -872,148 +707,175 @@ test.register_coroutine_test( } ) -test.register_message_test( - "Do not report when receiving a color temperature of 0 mireds", - { - { - channel = "matter", - direction = "receive", - message = { +test.register_coroutine_test( + "X and Y color values have 0 value", + function() + test.socket.matter:__queue_receive( + { mock_device.id, - clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 0) + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 0) } - } - }, + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 0) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.colorControl.hue(33) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.colorControl.saturation(100) + ) + ) + end, { - min_api_version = 17 + test_init = test_init_x_y_color_mode, + min_api_version = 17 } ) -test.register_message_test( - "Min and max color temperature attributes set capability constraint", - { - { - channel = "matter", - direction = "receive", - message = { +test.register_coroutine_test( + "Y and X color values should report hue and saturation once both have been received", + function() + test.socket.matter:__queue_receive( + { mock_device.id, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, 1, 153) + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) } - }, - { - channel = "matter", - direction = "receive", - message = { + ) + test.socket.matter:__queue_receive( + { mock_device.id, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 555) + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) } - }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 1800, maximum = 6500})) - } - }, + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.colorControl.hue(50) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.colorControl.saturation(72) + ) + ) + end, { - min_api_version = 17 + test_init = test_init_x_y_color_mode, + min_api_version = 17 } ) -test.register_message_test( - "Min and max color temperature attributes set capability constraint using improved temperature conversion rounding", - { - { - channel = "matter", - direction = "receive", - message = { +test.register_coroutine_test( + "colorControl capability sent based on CurrentHue and CurrentSaturation due to ColorMode", + function() + test.socket.matter:__queue_receive( + { mock_device.id, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, 1, 165) + clusters.ColorControl.attributes.ColorMode:build_test_report_data(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) } - }, - { - channel = "matter", - direction = "receive", - message = { + ) + local read_hue_sat = clusters.ColorControl.attributes.CurrentHue:read() + read_hue_sat:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) + test.socket.matter:__expect_send( + { mock_device.id, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 365) + read_hue_sat } - }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 2800, maximum = 6000})) - } - }, + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, 0xFE), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "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, + clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, 0xFE), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "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, { - min_api_version = 17 + test_init = test_init_x_y_color_mode, + min_api_version = 17 } ) + + +local function test_init_with_hue_sat_color_mode_set() + test.mock_device.add_test_device(mock_device) + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device)) + end + end + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + set_color_mode(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) +end + test.register_message_test( - "Device reports mireds outside of supported range, set capability to min/max value in kelvin", + "Set color command should send huesat commands when supported", { { channel = "matter", direction = "receive", message = { mock_device.id, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, 1, 165) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 365) + clusters.ColorControl.attributes.ColorCapabilities:build_test_report_data(mock_device, 1, 0x01) } }, { channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 2800, maximum = 6000})) - }, - { - channel = "matter", direction = "receive", message = { mock_device.id, - clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 160) + { capability = "colorControl", component = "main", command = "setColor", args = { { hue = 50, saturation = 50 } } } } }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(6000)) - }, { channel = "matter", - direction = "receive", + direction = "send", message = { mock_device.id, - clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 370) + clusters.ColorControl.server.commands.MoveToHueAndSaturation(mock_device, 1, hue, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(2800)) - } - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Capability sets color temp outside of supported range, value sent to device is limited to min/max value in mireds", - { { channel = "matter", direction = "receive", message = { mock_device.id, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, 1, 165) + clusters.ColorControl.server.commands.MoveToHueAndSaturation:build_test_command_response(mock_device, 1) } }, { @@ -1021,86 +883,77 @@ test.register_message_test( direction = "receive", message = { mock_device.id, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 365) + clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, hue) } }, { channel = "capability", direction = "send", - message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 2800, maximum = 6000})) + message = mock_device:generate_test_message("main", capabilities.colorControl.hue(50)) }, { - channel = "capability", - direction = "receive", + channel = "devices", + direction = "send", message = { - mock_device.id, - { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {6100} } + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "hue" } } }, { channel = "matter", - direction = "send", + direction = "receive", message = { mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 165, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) + clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, sat) } }, { channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {2700} } - } + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(50)) }, { - channel = "matter", + channel = "devices", direction = "send", message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 365, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "saturation" } } - } + }, }, { - min_api_version = 17 + test_init = test_init_with_hue_sat_color_mode_set, + min_api_version = 17 } ) +hue = 0xFE +sat = 0xFE test.register_message_test( - "Min color temperature outside of range, capability not sent", + "Set color command should clamp invalid huesat values", { { channel = "matter", direction = "receive", message = { mock_device.id, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, 1, 50) + clusters.ColorControl.attributes.ColorCapabilities:build_test_report_data(mock_device, 1, 0x01) } }, { - channel = "matter", + channel = "capability", direction = "receive", message = { mock_device.id, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 555) + { capability = "colorControl", component = "main", command = "setColor", args = { { hue = 110, saturation = 110 } } } } - } - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Max color temperature outside of range, capability not sent", - { + }, { channel = "matter", - direction = "receive", + direction = "send", message = { mock_device.id, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, 1, 153) + clusters.ColorControl.server.commands.MoveToHueAndSaturation(mock_device, 1, hue, sat, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } }, { @@ -1108,24 +961,28 @@ test.register_message_test( direction = "receive", message = { mock_device.id, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, 1, 1100) + clusters.ColorControl.server.commands.MoveToHueAndSaturation:build_test_command_response(mock_device, 1) } - } - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Min and max level attributes set capability constraint", - { + }, { channel = "matter", direction = "receive", message = { mock_device.id, - clusters.LevelControl.attributes.MinLevel:build_test_report_data(mock_device, 1, 5) + clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, hue) + } + }, + { + channel = "capability", + 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" } } }, { @@ -1133,163 +990,183 @@ test.register_message_test( direction = "receive", message = { mock_device.id, - clusters.LevelControl.attributes.MaxLevel:build_test_report_data(mock_device, 1, 10) + clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, sat) } }, { channel = "capability", direction = "send", - message = mock_device:generate_test_message("main", capabilities.switchLevel.levelRange({minimum = 2, maximum = 4})) - } + 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" } + } + }, }, { - min_api_version = 17 + test_init = test_init_with_hue_sat_color_mode_set, + min_api_version = 17 } ) -test.register_message_test( - "Min level attribute outside of range for lighting feature device (min level = 1), capability not sent", - { - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.LevelControl.attributes.MinLevel:build_test_report_data(mock_device, 1, 0) - } - }, + + + +local mock_color_device_no_hue_sat = 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 = { { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.LevelControl.attributes.MaxLevel:build_test_report_data(mock_device, 1, 10) + 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 = fields.DEVICE_TYPE_ID.LIGHT.EXTENDED_COLOR, device_type_revision = 1} } } - }, - { - min_api_version = 17 } -) +}) + +local function test_init_no_hue_sat() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_color_device_no_hue_sat) +end test.register_coroutine_test( - "colorControl capability sent based on CurrentHue and CurrentSaturation due to ColorMode", - function() - test.socket.matter:__queue_receive( + "Set color command should send the appropriate commands", function() + test.socket.capability:__queue_receive( { - mock_device.id, - clusters.ColorControl.attributes.ColorMode:build_test_report_data(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) + mock_color_device_no_hue_sat.id, + { capability = "colorControl", component = "main", command = "setColor", args = { { hue = 50, saturation = 72 } }}, } ) - local read_hue_sat = clusters.ColorControl.attributes.CurrentHue:read() - read_hue_sat:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) test.socket.matter:__expect_send( { - mock_device.id, - read_hue_sat + mock_color_device_no_hue_sat.id, + clusters.ColorControl.server.commands.MoveToColor(mock_color_device_no_hue_sat, 1, 15182, 21547, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) } ) test.socket.matter:__queue_receive( { - mock_device.id, - clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, 0xFE), + mock_color_device_no_hue_sat.id, + clusters.ColorControl.server.commands.MoveToColor:build_test_command_response(mock_color_device_no_hue_sat, 1) } ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorControl.hue(100) - ) - ) - test.socket.devices:__expect_send( + test.socket.matter:__queue_receive( { - "register_native_capability_attr_handler", - { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "hue" } + mock_color_device_no_hue_sat.id, + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_color_device_no_hue_sat, 1, 15091) } ) test.socket.matter:__queue_receive( { - mock_device.id, - clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, 0xFE), + mock_color_device_no_hue_sat.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_color_device_no_hue_sat, 1, 21547) } ) test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorControl.saturation(100) + mock_color_device_no_hue_sat:generate_test_message( + "main", capabilities.colorControl.hue(50) ) ) - test.socket.devices:__expect_send( - { - "register_native_capability_attr_handler", - { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "saturation" } - } + test.socket.capability:__expect_send( + mock_color_device_no_hue_sat:generate_test_message( + "main", capabilities.colorControl.saturation(72) + ) ) end, { - test_init = test_init_x_y_color_mode, + test_init = test_init_no_hue_sat, min_api_version = 17 } ) + + +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 = fields.DEVICE_TYPE_ID.LIGHT.ON_OFF, device_type_revision = 1}, + {device_type_id = fields.DEVICE_TYPE_ID.LIGHT.COLOR_TEMPERATURE, device_type_revision = 1} + } + } + } +}) + +local function test_init_color_temp() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device_color_temp) +end + test.register_coroutine_test( - "colorControl capability sent based on CurrentX and CurrentY due to ColorMode", + "doConfigure properly sets Color Temperature Light and does not attempt to switch profiles", function() - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.ColorMode:build_test_report_data(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) - } - ) - local read_x_y = clusters.ColorControl.attributes.CurrentX:read() - read_x_y:merge(clusters.ColorControl.attributes.CurrentY:read()) - test.socket.matter:__expect_send( - { - mock_device.id, - read_x_y - } - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) - } - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorControl.hue(50) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorControl.saturation(72) - ) - ) + mock_device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, { persist = true }) + mock_device:set_field(fields.profiling_data.POWER_TOPOLOGY, false, { persist = true }) + test.wait_for_events() + 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" }) end, { - min_api_version = 17 + test_init = test_init_color_temp, + min_api_version = 17 } ) test.register_coroutine_test( - "Refresh necessary attributes", + "init creates accurate subscription for Color Temperature Light", 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 + 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 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.matter:__expect_send({mock_device.id, read_request}) - test.wait_for_events() + test.socket.device_lifecycle:__queue_receive({ mock_device_color_temp.id, "init" }) + test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request}) end, { - min_api_version = 17 + test_init = test_init_color_temp, + min_api_version = 17 } ) 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 ced13d6eb8..788a00d76e 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 @@ -45,38 +45,31 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) -local cluster_subscribe_list = { - clusters.ValveConfigurationAndControl.attributes.CurrentState, - clusters.ValveConfigurationAndControl.attributes.CurrentLevel -} - local function test_init() - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) - for i, cluster in ipairs(cluster_subscribe_list) do - if i > 1 then - subscribe_request:merge(cluster:subscribe(mock_device)) - end - end - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - - -- the following subscribe is due to the init event sent by the test framework. - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.disable_startup_messages() test.mock_device.add_test_device(mock_device) end test.set_test_init_function(test_init) - test.register_coroutine_test( - "Test profile change on init for water valve parent cluster as server", + "Device should be added with correct subscription and profile", function() + local cluster_subscribe_list = { + clusters.ValveConfigurationAndControl.attributes.CurrentState, + clusters.ValveConfigurationAndControl.attributes.CurrentLevel + } + 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, "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 = "water-valve-level" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end, - { - min_api_version = 17 - } + end ) test.register_message_test( @@ -147,16 +140,7 @@ test.register_message_test( mock_device.id, clusters.ValveConfigurationAndControl.server.commands.Open(mock_device, 1, nil, 25) } - } - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Set level command should send the appropriate commands", - { + }, { channel = "capability", direction = "receive", @@ -196,13 +180,6 @@ test.register_message_test( message = mock_device:generate_test_message("main", capabilities.valve.valve.closed()) }, }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Current state reports should generate appropriate events", { { channel = "matter", @@ -217,15 +194,6 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.open()) }, - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Current state reports should generate appropriate events", - { { channel = "matter", direction = "receive", 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 7976b27a4a..5b1b569b25 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 @@ -145,8 +145,6 @@ local function test_init_mock_3switch() } test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch) - test.socket.device_lifecycle:__queue_receive({ mock_3switch.id, "added" }) - test.socket.matter:__expect_send({mock_3switch.id, subscribe_request}) -- the following subscribe is due to the init event sent by the test framework. test.socket.matter:__expect_send({mock_3switch.id, subscribe_request}) @@ -161,8 +159,6 @@ local function test_init_mock_2switch() } test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_2switch) - test.socket.device_lifecycle:__queue_receive({ mock_2switch.id, "added" }) - test.socket.matter:__expect_send({mock_2switch.id, subscribe_request}) test.socket.matter:__expect_send({mock_2switch.id, subscribe_request}) test.mock_device.add_test_device(mock_2switch) @@ -176,8 +172,6 @@ local function test_init_mock_3switch_non_sequential() } test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch_non_sequential) - test.socket.device_lifecycle:__queue_receive({ mock_3switch_non_sequential.id, "added" }) - test.socket.matter:__expect_send({mock_3switch_non_sequential.id, subscribe_request}) test.socket.matter:__expect_send({mock_3switch_non_sequential.id, subscribe_request}) test.mock_device.add_test_device(mock_3switch_non_sequential) From d116b07ae2f30aac870929dbb1995bf1c838f9e7 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu, 7 May 2026 14:52:20 -0500 Subject: [PATCH 342/538] add clear user failure test during defaultSchedule (#2950) --- .../src/test/test_new_matter_lock.lua | 110 +++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index bc9f5751c6..defeafe8e9 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -2100,7 +2100,6 @@ test.register_coroutine_test( } ) --- mock_device:set_field(lock_utils.COTA_CRED, "654123", {persist = true}) --overwrite random cred for test expectation test.register_coroutine_test( "Add Guest User and failure response ", function() @@ -2232,4 +2231,113 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Add Guest User and failure response, and ClearUser command fails as well", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockCredentials.ID, + command = "addCredential", + args = {0, "guest", "pin", "654123"} + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + {credential_type = DoorLock.types.CredentialTypeEnum.PIN, credential_index = 1} + ), -- credential + "654123", -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({{userIndex = 1, userType = "guest"}}, {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials( + {{credentialIndex=1, credentialType="pin", userIndex=1}}, {visibility={displayed=false}} + ) + ) + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetYearDaySchedule( + mock_device, 1, -- endpoint + 1, -- year_day_index + 1, -- user_index + 0, -- local_start_time + 0xffffffff -- local_end_time + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.SetYearDaySchedule:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.FAILURE -- status + ), + } + ) + test.socket.matter:__expect_send({ + mock_device.id, + DoorLock.server.commands.ClearUser( + mock_device, 1, + 1 + ) + }) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.ClearUser:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.FAILURE -- status + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.commandResult( + {commandName="addCredential", statusCode="success", userIndex=1}, {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + + test.run_registered_tests() From b72fbc9f64580d2359146fe4a9ed20e9ffd2e005 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu, 7 May 2026 14:56:23 -0500 Subject: [PATCH 343/538] update st energy reporting interval for multi-plugs (#2803) --- .../matter-switch/src/switch_utils/fields.lua | 1 + .../matter-switch/src/switch_utils/utils.lua | 4 ++-- .../src/test/test_electrical_sensor_set.lua | 21 +++++++++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 875589f227..a8749a6480 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -99,6 +99,7 @@ SwitchFields.updated_fields = { { 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 }, + { current_field_name = "__last_imported_report_timestamp", updated_field_name = nil }, } SwitchFields.vendor_overrides = { diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index e7f5a96187..93936e6093 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -392,13 +392,13 @@ 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 + local last_time = utils.get_field_for_endpoint(device, fields.LAST_IMPORTED_REPORT_TIMESTAMP, endpoint_id) 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 }) + utils.set_field_for_endpoint(device, fields.LAST_IMPORTED_REPORT_TIMESTAMP, endpoint_id, 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 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 index 2c53a14ada..f7a0ae32bc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -478,8 +478,14 @@ test.register_coroutine_test( 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.socket.capability:__expect_send( + mock_child: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.wait_for_events() test.mock_time.advance_time(1500) @@ -514,7 +520,7 @@ test.register_coroutine_test( mock_child:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ start = "1970-01-01T00:15:01Z", ["end"] = "1970-01-01T00:40:00Z", - deltaEnergy = 0.0, + deltaEnergy = 1.0, energy = 20.0 })) ) @@ -531,7 +537,14 @@ test.register_coroutine_test( 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). + 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 = 1.0, + energy = 20.0 + })) + ) end, { test_init = test_init, From 7dc266aefe289c5ce47d4ee0de49323db030a343 Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Fri, 8 May 2026 09:04:48 -0500 Subject: [PATCH 344/538] IKEA BILRESA Scroll: Populate subscribed attributes key to fix refresh (#2841) `SUBSCRIBED_ATTRIBUTES_KEY` should be populated in `IkeaScrollUtils.subscribe` to ensure that attributes are read following a refresh command. --- .../sub_drivers/ikea_scroll/scroll_utils/utils.lua | 10 +++++++++- .../matter-switch/src/test/test_ikea_scroll.lua | 12 ++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) 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 index 9a9f95228b..5e249955e2 100644 --- 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 @@ -4,6 +4,7 @@ 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 switch_fields = require "switch_utils.fields" local IkeaScrollUtils = {} @@ -28,11 +29,18 @@ function IkeaScrollUtils.subscribe(device) subscribe_request:with_info_block(ib) end end + local cluster_id = clusters.PowerSource.ID + local attr_id = clusters.PowerSource.attributes.BatPercentRemaining.ID local ib = im.InteractionInfoBlock( - scroll_fields.ENDPOINT_POWER_SOURCE, clusters.PowerSource.ID, clusters.PowerSource.attributes.BatPercentRemaining.ID + scroll_fields.ENDPOINT_POWER_SOURCE, cluster_id, attr_id ) subscribe_request:with_info_block(ib) device:send(subscribe_request) + + local subscribed_attrs = device:get_field(switch_fields.SUBSCRIBED_ATTRIBUTES_KEY) or {} + subscribed_attrs[cluster_id] = subscribed_attrs[cluster_id] or {} + subscribed_attrs[cluster_id][attr_id] = ib + device:set_field(switch_fields.SUBSCRIBED_ATTRIBUTES_KEY, subscribed_attrs) end return IkeaScrollUtils \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua index e07edf6fc9..5a33424e1f 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua @@ -781,4 +781,16 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Refresh necessary attributes", + function() + test.socket.capability:__queue_receive( + {mock_ikea_scroll.id, {capability = "refresh", component = "main", command = "refresh", args = {}}} + ) + local read_request = clusters.PowerSource.attributes.BatPercentRemaining:read(mock_ikea_scroll, 0) + test.socket.matter:__expect_send({mock_ikea_scroll.id, read_request}) + test.wait_for_events() + end +) + test.run_registered_tests() From a4ddf6cde2137d32f6971cc2f5fa43bcf8257302 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Fri, 8 May 2026 15:47:27 -0500 Subject: [PATCH 345/538] WWSTCERT-11539 Smart AC Controller (#2953) --- drivers/SmartThings/matter-thermostat/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-thermostat/fingerprints.yml b/drivers/SmartThings/matter-thermostat/fingerprints.yml index cd1e7c5cbd..814261790e 100644 --- a/drivers/SmartThings/matter-thermostat/fingerprints.yml +++ b/drivers/SmartThings/matter-thermostat/fingerprints.yml @@ -101,6 +101,12 @@ matterManufacturer: vendorId: 0x156A productId: 0x0001 deviceProfileName: air-purifier-hepa-wind-aqs-pm25-meas-pm25-level + #Gerlsair + - id: "5493/1" + deviceLabel: Smart AC Controller + vendorId: 0x1575 + productId: 0x0001 + deviceProfileName: thermostat-modular matterGeneric: - id: "matter/hvac/heatcool" deviceLabel: Matter Thermostat From d8f43d02c85665475f7e12a232c6afca78d2b8c4 Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Tue, 12 May 2026 13:40:13 +0900 Subject: [PATCH 346/538] Add Allegion(Schlage) products to allowed list (#2935) Signed-off-by: Hunsup Jung --- .../matter-lock/src/new-matter-lock/fingerprints.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index 901c0ea39c..ac8aa0c07c 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -34,6 +34,8 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x1421, 0x0042}, -- Kwikset Halo Select Plus {0x1421, 0x0081}, -- Kwikset Aura Reach {0x1236, 0xa538}, -- Schlage Sense Pro + {0x1236, 0x3800}, -- Schlage + {0x1236, 0xA738} -- Schlage } return NEW_MATTER_LOCK_PRODUCTS From 834b3dbad2e7500c11c4c5091c2d7d45844283aa Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 11 May 2026 12:27:44 -0500 Subject: [PATCH 347/538] Changing sha to point at pull_request.head.sha --- .github/workflows/jenkins-driver-tests.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/jenkins-driver-tests.yml b/.github/workflows/jenkins-driver-tests.yml index f04d237442..b9fc07f091 100644 --- a/.github/workflows/jenkins-driver-tests.yml +++ b/.github/workflows/jenkins-driver-tests.yml @@ -19,14 +19,15 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd with: script: | - core.setOutput('status_url', (await github.rest.repos.createCommitStatus({ + let response = await github.rest.repos.createCommitStatus({ owner: context.repo.owner, repo: context.repo.repo, - sha: context.sha, + sha: context.payload.pull_request.head.sha, state: 'pending', description: 'Jenkins job triggered...', context: 'Driver Tests (${{ matrix.version }})' - })).data.url); + }); + core.setOutput('status_url', response.data.url); - name: Trigger Jenkins Generic Webhook env: JENKINS_WEBHOOK_TOKEN: ${{ secrets.JENKINS_WEBHOOK_TOKEN }} From e645be8db6264f95dd88224a702f23d02003b439 Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Tue, 12 May 2026 14:00:53 -0500 Subject: [PATCH 348/538] Add support for Closure device type (#2875) Add support for the Closure device type as a sub-driver in Matter Window Covering. --- .../matter-window-covering/fingerprints.yml | 5 + .../profiles/covering.yml | 56 ++ .../matter-window-covering/profiles/door.yml | 56 ++ .../profiles/garage-door.yml | 56 ++ .../matter-window-covering/profiles/gate.yml | 56 ++ .../embedded_clusters/ClosureControl/init.lua | 97 +++ .../server/attributes/AcceptedCommandList.lua | 74 ++ .../server/attributes/AttributeList.lua | 74 ++ .../server/attributes/CountdownTime.lua | 67 ++ .../server/attributes/CurrentErrorList.lua | 74 ++ .../server/attributes/LatchControlModes.lua | 67 ++ .../server/attributes/MainState.lua | 67 ++ .../server/attributes/OverallCurrentState.lua | 67 ++ .../server/attributes/OverallTargetState.lua | 67 ++ .../ClosureControl/server/attributes/init.lua | 19 + .../server/commands/Calibrate.lua | 91 ++ .../ClosureControl/server/commands/MoveTo.lua | 112 +++ .../ClosureControl/server/commands/Stop.lua | 90 ++ .../ClosureControl/server/commands/init.lua | 19 + .../ClosureControl/types/ClosureErrorEnum.lua | 36 + .../types/CurrentPositionEnum.lua | 39 + .../ClosureControl/types/Feature.lua | 228 +++++ .../types/LatchControlModesBitmap.lua | 64 ++ .../ClosureControl/types/MainStateEnum.lua | 45 + .../types/OverallCurrentStateStruct.lua | 91 ++ .../types/OverallTargetStateStruct.lua | 84 ++ .../types/TargetPositionEnum.lua | 36 + .../ClosureControl/types/init.lua | 10 + .../ClosureDimension/init.lua | 106 +++ .../server/attributes/CurrentState.lua | 67 ++ .../server/attributes/LimitRange.lua | 67 ++ .../server/attributes/StepValue.lua | 67 ++ .../server/attributes/TargetState.lua | 67 ++ .../server/attributes/init.lua | 19 + .../server/commands/SetTarget.lua | 112 +++ .../ClosureDimension/server/commands/Step.lua | 112 +++ .../ClosureDimension/server/commands/init.lua | 19 + .../types/DimensionStateStruct.lua | 84 ++ .../ClosureDimension/types/Feature.lua | 207 +++++ .../types/StepDirectionEnum.lua | 27 + .../types/TranslationDirectionEnum.lua | 57 ++ .../ClosureDimension/types/init.lua | 10 + .../matter-window-covering/src/init.lua | 2 + .../src/sub_drivers.lua | 3 +- .../src/sub_drivers/closure/can_handle.lua | 11 + .../closure_handlers/attribute_handlers.lua | 110 +++ .../closure_handlers/capability_handlers.lua | 71 ++ .../closure/closure_utils/fields.lua | 37 + .../closure/closure_utils/utils.lua | 318 +++++++ .../src/sub_drivers/closure/init.lua | 140 ++++ .../can_handle.lua | 4 +- .../fingerprints.lua | 0 .../init.lua | 0 .../src/test/test_matter_closure.lua | 785 ++++++++++++++++++ 54 files changed, 4346 insertions(+), 3 deletions(-) create mode 100644 drivers/SmartThings/matter-window-covering/profiles/covering.yml create mode 100644 drivers/SmartThings/matter-window-covering/profiles/door.yml create mode 100644 drivers/SmartThings/matter-window-covering/profiles/garage-door.yml create mode 100644 drivers/SmartThings/matter-window-covering/profiles/gate.yml create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/AcceptedCommandList.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/AttributeList.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/CountdownTime.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/CurrentErrorList.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/LatchControlModes.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/MainState.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/OverallCurrentState.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/OverallTargetState.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/Calibrate.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/MoveTo.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/Stop.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/ClosureErrorEnum.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/CurrentPositionEnum.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/Feature.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/LatchControlModesBitmap.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/MainStateEnum.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallCurrentStateStruct.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallTargetStateStruct.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/TargetPositionEnum.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/CurrentState.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/LimitRange.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/StepValue.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/TargetState.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/SetTarget.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/Step.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/DimensionStateStruct.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/Feature.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/StepDirectionEnum.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/TranslationDirectionEnum.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/can_handle.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/attribute_handlers.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/capability_handlers.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/fields.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/utils.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/init.lua rename drivers/SmartThings/matter-window-covering/src/{ => sub_drivers}/matter-window-covering-position-updates-while-moving/can_handle.lua (72%) rename drivers/SmartThings/matter-window-covering/src/{ => sub_drivers}/matter-window-covering-position-updates-while-moving/fingerprints.lua (100%) rename drivers/SmartThings/matter-window-covering/src/{ => sub_drivers}/matter-window-covering-position-updates-while-moving/init.lua (100%) create mode 100644 drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua diff --git a/drivers/SmartThings/matter-window-covering/fingerprints.yml b/drivers/SmartThings/matter-window-covering/fingerprints.yml index 531dc8c36f..34406cf01c 100644 --- a/drivers/SmartThings/matter-window-covering/fingerprints.yml +++ b/drivers/SmartThings/matter-window-covering/fingerprints.yml @@ -261,3 +261,8 @@ matterGeneric: deviceTypes: - id: 0x0202 # Window Covering deviceProfileName: window-covering + - id: "closure" + deviceLabel: Matter Closure + deviceTypes: + - id: 0x0230 # Closure + deviceProfileName: covering diff --git a/drivers/SmartThings/matter-window-covering/profiles/covering.yml b/drivers/SmartThings/matter-window-covering/profiles/covering.yml new file mode 100644 index 0000000000..11e2319a5a --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/profiles/covering.yml @@ -0,0 +1,56 @@ +name: covering +components: +- id: main + capabilities: + - id: windowShade + version: 1 + - id: windowShadeLevel + version: 1 + optional: true + - id: battery + version: 1 + optional: true + - id: batteryLevel + version: 1 + optional: true + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Blind +- id: windowShade1 + optional: true + capabilities: + - id: windowShadeLevel + version: 1 + optional: true + categories: + - name: Blind +- id: windowShade2 + optional: true + capabilities: + - id: windowShadeLevel + version: 1 + optional: true + categories: + - name: Blind +- id: windowShade3 + optional: true + capabilities: + - id: windowShadeLevel + version: 1 + optional: true + categories: + - name: Blind +- id: windowShade4 + optional: true + capabilities: + - id: windowShadeLevel + version: 1 + optional: true + categories: + - name: Blind +preferences: + - preferenceId: reverse + explicit: true diff --git a/drivers/SmartThings/matter-window-covering/profiles/door.yml b/drivers/SmartThings/matter-window-covering/profiles/door.yml new file mode 100644 index 0000000000..bd19c2dc8f --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/profiles/door.yml @@ -0,0 +1,56 @@ +name: door +components: +- id: main + capabilities: + - id: doorControl + version: 1 + - id: level + version: 1 + optional: true + - id: battery + version: 1 + optional: true + - id: batteryLevel + version: 1 + optional: true + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Door +- id: door1 + optional: true + capabilities: + - id: level + version: 1 + optional: true + categories: + - name: Door +- id: door2 + optional: true + capabilities: + - id: level + version: 1 + optional: true + categories: + - name: Door +- id: door3 + optional: true + capabilities: + - id: level + version: 1 + optional: true + categories: + - name: Door +- id: door4 + optional: true + capabilities: + - id: level + version: 1 + optional: true + categories: + - name: Door +preferences: + - preferenceId: reverse + explicit: true diff --git a/drivers/SmartThings/matter-window-covering/profiles/garage-door.yml b/drivers/SmartThings/matter-window-covering/profiles/garage-door.yml new file mode 100644 index 0000000000..a75ae63580 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/profiles/garage-door.yml @@ -0,0 +1,56 @@ +name: garage-door +components: +- id: main + capabilities: + - id: doorControl + version: 1 + - id: level + version: 1 + optional: true + - id: battery + version: 1 + optional: true + - id: batteryLevel + version: 1 + optional: true + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: GarageDoor +- id: door1 + optional: true + capabilities: + - id: level + version: 1 + optional: true + categories: + - name: GarageDoor +- id: door2 + optional: true + capabilities: + - id: level + version: 1 + optional: true + categories: + - name: GarageDoor +- id: door3 + optional: true + capabilities: + - id: level + version: 1 + optional: true + categories: + - name: GarageDoor +- id: door4 + optional: true + capabilities: + - id: level + version: 1 + optional: true + categories: + - name: GarageDoor +preferences: + - preferenceId: reverse + explicit: true diff --git a/drivers/SmartThings/matter-window-covering/profiles/gate.yml b/drivers/SmartThings/matter-window-covering/profiles/gate.yml new file mode 100644 index 0000000000..8cc49b425f --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/profiles/gate.yml @@ -0,0 +1,56 @@ +name: gate +components: +- id: main + capabilities: + - id: doorControl + version: 1 + - id: level + version: 1 + optional: true + - id: battery + version: 1 + optional: true + - id: batteryLevel + version: 1 + optional: true + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Door +- id: door1 + optional: true + capabilities: + - id: level + version: 1 + optional: true + categories: + - name: Door +- id: door2 + optional: true + capabilities: + - id: level + version: 1 + optional: true + categories: + - name: Door +- id: door3 + optional: true + capabilities: + - id: level + version: 1 + optional: true + categories: + - name: Door +- id: door4 + optional: true + capabilities: + - id: level + version: 1 + optional: true + categories: + - name: Door +preferences: + - preferenceId: reverse + explicit: true diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/init.lua new file mode 100644 index 0000000000..76adc03375 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/init.lua @@ -0,0 +1,97 @@ +local cluster_base = require "st.matter.cluster_base" +local ClosureControlServerAttributes = require "embedded_clusters.ClosureControl.server.attributes" +local ClosureControlServerCommands = require "embedded_clusters.ClosureControl.server.commands" +local ClosureControlTypes = require "embedded_clusters.ClosureControl.types" + +local ClosureControl = {} + +ClosureControl.ID = 0x0104 +ClosureControl.NAME = "ClosureControl" +ClosureControl.server = {} +ClosureControl.client = {} +ClosureControl.server.attributes = ClosureControlServerAttributes:set_parent_cluster(ClosureControl) +ClosureControl.server.commands = ClosureControlServerCommands:set_parent_cluster(ClosureControl) +ClosureControl.types = ClosureControlTypes + +function ClosureControl:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "CountdownTime", + [0x0001] = "MainState", + [0x0002] = "CurrentErrorList", + [0x0003] = "OverallCurrentState", + [0x0004] = "OverallTargetState", + [0x0005] = "LatchControlModes", + [0xFFF9] = "AcceptedCommandList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function ClosureControl:get_server_command_by_id(command_id) + local server_id_map = { + [0x0000] = "Stop", + [0x0001] = "MoveTo", + [0x0002] = "Calibrate", + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + + +ClosureControl.attribute_direction_map = { + ["CountdownTime"] = "server", + ["MainState"] = "server", + ["CurrentErrorList"] = "server", + ["OverallCurrentState"] = "server", + ["OverallTargetState"] = "server", + ["LatchControlModes"] = "server", + ["AcceptedCommandList"] = "server", + ["AttributeList"] = "server", +} + +ClosureControl.command_direction_map = { + ["Stop"] = "server", + ["MoveTo"] = "server", + ["Calibrate"] = "server", +} + +ClosureControl.FeatureMap = ClosureControl.types.Feature + +function ClosureControl.are_features_supported(feature, feature_map) + if (ClosureControl.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 = ClosureControl.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, ClosureControl.NAME)) + end + return ClosureControl[direction].attributes[key] +end +ClosureControl.attributes = {} +setmetatable(ClosureControl.attributes, attribute_helper_mt) + +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = ClosureControl.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, ClosureControl.NAME)) + end + return ClosureControl[direction].commands[key] +end +ClosureControl.commands = {} +setmetatable(ClosureControl.commands, command_helper_mt) + +setmetatable(ClosureControl, {__index = cluster_base}) + +return ClosureControl diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/AcceptedCommandList.lua new file mode 100644 index 0000000000..f86ac60dbe --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/AcceptedCommandList.lua @@ -0,0 +1,74 @@ +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-window-covering/src/embedded_clusters/ClosureControl/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/AttributeList.lua new file mode 100644 index 0000000000..7f6827b026 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/AttributeList.lua @@ -0,0 +1,74 @@ +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 + ) +end + +function AttributeList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +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-window-covering/src/embedded_clusters/ClosureControl/server/attributes/CountdownTime.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/CountdownTime.lua new file mode 100644 index 0000000000..b6e4f861b6 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/CountdownTime.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CountdownTime = { + ID = 0x0000, + NAME = "CountdownTime", + base_type = require "st.matter.data_types.Uint32", +} + +function CountdownTime:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function CountdownTime:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CountdownTime:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CountdownTime:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CountdownTime: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 CountdownTime:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(CountdownTime, {__call = CountdownTime.new_value, __index = CountdownTime.base_type}) +return CountdownTime diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/CurrentErrorList.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/CurrentErrorList.lua new file mode 100644 index 0000000000..21076e7e5d --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/CurrentErrorList.lua @@ -0,0 +1,74 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CurrentErrorList = { + ID = 0x0002, + NAME = "CurrentErrorList", + base_type = require "st.matter.data_types.Array", + element_type = require "embedded_clusters.ClosureControl.types.ClosureErrorEnum", +} + +function CurrentErrorList: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, CurrentErrorList.element_type) + end +end + +function CurrentErrorList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function CurrentErrorList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentErrorList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentErrorList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CurrentErrorList: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 CurrentErrorList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(CurrentErrorList, {__call = CurrentErrorList.new_value, __index = CurrentErrorList.base_type}) +return CurrentErrorList diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/LatchControlModes.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/LatchControlModes.lua new file mode 100644 index 0000000000..d62831b4f3 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/LatchControlModes.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local LatchControlModes = { + ID = 0x0005, + NAME = "LatchControlModes", + base_type = require "embedded_clusters.ClosureControl.types.LatchControlModesBitmap", +} + +function LatchControlModes:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function LatchControlModes:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function LatchControlModes:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function LatchControlModes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LatchControlModes: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 LatchControlModes:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(LatchControlModes, {__call = LatchControlModes.new_value, __index = LatchControlModes.base_type}) +return LatchControlModes diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/MainState.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/MainState.lua new file mode 100644 index 0000000000..5f1bbc06e2 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/MainState.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local MainState = { + ID = 0x0001, + NAME = "MainState", + base_type = require "embedded_clusters.ClosureControl.types.MainStateEnum", +} + +function MainState:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function MainState:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function MainState:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function MainState:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MainState: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 MainState:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MainState, {__call = MainState.new_value, __index = MainState.base_type}) +return MainState diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/OverallCurrentState.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/OverallCurrentState.lua new file mode 100644 index 0000000000..f93477ec47 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/OverallCurrentState.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local OverallCurrentState = { + ID = 0x0003, + NAME = "OverallCurrentState", + base_type = require "embedded_clusters.ClosureControl.types.OverallCurrentStateStruct", +} + +function OverallCurrentState:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function OverallCurrentState:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function OverallCurrentState:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function OverallCurrentState:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function OverallCurrentState: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 OverallCurrentState:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(OverallCurrentState, {__call = OverallCurrentState.new_value, __index = OverallCurrentState.base_type}) +return OverallCurrentState diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/OverallTargetState.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/OverallTargetState.lua new file mode 100644 index 0000000000..6c8cbb9ee3 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/OverallTargetState.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local OverallTargetState = { + ID = 0x0004, + NAME = "OverallTargetState", + base_type = require "embedded_clusters.ClosureControl.types.OverallTargetStateStruct", +} + +function OverallTargetState:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function OverallTargetState:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function OverallTargetState:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function OverallTargetState:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function OverallTargetState: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 OverallTargetState:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(OverallTargetState, {__call = OverallTargetState.new_value, __index = OverallTargetState.base_type}) +return OverallTargetState diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/init.lua new file mode 100644 index 0000000000..0c71152760 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/attributes/init.lua @@ -0,0 +1,19 @@ +local attr_mt = {} +attr_mt.__index = function(self, key) + local req_loc = string.format("embedded_clusters.ClosureControl.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 ClosureControlServerAttributes = {} + +function ClosureControlServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ClosureControlServerAttributes, attr_mt) + +return ClosureControlServerAttributes diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/Calibrate.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/Calibrate.lua new file mode 100644 index 0000000000..1a2931c0ad --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/Calibrate.lua @@ -0,0 +1,91 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local Calibrate = {} + +Calibrate.NAME = "Calibrate" +Calibrate.ID = 0x0002 +Calibrate.field_defs = { +} + +function Calibrate:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, + status + ) +end + +function Calibrate:init(device, endpoint_id) + local out = {} + local args = {} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.is_optional and args[i] == nil then + out[v.name] = nil + elseif v.is_nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.is_optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = Calibrate, + __tostring = Calibrate.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID, + true + ) +end + +function Calibrate:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function Calibrate: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 + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == 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 Calibrate:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(Calibrate, {__call = Calibrate.init}) + +return Calibrate diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/MoveTo.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/MoveTo.lua new file mode 100644 index 0000000000..80c1bca52d --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/MoveTo.lua @@ -0,0 +1,112 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local MoveTo = {} + +MoveTo.NAME = "MoveTo" +MoveTo.ID = 0x0001 +MoveTo.field_defs = { + { + name = "position", + field_id = 0, + is_nullable = false, + is_optional = true, + data_type = require "embedded_clusters.ClosureControl.types.TargetPositionEnum", + }, + { + name = "latch", + field_id = 1, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Boolean", + }, + { + name = "speed", + field_id = 2, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + }, +} + +function MoveTo:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, + status + ) +end + +function MoveTo:init(device, endpoint_id, position, latch, speed) + local out = {} + local args = {position, latch, speed} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.is_optional and args[i] == nil then + out[v.name] = nil + elseif v.is_nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.is_optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = MoveTo, + __tostring = MoveTo.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID, + true + ) +end + +function MoveTo:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MoveTo: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 + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == 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 MoveTo:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MoveTo, {__call = MoveTo.init}) + +return MoveTo diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/Stop.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/Stop.lua new file mode 100644 index 0000000000..9ac41ba122 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/Stop.lua @@ -0,0 +1,90 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local Stop = {} + +Stop.NAME = "Stop" +Stop.ID = 0x0000 +Stop.field_defs = { +} + +function Stop:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, + status + ) +end + +function Stop:init(device, endpoint_id) + local out = {} + local args = {} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.is_optional and args[i] == nil then + out[v.name] = nil + elseif v.is_nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.is_optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = Stop, + __tostring = Stop.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID + ) +end + +function Stop:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function Stop: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 + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == 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 Stop:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(Stop, {__call = Stop.init}) + +return Stop diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/init.lua new file mode 100644 index 0000000000..1125ef4296 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/init.lua @@ -0,0 +1,19 @@ +local command_mt = {} +command_mt.__index = function(self, key) + local req_loc = string.format("embedded_clusters.ClosureControl.server.commands.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + return raw_def +end + +local ClosureControlServerCommands = {} + +function ClosureControlServerCommands:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ClosureControlServerCommands, command_mt) + +return ClosureControlServerCommands diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/ClosureErrorEnum.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/ClosureErrorEnum.lua new file mode 100644 index 0000000000..5e5a09da56 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/ClosureErrorEnum.lua @@ -0,0 +1,36 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local ClosureErrorEnum = {} +local new_mt = UintABC.new_mt({NAME = "ClosureErrorEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.PHYSICALLY_BLOCKED] = "PHYSICALLY_BLOCKED", + [self.BLOCKED_BY_SENSOR] = "BLOCKED_BY_SENSOR", + [self.TEMPERATURE_LIMITED] = "TEMPERATURE_LIMITED", + [self.MAINTENANCE_REQUIRED] = "MAINTENANCE_REQUIRED", + [self.INTERNAL_INTERFERENCE] = "INTERNAL_INTERFERENCE", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.PHYSICALLY_BLOCKED = 0x00 +new_mt.__index.BLOCKED_BY_SENSOR = 0x01 +new_mt.__index.TEMPERATURE_LIMITED = 0x02 +new_mt.__index.MAINTENANCE_REQUIRED = 0x03 +new_mt.__index.INTERNAL_INTERFERENCE = 0x04 + +ClosureErrorEnum.PHYSICALLY_BLOCKED = 0x00 +ClosureErrorEnum.BLOCKED_BY_SENSOR = 0x01 +ClosureErrorEnum.TEMPERATURE_LIMITED = 0x02 +ClosureErrorEnum.MAINTENANCE_REQUIRED = 0x03 +ClosureErrorEnum.INTERNAL_INTERFERENCE = 0x04 + +ClosureErrorEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(ClosureErrorEnum, new_mt) + +return ClosureErrorEnum diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/CurrentPositionEnum.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/CurrentPositionEnum.lua new file mode 100644 index 0000000000..a5fee86d89 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/CurrentPositionEnum.lua @@ -0,0 +1,39 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local CurrentPositionEnum = {} +local new_mt = UintABC.new_mt({NAME = "CurrentPositionEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.FULLY_CLOSED] = "FULLY_CLOSED", + [self.FULLY_OPENED] = "FULLY_OPENED", + [self.PARTIALLY_OPENED] = "PARTIALLY_OPENED", + [self.OPENED_FOR_PEDESTRIAN] = "OPENED_FOR_PEDESTRIAN", + [self.OPENED_FOR_VENTILATION] = "OPENED_FOR_VENTILATION", + [self.OPENED_AT_SIGNATURE] = "OPENED_AT_SIGNATURE", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.FULLY_CLOSED = 0x00 +new_mt.__index.FULLY_OPENED = 0x01 +new_mt.__index.PARTIALLY_OPENED = 0x02 +new_mt.__index.OPENED_FOR_PEDESTRIAN = 0x03 +new_mt.__index.OPENED_FOR_VENTILATION = 0x04 +new_mt.__index.OPENED_AT_SIGNATURE = 0x05 + +CurrentPositionEnum.FULLY_CLOSED = 0x00 +CurrentPositionEnum.FULLY_OPENED = 0x01 +CurrentPositionEnum.PARTIALLY_OPENED = 0x02 +CurrentPositionEnum.OPENED_FOR_PEDESTRIAN = 0x03 +CurrentPositionEnum.OPENED_FOR_VENTILATION = 0x04 +CurrentPositionEnum.OPENED_AT_SIGNATURE = 0x05 + +CurrentPositionEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(CurrentPositionEnum, new_mt) + +return CurrentPositionEnum diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/Feature.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/Feature.lua new file mode 100644 index 0000000000..e41485dbe7 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/Feature.lua @@ -0,0 +1,228 @@ +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.POSITIONING = 0x0001 +Feature.MOTION_LATCHING = 0x0002 +Feature.INSTANTANEOUS = 0x0004 +Feature.SPEED = 0x0008 +Feature.VENTILATION = 0x0010 +Feature.PEDESTRIAN = 0x0020 +Feature.CALIBRATION = 0x0040 +Feature.PROTECTION = 0x0080 +Feature.MANUALLY_OPERABLE = 0x0100 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + POSITIONING = 0x0001, + MOTION_LATCHING = 0x0002, + INSTANTANEOUS = 0x0004, + SPEED = 0x0008, + VENTILATION = 0x0010, + PEDESTRIAN = 0x0020, + CALIBRATION = 0x0040, + PROTECTION = 0x0080, + MANUALLY_OPERABLE = 0x0100, +} + +Feature.is_positioning_set = function(self) + return (self.value & self.POSITIONING) ~= 0 +end + +Feature.set_positioning = function(self) + if self.value ~= nil then + self.value = self.value | self.POSITIONING + else + self.value = self.POSITIONING + end +end + +Feature.unset_positioning = function(self) + self.value = self.value & (~self.POSITIONING & self.BASE_MASK) +end +Feature.is_motion_latching_set = function(self) + return (self.value & self.MOTION_LATCHING) ~= 0 +end + +Feature.set_motion_latching = function(self) + if self.value ~= nil then + self.value = self.value | self.MOTION_LATCHING + else + self.value = self.MOTION_LATCHING + end +end + +Feature.unset_motion_latching = function(self) + self.value = self.value & (~self.MOTION_LATCHING & self.BASE_MASK) +end + +Feature.is_instantaneous_set = function(self) + return (self.value & self.INSTANTANEOUS) ~= 0 +end + +Feature.set_instantaneous = function(self) + if self.value ~= nil then + self.value = self.value | self.INSTANTANEOUS + else + self.value = self.INSTANTANEOUS + end +end + +Feature.unset_instantaneous = function(self) + self.value = self.value & (~self.INSTANTANEOUS & self.BASE_MASK) +end + +Feature.is_speed_set = function(self) + return (self.value & self.SPEED) ~= 0 +end + +Feature.set_speed = function(self) + if self.value ~= nil then + self.value = self.value | self.SPEED + else + self.value = self.SPEED + end +end + +Feature.unset_speed = function(self) + self.value = self.value & (~self.SPEED & self.BASE_MASK) +end + +Feature.is_ventilation_set = function(self) + return (self.value & self.VENTILATION) ~= 0 +end + +Feature.set_ventilation = function(self) + if self.value ~= nil then + self.value = self.value | self.VENTILATION + else + self.value = self.VENTILATION + end +end + +Feature.unset_ventilation = function(self) + self.value = self.value & (~self.VENTILATION & self.BASE_MASK) +end + +Feature.is_pedestrian_set = function(self) + return (self.value & self.PEDESTRIAN) ~= 0 +end + +Feature.set_pedestrian = function(self) + if self.value ~= nil then + self.value = self.value | self.PEDESTRIAN + else + self.value = self.PEDESTRIAN + end +end + +Feature.unset_pedestrian = function(self) + self.value = self.value & (~self.PEDESTRIAN & self.BASE_MASK) +end + +Feature.is_calibration_set = function(self) + return (self.value & self.CALIBRATION) ~= 0 +end + +Feature.set_calibration = function(self) + if self.value ~= nil then + self.value = self.value | self.CALIBRATION + else + self.value = self.CALIBRATION + end +end + +Feature.unset_calibration = function(self) + self.value = self.value & (~self.CALIBRATION & self.BASE_MASK) +end + +Feature.is_protection_set = function(self) + return (self.value & self.PROTECTION) ~= 0 +end + +Feature.set_protection = function(self) + if self.value ~= nil then + self.value = self.value | self.PROTECTION + else + self.value = self.PROTECTION + end +end + +Feature.unset_protection = function(self) + self.value = self.value & (~self.PROTECTION & self.BASE_MASK) +end + +Feature.is_manually_operable_set = function(self) + return (self.value & self.MANUALLY_OPERABLE) ~= 0 +end + +Feature.set_manually_operable = function(self) + if self.value ~= nil then + self.value = self.value | self.MANUALLY_OPERABLE + else + self.value = self.MANUALLY_OPERABLE + end +end + +Feature.unset_manually_operable = function(self) + self.value = self.value & (~self.MANUALLY_OPERABLE & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.POSITIONING | + Feature.MOTION_LATCHING | + Feature.INSTANTANEOUS | + Feature.SPEED | + Feature.VENTILATION | + Feature.PEDESTRIAN | + Feature.CALIBRATION | + Feature.PROTECTION | + Feature.MANUALLY_OPERABLE + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_positioning_set = Feature.is_positioning_set, + set_positioning = Feature.set_positioning, + unset_positioning = Feature.unset_positioning, + is_motion_latching_set = Feature.is_motion_latching_set, + set_motion_latching = Feature.set_motion_latching, + unset_motion_latching = Feature.unset_motion_latching, + is_instantaneous_set = Feature.is_instantaneous_set, + set_instantaneous = Feature.set_instantaneous, + unset_instantaneous = Feature.unset_instantaneous, + is_speed_set = Feature.is_speed_set, + set_speed = Feature.set_speed, + unset_speed = Feature.unset_speed, + is_ventilation_set = Feature.is_ventilation_set, + set_ventilation = Feature.set_ventilation, + unset_ventilation = Feature.unset_ventilation, + is_pedestrian_set = Feature.is_pedestrian_set, + set_pedestrian = Feature.set_pedestrian, + unset_pedestrian = Feature.unset_pedestrian, + is_calibration_set = Feature.is_calibration_set, + set_calibration = Feature.set_calibration, + unset_calibration = Feature.unset_calibration, + is_protection_set = Feature.is_protection_set, + set_protection = Feature.set_protection, + unset_protection = Feature.unset_protection, + is_manually_operable_set = Feature.is_manually_operable_set, + set_manually_operable = Feature.set_manually_operable, + unset_manually_operable = Feature.unset_manually_operable, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/LatchControlModesBitmap.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/LatchControlModesBitmap.lua new file mode 100644 index 0000000000..ba6188e4a3 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/LatchControlModesBitmap.lua @@ -0,0 +1,64 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local LatchControlModesBitmap = {} +local new_mt = UintABC.new_mt({NAME = "LatchControlModesBitmap", ID = data_types.name_to_id_map["Uint8"]}, 1) + +LatchControlModesBitmap.BASE_MASK = 0xFFFF +LatchControlModesBitmap.REMOTE_LATCHING = 0x0001 +LatchControlModesBitmap.REMOTE_UNLATCHING = 0x0002 + +LatchControlModesBitmap.mask_fields = { + BASE_MASK = 0xFFFF, + REMOTE_LATCHING = 0x0001, + REMOTE_UNLATCHING = 0x0002, +} + +LatchControlModesBitmap.is_remote_latching_set = function(self) + return (self.value & self.REMOTE_LATCHING) ~= 0 +end + +LatchControlModesBitmap.set_remote_latching = function(self) + if self.value ~= nil then + self.value = self.value | self.REMOTE_LATCHING + else + self.value = self.REMOTE_LATCHING + end +end + +LatchControlModesBitmap.unset_remote_latching = function(self) + self.value = self.value & (~self.REMOTE_LATCHING & self.BASE_MASK) +end + +LatchControlModesBitmap.is_remote_unlatching_set = function(self) + return (self.value & self.REMOTE_UNLATCHING) ~= 0 +end + +LatchControlModesBitmap.set_remote_unlatching = function(self) + if self.value ~= nil then + self.value = self.value | self.REMOTE_UNLATCHING + else + self.value = self.REMOTE_UNLATCHING + end +end + +LatchControlModesBitmap.unset_remote_unlatching = function(self) + self.value = self.value & (~self.REMOTE_UNLATCHING & self.BASE_MASK) +end + +LatchControlModesBitmap.mask_methods = { + is_remote_latching_set = LatchControlModesBitmap.is_remote_latching_set, + set_remote_latching = LatchControlModesBitmap.set_remote_latching, + unset_remote_latching = LatchControlModesBitmap.unset_remote_latching, + is_remote_unlatching_set = LatchControlModesBitmap.is_remote_unlatching_set, + set_remote_unlatching = LatchControlModesBitmap.set_remote_unlatching, + unset_remote_unlatching = LatchControlModesBitmap.unset_remote_unlatching, +} + +LatchControlModesBitmap.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(LatchControlModesBitmap, new_mt) + +return LatchControlModesBitmap diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/MainStateEnum.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/MainStateEnum.lua new file mode 100644 index 0000000000..916e27dda0 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/MainStateEnum.lua @@ -0,0 +1,45 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local MainStateEnum = {} +local new_mt = UintABC.new_mt({NAME = "MainStateEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.STOPPED] = "STOPPED", + [self.MOVING] = "MOVING", + [self.WAITING_FOR_MOTION] = "WAITING_FOR_MOTION", + [self.ERROR] = "ERROR", + [self.CALIBRATING] = "CALIBRATING", + [self.PROTECTED] = "PROTECTED", + [self.DISENGAGED] = "DISENGAGED", + [self.SETUP_REQUIRED] = "SETUP_REQUIRED", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.STOPPED = 0x00 +new_mt.__index.MOVING = 0x01 +new_mt.__index.WAITING_FOR_MOTION = 0x02 +new_mt.__index.ERROR = 0x03 +new_mt.__index.CALIBRATING = 0x04 +new_mt.__index.PROTECTED = 0x05 +new_mt.__index.DISENGAGED = 0x06 +new_mt.__index.SETUP_REQUIRED = 0x07 + +MainStateEnum.STOPPED = 0x00 +MainStateEnum.MOVING = 0x01 +MainStateEnum.WAITING_FOR_MOTION = 0x02 +MainStateEnum.ERROR = 0x03 +MainStateEnum.CALIBRATING = 0x04 +MainStateEnum.PROTECTED = 0x05 +MainStateEnum.DISENGAGED = 0x06 +MainStateEnum.SETUP_REQUIRED = 0x07 + +MainStateEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(MainStateEnum, new_mt) + +return MainStateEnum diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallCurrentStateStruct.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallCurrentStateStruct.lua new file mode 100644 index 0000000000..41af9d66db --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallCurrentStateStruct.lua @@ -0,0 +1,91 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local OverallCurrentStateStruct = {} +local new_mt = StructureABC.new_mt({NAME = "OverallCurrentStateStruct", ID = data_types.name_to_id_map["Structure"]}) + +OverallCurrentStateStruct.field_defs = { + { + name = "position", + field_id = 0, + is_nullable = true, + is_optional = true, + data_type = require "embedded_clusters.ClosureControl.types.CurrentPositionEnum", + }, + { + name = "latch", + field_id = 1, + is_nullable = true, + is_optional = true, + data_type = require "st.matter.data_types.Boolean", + }, + { + name = "speed", + field_id = 2, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + }, + { + name = "secure_state", + field_id = 3, + is_nullable = true, + is_optional = false, + data_type = require "st.matter.data_types.Boolean", + }, +} + +OverallCurrentStateStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +OverallCurrentStateStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = OverallCurrentStateStruct.init +new_mt.__index.serialize = OverallCurrentStateStruct.serialize + +OverallCurrentStateStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + 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 + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(OverallCurrentStateStruct, new_mt) + +return OverallCurrentStateStruct diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallTargetStateStruct.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallTargetStateStruct.lua new file mode 100644 index 0000000000..eac6492815 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallTargetStateStruct.lua @@ -0,0 +1,84 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local OverallTargetStateStruct = {} +local new_mt = StructureABC.new_mt({NAME = "OverallTargetStateStruct", ID = data_types.name_to_id_map["Structure"]}) + +OverallTargetStateStruct.field_defs = { + { + name = "position", + field_id = 0, + is_nullable = true, + is_optional = true, + data_type = require "embedded_clusters.ClosureControl.types.TargetPositionEnum", + }, + { + name = "latch", + field_id = 1, + is_nullable = true, + is_optional = true, + data_type = require "st.matter.data_types.Boolean", + }, + { + name = "speed", + field_id = 2, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + }, +} + +OverallTargetStateStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +OverallTargetStateStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = OverallTargetStateStruct.init +new_mt.__index.serialize = OverallTargetStateStruct.serialize + +OverallTargetStateStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + 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 + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(OverallTargetStateStruct, new_mt) + +return OverallTargetStateStruct diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/TargetPositionEnum.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/TargetPositionEnum.lua new file mode 100644 index 0000000000..b7a6122863 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/TargetPositionEnum.lua @@ -0,0 +1,36 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local TargetPositionEnum = {} +local new_mt = UintABC.new_mt({NAME = "TargetPositionEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.MOVE_TO_FULLY_CLOSED] = "MOVE_TO_FULLY_CLOSED", + [self.MOVE_TO_FULLY_OPEN] = "MOVE_TO_FULLY_OPEN", + [self.MOVE_TO_PEDESTRIAN_POSITION] = "MOVE_TO_PEDESTRIAN_POSITION", + [self.MOVE_TO_VENTILATION_POSITION] = "MOVE_TO_VENTILATION_POSITION", + [self.MOVE_TO_SIGNATURE_POSITION] = "MOVE_TO_SIGNATURE_POSITION", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.MOVE_TO_FULLY_CLOSED = 0x00 +new_mt.__index.MOVE_TO_FULLY_OPEN = 0x01 +new_mt.__index.MOVE_TO_PEDESTRIAN_POSITION = 0x02 +new_mt.__index.MOVE_TO_VENTILATION_POSITION = 0x03 +new_mt.__index.MOVE_TO_SIGNATURE_POSITION = 0x04 + +TargetPositionEnum.MOVE_TO_FULLY_CLOSED = 0x00 +TargetPositionEnum.MOVE_TO_FULLY_OPEN = 0x01 +TargetPositionEnum.MOVE_TO_PEDESTRIAN_POSITION = 0x02 +TargetPositionEnum.MOVE_TO_VENTILATION_POSITION = 0x03 +TargetPositionEnum.MOVE_TO_SIGNATURE_POSITION = 0x04 + +TargetPositionEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(TargetPositionEnum, new_mt) + +return TargetPositionEnum diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/init.lua new file mode 100644 index 0000000000..6531f734a3 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/init.lua @@ -0,0 +1,10 @@ +local types_mt = {} +types_mt.__index = function(self, key) + return require("embedded_clusters.ClosureControl.types." .. key) +end + +local ClosureControlTypes = {} + +setmetatable(ClosureControlTypes, types_mt) + +return ClosureControlTypes diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/init.lua new file mode 100644 index 0000000000..eb2f4cdae2 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/init.lua @@ -0,0 +1,106 @@ +local cluster_base = require "st.matter.cluster_base" +local ClosureDimensionServerAttributes = require "embedded_clusters.ClosureDimension.server.attributes" +local ClosureDimensionServerCommands = require "embedded_clusters.ClosureDimension.server.commands" +local ClosureDimensionTypes = require "embedded_clusters.ClosureDimension.types" + +local ClosureDimension = {} + +ClosureDimension.ID = 0x0105 +ClosureDimension.NAME = "ClosureDimension" +ClosureDimension.server = {} +ClosureDimension.client = {} +ClosureDimension.server.attributes = ClosureDimensionServerAttributes:set_parent_cluster(ClosureDimension) +ClosureDimension.server.commands = ClosureDimensionServerCommands:set_parent_cluster(ClosureDimension) +ClosureDimension.types = ClosureDimensionTypes + +function ClosureDimension:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "CurrentState", + [0x0001] = "TargetState", + [0x0002] = "Resolution", + [0x0003] = "StepValue", + [0x0004] = "Unit", + [0x0005] = "UnitRange", + [0x0006] = "LimitRange", + [0x0007] = "TranslationDirection", + [0x0008] = "RotationAxis", + [0x0009] = "Overflow", + [0x000A] = "ModulationType", + [0x000B] = "LatchControlModes", + [0xFFF9] = "AcceptedCommandList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function ClosureDimension:get_server_command_by_id(command_id) + local server_id_map = { + [0x0000] = "SetTarget", + [0x0001] = "Step", + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +ClosureDimension.attribute_direction_map = { + ["CurrentState"] = "server", + ["TargetState"] = "server", + ["Resolution"] = "server", + ["StepValue"] = "server", + ["Unit"] = "server", + ["UnitRange"] = "server", + ["LimitRange"] = "server", + ["TranslationDirection"] = "server", + ["RotationAxis"] = "server", + ["Overflow"] = "server", + ["ModulationType"] = "server", + ["LatchControlModes"] = "server", + ["AcceptedCommandList"] = "server", + ["AttributeList"] = "server", +} + +ClosureDimension.command_direction_map = { + ["SetTarget"] = "server", + ["Step"] = "server", +} + +ClosureDimension.FeatureMap = ClosureDimension.types.Feature + +function ClosureDimension.are_features_supported(feature, feature_map) + if (ClosureDimension.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 = ClosureDimension.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, ClosureDimension.NAME)) + end + return ClosureDimension[direction].attributes[key] +end +ClosureDimension.attributes = {} +setmetatable(ClosureDimension.attributes, attribute_helper_mt) + +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = ClosureDimension.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, ClosureDimension.NAME)) + end + return ClosureDimension[direction].commands[key] +end +ClosureDimension.commands = {} +setmetatable(ClosureDimension.commands, command_helper_mt) + +setmetatable(ClosureDimension, {__index = cluster_base}) + +return ClosureDimension diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/CurrentState.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/CurrentState.lua new file mode 100644 index 0000000000..cd0566b774 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/CurrentState.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CurrentState = { + ID = 0x0000, + NAME = "CurrentState", + base_type = require "embedded_clusters.ClosureDimension.types.DimensionStateStruct", +} + +function CurrentState:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function CurrentState:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentState:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentState:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CurrentState: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 CurrentState:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(CurrentState, {__call = CurrentState.new_value, __index = CurrentState.base_type}) +return CurrentState diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/LimitRange.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/LimitRange.lua new file mode 100644 index 0000000000..8f9b357bf8 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/LimitRange.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local LimitRange = { + ID = 0x0006, + NAME = "LimitRange", + base_type = require "embedded_clusters.ClosureDimension.types.RangePercent100thsStruct", +} + +function LimitRange:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function LimitRange:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function LimitRange:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function LimitRange:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LimitRange: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 LimitRange:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(LimitRange, {__call = LimitRange.new_value, __index = LimitRange.base_type}) +return LimitRange diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/StepValue.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/StepValue.lua new file mode 100644 index 0000000000..c6958a20bb --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/StepValue.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local StepValue = { + ID = 0x0003, + NAME = "StepValue", + base_type = require "st.matter.data_types.Uint16", +} + +function StepValue:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function StepValue:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function StepValue:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function StepValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function StepValue: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 StepValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(StepValue, {__call = StepValue.new_value, __index = StepValue.base_type}) +return StepValue diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/TargetState.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/TargetState.lua new file mode 100644 index 0000000000..fa242ca507 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/TargetState.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local TargetState = { + ID = 0x0001, + NAME = "TargetState", + base_type = require "embedded_clusters.ClosureDimension.types.DimensionStateStruct", +} + +function TargetState:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function TargetState:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function TargetState:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function TargetState:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function TargetState: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 TargetState:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(TargetState, {__call = TargetState.new_value, __index = TargetState.base_type}) +return TargetState diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/init.lua new file mode 100644 index 0000000000..8728e92157 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/attributes/init.lua @@ -0,0 +1,19 @@ +local attr_mt = {} +attr_mt.__index = function(self, key) + local req_loc = string.format("embedded_clusters.ClosureDimension.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 ClosureDimensionServerAttributes = {} + +function ClosureDimensionServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ClosureDimensionServerAttributes, attr_mt) + +return ClosureDimensionServerAttributes diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/SetTarget.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/SetTarget.lua new file mode 100644 index 0000000000..d2f4a51b78 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/SetTarget.lua @@ -0,0 +1,112 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SetTarget = {} + +SetTarget.NAME = "SetTarget" +SetTarget.ID = 0x0000 +SetTarget.field_defs = { + { + name = "position", + field_id = 0, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint16", + }, + { + name = "latch", + field_id = 1, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Boolean", + }, + { + name = "speed", + field_id = 2, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + }, +} + +function SetTarget:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, + status + ) +end + +function SetTarget:init(device, endpoint_id, position, latch, speed) + local out = {} + local args = {position, latch, speed} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.is_optional and args[i] == nil then + out[v.name] = nil + elseif v.is_nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.is_optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = SetTarget, + __tostring = SetTarget.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID, + true + ) +end + +function SetTarget:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SetTarget: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 + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == 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 SetTarget:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(SetTarget, {__call = SetTarget.init}) + +return SetTarget diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/Step.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/Step.lua new file mode 100644 index 0000000000..ce70c7e680 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/Step.lua @@ -0,0 +1,112 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local Step = {} + +Step.NAME = "Step" +Step.ID = 0x0001 +Step.field_defs = { + { + name = "direction", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "embedded_clusters.ClosureDimension.types.StepDirectionEnum", + }, + { + name = "number_of_steps", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint16", + }, + { + name = "speed", + field_id = 2, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + }, +} + +function Step:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, + status + ) +end + +function Step:init(device, endpoint_id, direction, number_of_steps, speed) + local out = {} + local args = {direction, number_of_steps, speed} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.is_optional and args[i] == nil then + out[v.name] = nil + elseif v.is_nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.is_optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = Step, + __tostring = Step.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID, + true + ) +end + +function Step:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function Step: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 + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == 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 Step:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(Step, {__call = Step.init}) + +return Step diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/init.lua new file mode 100644 index 0000000000..74003acc00 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/init.lua @@ -0,0 +1,19 @@ +local command_mt = {} +command_mt.__index = function(self, key) + local req_loc = string.format("embedded_clusters.ClosureDimension.server.commands.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + return raw_def +end + +local ClosureDimensionServerCommands = {} + +function ClosureDimensionServerCommands:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ClosureDimensionServerCommands, command_mt) + +return ClosureDimensionServerCommands diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/DimensionStateStruct.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/DimensionStateStruct.lua new file mode 100644 index 0000000000..56958b2020 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/DimensionStateStruct.lua @@ -0,0 +1,84 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local DimensionStateStruct = {} +local new_mt = StructureABC.new_mt({NAME = "DimensionStateStruct", ID = data_types.name_to_id_map["Structure"]}) + +DimensionStateStruct.field_defs = { + { + name = "position", + field_id = 0, + is_nullable = true, + is_optional = true, + data_type = require "st.matter.data_types.Uint16", + }, + { + name = "latch", + field_id = 1, + is_nullable = true, + is_optional = true, + data_type = require "st.matter.data_types.Boolean", + }, + { + name = "speed", + field_id = 2, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + }, +} + +DimensionStateStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +DimensionStateStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = DimensionStateStruct.init +new_mt.__index.serialize = DimensionStateStruct.serialize + +DimensionStateStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + 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 + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(DimensionStateStruct, new_mt) + +return DimensionStateStruct diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/Feature.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/Feature.lua new file mode 100644 index 0000000000..ecf029105c --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/Feature.lua @@ -0,0 +1,207 @@ +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.POSITIONING = 0x0001 +Feature.MOTION_LATCHING = 0x0002 +Feature.UNIT = 0x0004 +Feature.LIMITATION = 0x0008 +Feature.SPEED = 0x0010 +Feature.TRANSLATION = 0x0020 +Feature.ROTATION = 0x0040 +Feature.MODULATION = 0x0080 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + POSITIONING = 0x0001, + MOTION_LATCHING = 0x0002, + UNIT = 0x0004, + LIMITATION = 0x0008, + SPEED = 0x0010, + TRANSLATION = 0x0020, + ROTATION = 0x0040, + MODULATION = 0x0080, +} + +Feature.is_positioning_set = function(self) + return (self.value & self.POSITIONING) ~= 0 +end + +Feature.set_positioning = function(self) + if self.value ~= nil then + self.value = self.value | self.POSITIONING + else + self.value = self.POSITIONING + end +end + +Feature.unset_positioning = function(self) + self.value = self.value & (~self.POSITIONING & self.BASE_MASK) +end + +Feature.is_motion_latching_set = function(self) + return (self.value & self.MOTION_LATCHING) ~= 0 +end + +Feature.set_motion_latching = function(self) + if self.value ~= nil then + self.value = self.value | self.MOTION_LATCHING + else + self.value = self.MOTION_LATCHING + end +end + +Feature.unset_motion_latching = function(self) + self.value = self.value & (~self.MOTION_LATCHING & self.BASE_MASK) +end + +Feature.is_unit_set = function(self) + return (self.value & self.UNIT) ~= 0 +end + +Feature.set_unit = function(self) + if self.value ~= nil then + self.value = self.value | self.UNIT + else + self.value = self.UNIT + end +end + +Feature.unset_unit = function(self) + self.value = self.value & (~self.UNIT & self.BASE_MASK) +end + +Feature.is_limitation_set = function(self) + return (self.value & self.LIMITATION) ~= 0 +end + +Feature.set_limitation = function(self) + if self.value ~= nil then + self.value = self.value | self.LIMITATION + else + self.value = self.LIMITATION + end +end + +Feature.unset_limitation = function(self) + self.value = self.value & (~self.LIMITATION & self.BASE_MASK) +end + +Feature.is_speed_set = function(self) + return (self.value & self.SPEED) ~= 0 +end + +Feature.set_speed = function(self) + if self.value ~= nil then + self.value = self.value | self.SPEED + else + self.value = self.SPEED + end +end + +Feature.unset_speed = function(self) + self.value = self.value & (~self.SPEED & self.BASE_MASK) +end + +Feature.is_translation_set = function(self) + return (self.value & self.TRANSLATION) ~= 0 +end + +Feature.set_translation = function(self) + if self.value ~= nil then + self.value = self.value | self.TRANSLATION + else + self.value = self.TRANSLATION + end +end + +Feature.unset_translation = function(self) + self.value = self.value & (~self.TRANSLATION & self.BASE_MASK) +end + +Feature.is_rotation_set = function(self) + return (self.value & self.ROTATION) ~= 0 +end + +Feature.set_rotation = function(self) + if self.value ~= nil then + self.value = self.value | self.ROTATION + else + self.value = self.ROTATION + end +end + +Feature.unset_rotation = function(self) + self.value = self.value & (~self.ROTATION & self.BASE_MASK) +end + +Feature.is_modulation_set = function(self) + return (self.value & self.MODULATION) ~= 0 +end + +Feature.set_modulation = function(self) + if self.value ~= nil then + self.value = self.value | self.MODULATION + else + self.value = self.MODULATION + end +end + +Feature.unset_modulation = function(self) + self.value = self.value & (~self.MODULATION & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.POSITIONING | + Feature.MOTION_LATCHING | + Feature.UNIT | + Feature.LIMITATION | + Feature.SPEED | + Feature.TRANSLATION | + Feature.ROTATION | + Feature.MODULATION + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_positioning_set = Feature.is_positioning_set, + set_positioning = Feature.set_positioning, + unset_positioning = Feature.unset_positioning, + is_motion_latching_set = Feature.is_motion_latching_set, + set_motion_latching = Feature.set_motion_latching, + unset_motion_latching = Feature.unset_motion_latching, + is_unit_set = Feature.is_unit_set, + set_unit = Feature.set_unit, + unset_unit = Feature.unset_unit, + is_limitation_set = Feature.is_limitation_set, + set_limitation = Feature.set_limitation, + unset_limitation = Feature.unset_limitation, + is_speed_set = Feature.is_speed_set, + set_speed = Feature.set_speed, + unset_speed = Feature.unset_speed, + is_translation_set = Feature.is_translation_set, + set_translation = Feature.set_translation, + unset_translation = Feature.unset_translation, + is_rotation_set = Feature.is_rotation_set, + set_rotation = Feature.set_rotation, + unset_rotation = Feature.unset_rotation, + is_modulation_set = Feature.is_modulation_set, + set_modulation = Feature.set_modulation, + unset_modulation = Feature.unset_modulation, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/StepDirectionEnum.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/StepDirectionEnum.lua new file mode 100644 index 0000000000..c33e8a23ef --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/StepDirectionEnum.lua @@ -0,0 +1,27 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local StepDirectionEnum = {} +local new_mt = UintABC.new_mt({NAME = "StepDirectionEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.DECREASE] = "DECREASE", + [self.INCREASE] = "INCREASE", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.DECREASE = 0x00 +new_mt.__index.INCREASE = 0x01 + +StepDirectionEnum.DECREASE = 0x00 +StepDirectionEnum.INCREASE = 0x01 + +StepDirectionEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(StepDirectionEnum, new_mt) + +return StepDirectionEnum diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/TranslationDirectionEnum.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/TranslationDirectionEnum.lua new file mode 100644 index 0000000000..b84a353442 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/TranslationDirectionEnum.lua @@ -0,0 +1,57 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local TranslationDirectionEnum = {} +local new_mt = UintABC.new_mt({NAME = "TranslationDirectionEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.DOWNWARD] = "DOWNWARD", + [self.UPWARD] = "UPWARD", + [self.VERTICAL_MASK] = "VERTICAL_MASK", + [self.VERTICAL_SYMMETRY] = "VERTICAL_SYMMETRY", + [self.LEFTWARD] = "LEFTWARD", + [self.RIGHTWARD] = "RIGHTWARD", + [self.HORIZONTAL_MASK] = "HORIZONTAL_MASK", + [self.HORIZONTAL_SYMMETRY] = "HORIZONTAL_SYMMETRY", + [self.FORWARD] = "FORWARD", + [self.BACKWARD] = "BACKWARD", + [self.DEPTH_MASK] = "DEPTH_MASK", + [self.DEPTH_SYMMETRY] = "DEPTH_SYMMETRY", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.DOWNWARD = 0x00 +new_mt.__index.UPWARD = 0x01 +new_mt.__index.VERTICAL_MASK = 0x02 +new_mt.__index.VERTICAL_SYMMETRY = 0x03 +new_mt.__index.LEFTWARD = 0x04 +new_mt.__index.RIGHTWARD = 0x05 +new_mt.__index.HORIZONTAL_MASK = 0x06 +new_mt.__index.HORIZONTAL_SYMMETRY = 0x07 +new_mt.__index.FORWARD = 0x08 +new_mt.__index.BACKWARD = 0x09 +new_mt.__index.DEPTH_MASK = 0x0A +new_mt.__index.DEPTH_SYMMETRY = 0x0B + +TranslationDirectionEnum.DOWNWARD = 0x00 +TranslationDirectionEnum.UPWARD = 0x01 +TranslationDirectionEnum.VERTICAL_MASK = 0x02 +TranslationDirectionEnum.VERTICAL_SYMMETRY = 0x03 +TranslationDirectionEnum.LEFTWARD = 0x04 +TranslationDirectionEnum.RIGHTWARD = 0x05 +TranslationDirectionEnum.HORIZONTAL_MASK = 0x06 +TranslationDirectionEnum.HORIZONTAL_SYMMETRY = 0x07 +TranslationDirectionEnum.FORWARD = 0x08 +TranslationDirectionEnum.BACKWARD = 0x09 +TranslationDirectionEnum.DEPTH_MASK = 0x0A +TranslationDirectionEnum.DEPTH_SYMMETRY = 0x0B + +TranslationDirectionEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(TranslationDirectionEnum, new_mt) + +return TranslationDirectionEnum diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/init.lua new file mode 100644 index 0000000000..8bc2fe2cb2 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/init.lua @@ -0,0 +1,10 @@ +local types_mt = {} +types_mt.__index = function(self, key) + return require("embedded_clusters.ClosureDimension.types." .. key) +end + +local ClosureDimensionTypes = {} + +setmetatable(ClosureDimensionTypes, types_mt) + +return ClosureDimensionTypes diff --git a/drivers/SmartThings/matter-window-covering/src/init.lua b/drivers/SmartThings/matter-window-covering/src/init.lua index a98f8a321d..66581d0ae8 100644 --- a/drivers/SmartThings/matter-window-covering/src/init.lua +++ b/drivers/SmartThings/matter-window-covering/src/init.lua @@ -363,6 +363,8 @@ local matter_driver_template = { capabilities.windowShadeTiltLevel, capabilities.windowShade, capabilities.windowShadePreset, + capabilities.doorControl, + capabilities.level, capabilities.battery, capabilities.batteryLevel, }, diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers.lua index ff048340d0..ad3e53e4b0 100644 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers.lua @@ -3,6 +3,7 @@ local lazy_load_if_possible = require "lazy_load_subdriver" local sub_drivers = { - lazy_load_if_possible("matter-window-covering-position-updates-while-moving"), + lazy_load_if_possible("sub_drivers.closure"), + lazy_load_if_possible("sub_drivers.matter-window-covering-position-updates-while-moving"), } return sub_drivers diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/can_handle.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/can_handle.lua new file mode 100644 index 0000000000..a643fa909e --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local CLOSURE_CONTROL_CLUSTER_ID = 0x0104 + +return function(opts, driver, device) + if #device:get_endpoints(CLOSURE_CONTROL_CLUSTER_ID) > 0 then + return true, require("sub_drivers.closure") + end + return false +end diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/attribute_handlers.lua new file mode 100644 index 0000000000..c574991c82 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/attribute_handlers.lua @@ -0,0 +1,110 @@ +-- Copyright 2026 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" + +if version.api < 20 then + clusters.ClosureControl = require "embedded_clusters.ClosureControl" + clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" +end + +local fields = require "sub_drivers.closure.closure_utils.fields" +local closure_utils = require "sub_drivers.closure.closure_utils.utils" + +local ClosureAttrHandlers = {} + +function ClosureAttrHandlers.main_state_attr_handler(driver, device, ib, response) + if ib.data.value == nil then return end + closure_utils.set_closure_control_state(device, ib.endpoint_id, { main = ib.data.value }) + closure_utils.emit_closure_control_capability(device, ib.endpoint_id) +end + +function ClosureAttrHandlers.overall_current_state_attr_handler(driver, device, ib, response) + if not ib.data.elements then return end + clusters.ClosureControl.types.OverallCurrentStateStruct:augment_type(ib.data) + for _, v in pairs(ib.data.elements or {}) do + if v.field_id == 0 then + local current = v.value + closure_utils.set_closure_control_state(device, ib.endpoint_id, { current = current }) + closure_utils.emit_closure_control_capability(device, ib.endpoint_id) + break + end + end +end + +function ClosureAttrHandlers.overall_target_state_attr_handler(driver, device, ib, response) + if not ib.data.elements then return end + clusters.ClosureControl.types.OverallTargetStateStruct:augment_type(ib.data) + for _, v in pairs(ib.data.elements or {}) do + if v.field_id == 0 then + local target = v.value + closure_utils.set_closure_control_state(device, ib.endpoint_id, { target = target }) + closure_utils.emit_closure_control_capability(device, ib.endpoint_id) + break + end + end +end + +function ClosureAttrHandlers.closure_dimension_current_state_handler(driver, device, ib, response) + if not ib.data.elements then return end + clusters.ClosureDimension.types.DimensionStateStruct:augment_type(ib.data) + local pos_field = ib.data.elements.position + if not pos_field or pos_field.value == nil then return end + local level = math.floor(pos_field.value / 100) + if device:supports_capability_by_id(capabilities.doorControl.ID) then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.level.level(level)) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windowShadeLevel.shadeLevel(level)) + end +end + +function ClosureAttrHandlers.tag_list_handler(driver, device, ib, response) + if not ib.data.elements then return end + local tag_value + for _, v in ipairs(ib.data.elements) do + local tag = v.elements + if tag and tag.namespace_id and tag.namespace_id.value == 0x44 then + tag_value = tag.tag and tag.tag.value + break + end + end + + local closure_tag_map = { + [0] = fields.closure_tag_list.COVERING, + [1] = fields.closure_tag_list.WINDOW, + [2] = fields.closure_tag_list.BARRIER, + [3] = fields.closure_tag_list.CABINET, + [4] = fields.closure_tag_list.GATE, + [5] = fields.closure_tag_list.GARAGE_DOOR, + [6] = fields.closure_tag_list.DOOR, + } + + local closure_tag = fields.closure_tag_list.NA + if tag_value ~= nil and closure_tag_map[tag_value] ~= nil then + closure_tag = closure_tag_map[tag_value] + end + + device:set_field(fields.CLOSURE_TAG, closure_tag, {persist = true}) + closure_utils.match_profile(device) +end + +function ClosureAttrHandlers.power_source_attribute_list_handler(driver, device, ib, response) + for _, attr in ipairs(ib.data.elements) do + if attr.value == 0x0C then -- BatPercentRemaining + device:set_field(fields.CLOSURE_BATTERY_SUPPORT, fields.battery_support.BATTERY_PERCENTAGE, {persist = true}) + closure_utils.match_profile(device) + return + elseif attr.value == 0x0E then -- BatChargeLevel + device:set_field(fields.CLOSURE_BATTERY_SUPPORT, fields.battery_support.BATTERY_LEVEL, {persist = true}) + closure_utils.match_profile(device) + return + end + end + -- No battery attribute found + device:set_field(fields.CLOSURE_BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, {persist = true}) + closure_utils.match_profile(device) +end + +return ClosureAttrHandlers diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/capability_handlers.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/capability_handlers.lua new file mode 100644 index 0000000000..4084d0fae1 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/capability_handlers.lua @@ -0,0 +1,71 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local clusters = require "st.matter.clusters" +local version = require "version" + +if version.api < 20 then + clusters.ClosureControl = require "embedded_clusters.ClosureControl" + clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" +end + +local fields = require "sub_drivers.closure.closure_utils.fields" +local closure_utils = require "sub_drivers.closure.closure_utils.utils" + +local ClosureCapabilityHandlers = {} + +-- close covering (or door/gate) +function ClosureCapabilityHandlers.handle_close(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local reverse = device:get_field(fields.REVERSE_POLARITY) + local req = reverse and + clusters.ClosureControl.server.commands.MoveTo( + device, endpoint_id, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN + ) or + clusters.ClosureControl.server.commands.MoveTo( + device, endpoint_id, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED + ) + device:send(req) +end + +-- open covering (or door/gate) +function ClosureCapabilityHandlers.handle_open(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local reverse = device:get_field(fields.REVERSE_POLARITY) + local req = reverse and + clusters.ClosureControl.server.commands.MoveTo( + device, endpoint_id, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED + ) or + clusters.ClosureControl.server.commands.MoveTo( + device, endpoint_id, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN + ) + device:send(req) +end + +-- pause / stop covering +function ClosureCapabilityHandlers.handle_pause(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.ClosureControl.server.commands.Stop(device, endpoint_id)) +end + +-- move to shade level 0-100 for covering Closure devices +function ClosureCapabilityHandlers.handle_shade_level(driver, device, cmd) + local dim_eps = closure_utils.get_closure_dimension_eps(device) + local endpoint_id = #dim_eps == 1 and dim_eps[1] or device:component_to_endpoint(cmd.component) + if endpoint_id then + device:send(clusters.ClosureDimension.server.commands.SetTarget( + device, endpoint_id, cmd.args.shadeLevel * 100 + )) + end +end + +-- move to level 0-100 for door/gate/garage-door Closure devices +function ClosureCapabilityHandlers.handle_level(driver, device, cmd) + local dim_eps = closure_utils.get_closure_dimension_eps(device) + local endpoint_id = #dim_eps == 1 and dim_eps[1] or device:component_to_endpoint(cmd.component) + device:send(clusters.ClosureDimension.server.commands.SetTarget( + device, endpoint_id, cmd.args.level * 100 + )) +end + +return ClosureCapabilityHandlers diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/fields.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/fields.lua new file mode 100644 index 0000000000..11cd32b8bd --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/fields.lua @@ -0,0 +1,37 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local fields = {} + +fields.REVERSE_POLARITY = "__reverse_polarity" +fields.PRESET_LEVEL_KEY = "__preset_level_key" +fields.DEFAULT_PRESET_LEVEL = 50 + +fields.battery_support = { + NO_BATTERY = "NO_BATTERY", + BATTERY_LEVEL = "BATTERY_LEVEL", + BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE", +} + +fields.CLOSURE_CONTROL_STATE_CACHE = "__closure_control_state_cache" +fields.CLOSURE_BATTERY_SUPPORT = "__closure_battery_support" +fields.CLOSURE_TAG = "__closure_tag" + +fields.closure_tag_list = { + NA = "N/A", + COVERING = "COVERING", + WINDOW = "WINDOW", + BARRIER = "BARRIER", + CABINET = "CABINET", + GATE = "GATE", + GARAGE_DOOR = "GARAGE_DOOR", + DOOR = "DOOR", +} + +-- The maximum number of supported panels for a closure device. Note that this is an +-- arbitrary number and should be raised if needed by a closure device with more panels. +fields.MAX_CLOSURE_PANELS = 4 + +fields.SUBSCRIBED_ATTRIBUTES_KEY = "__subscribed_attributes" + +return fields diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/utils.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/utils.lua new file mode 100644 index 0000000000..df6dc9015c --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/utils.lua @@ -0,0 +1,318 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local im = require "st.matter.interaction_model" +local log = require "log" +local version = require "version" + +if version.api < 20 then + clusters.ClosureControl = require "embedded_clusters.ClosureControl" + clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" +end + +local fields = require "sub_drivers.closure.closure_utils.fields" + +local utils = {} + +function utils.find_default_endpoint(device, cluster) + local res = device.MATTER_DEFAULT_ENDPOINT + local eps = device:get_endpoints(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 utils.get_closure_dimension_eps(device) + local eps = device:get_endpoints(clusters.ClosureDimension.ID) or {} + table.sort(eps) + local result = {} + for _, ep in ipairs(eps) do + if ep ~= 0 then + table.insert(result, ep) + if #result >= fields.MAX_CLOSURE_PANELS then break end + end + end + return result +end + +--- Single-panel devices always map to "main"; +--- multi-panel devices map to "windowShade1"..."windowShade4" or "door1"..."door4". +function utils.endpoint_to_component(device, ep_id) + local dim_eps = utils.get_closure_dimension_eps(device) + if #dim_eps > 1 then + local is_door_type = device:supports_capability_by_id(capabilities.doorControl.ID) + local prefix = is_door_type and "door" or "windowShade" + for i, ep in ipairs(dim_eps) do + if ep == ep_id then + return prefix .. i + end + end + end + return "main" +end + +function utils.component_to_endpoint(device, component_name) + local dim_eps = utils.get_closure_dimension_eps(device) + if #dim_eps > 1 then + local comp_num = tonumber(component_name:match("(%d+)$")) + if comp_num and dim_eps[comp_num] then + return dim_eps[comp_num] + end + end + return utils.find_default_endpoint(device, clusters.ClosureControl.ID) +end + +function utils.match_profile(device) + if not device:get_field(fields.CLOSURE_TAG) or not device:get_field(fields.CLOSURE_BATTERY_SUPPORT) then + log.warn("Closure tag or battery support not set yet, cannot match profile") + return + end + + local tag = device:get_field(fields.CLOSURE_TAG) + local profile_name + local is_door_type = true + + if tag == fields.closure_tag_list.GATE then + profile_name = "gate" + elseif tag == fields.closure_tag_list.GARAGE_DOOR then + profile_name = "garage-door" + elseif tag == fields.closure_tag_list.DOOR then + profile_name = "door" + else + -- COVERING, WINDOW, BARRIER, CABINET, NA -> generic covering profile + profile_name = "covering" + is_door_type = false + end + + local optional_caps = {} + local main_component_capabilities = {} + + local closure_battery = device:get_field(fields.CLOSURE_BATTERY_SUPPORT) + if closure_battery == fields.battery_support.BATTERY_PERCENTAGE then + table.insert(main_component_capabilities, capabilities.battery.ID) + elseif closure_battery == fields.battery_support.BATTERY_LEVEL then + table.insert(main_component_capabilities, capabilities.batteryLevel.ID) + end + + -- ClosureDimension capabilities: windowShadeLevel (covering) or level (door types) + local dim_eps = utils.get_closure_dimension_eps(device) + if #dim_eps > 0 then + local dim_cap = is_door_type and capabilities.level.ID or capabilities.windowShadeLevel.ID + if #dim_eps == 1 then + -- Single ClosureDimension: enable the capability on the main component. + table.insert(main_component_capabilities, dim_cap) + else + -- Multiple ClosureDimensions: one optional component+capability per panel. + local prefix = is_door_type and "door" or "windowShade" + for i = 1, math.min(#dim_eps, fields.MAX_CLOSURE_PANELS) do + table.insert(optional_caps, {prefix .. i, {dim_cap}}) + end + end + end + + if #main_component_capabilities > 0 then + table.insert(optional_caps, 1, {"main", main_component_capabilities}) + end + + device:try_update_metadata({ + profile = profile_name, + optional_component_capabilities = #optional_caps > 0 and optional_caps or nil, + }) +end + +--- Deeply compare two values. +--- Handles metatables. Can optionally ignore cycle checking and/or function differences. +--- +--- @param a any +--- @param b any +--- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } +--- @param seen table|nil +--- @return boolean +function utils.deep_equals(a, b, opts, seen) + if a == b then return true end + if type(a) ~= type(b) then return false end + if type(a) == "function" and opts and opts.ignore_functions then return true end + if type(a) ~= "table" then return false end + + if not (opts and opts.ignore_cycles) then + seen = seen or {} + seen[a] = seen[a] or {} + if seen[a][b] then + return seen[a][b] + end + seen[a][b] = true + end + + for k, v in pairs(a) do + if not utils.deep_equals(v, b[k], opts, seen) then + return false + end + end + + for k in pairs(b) do + if a[k] == nil then + return false + end + end + + local mt_a = getmetatable(a) + local mt_b = getmetatable(b) + return utils.deep_equals(mt_a, mt_b, opts, seen) +end + +function utils.set_closure_control_state(device, endpoint_id, field) + local cache = device:get_field(fields.CLOSURE_CONTROL_STATE_CACHE) or {} + if not cache[endpoint_id] then cache[endpoint_id] = {} end + for k, v in pairs(field) do + cache[endpoint_id][k] = v + end + device:set_field(fields.CLOSURE_CONTROL_STATE_CACHE, cache) +end + +--- Emits the appropriate windowShade / doorControl capability event from the +--- cached MainState, OverallCurrentState.position, and OverallTargetState.position. +function utils.emit_closure_control_capability(device, endpoint_id) + local cache = device:get_field(fields.CLOSURE_CONTROL_STATE_CACHE) + if not cache then return end + local closure_control_state = cache[endpoint_id] or {} + local reverse = device:get_field(fields.REVERSE_POLARITY) + + local main = closure_control_state.main + local current = closure_control_state.current + local target = closure_control_state.target + + local closure_capability = capabilities.windowShade.windowShade + if device:supports_capability_by_id(capabilities.doorControl.ID) then + closure_capability = capabilities.doorControl.door + end + + if main == clusters.ClosureControl.types.MainStateEnum.MOVING then + if target == clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED then + device:emit_event_for_endpoint( + endpoint_id, reverse and closure_capability.opening() or closure_capability.closing() + ) + elseif target == clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN then + device:emit_event_for_endpoint( + endpoint_id, reverse and closure_capability.closing() or closure_capability.opening() + ) + end + elseif main == clusters.ClosureControl.types.MainStateEnum.STOPPED or main == nil then + if current == nil then return end + if current == clusters.ClosureControl.types.CurrentPositionEnum.FULLY_CLOSED then + device:emit_event_for_endpoint( + endpoint_id, reverse and closure_capability.open() or closure_capability.closed() + ) + elseif current == clusters.ClosureControl.types.CurrentPositionEnum.FULLY_OPENED or + device:supports_capability_by_id(capabilities.doorControl.ID) then + -- doorControl does not support partially_open; treat any non-fully-closed as open + device:emit_event_for_endpoint( + endpoint_id, reverse and closure_capability.closed() or closure_capability.open() + ) + else + device:emit_event_for_endpoint(endpoint_id, closure_capability.partially_open()) + end + 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 subscribed_attributes table key-value pairs mapping capability ids to subscribed attributes +function utils.populate_subscribe_request_for_device(checked_device, subscribe_request, capabilities_seen, attributes_seen, subscribed_attributes) + 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 + capabilities_seen[capability.id] = true -- only loop through any capability once + end + end + end +end + +function utils.subscribe(device) + local closure_subscribed_attributes = { + [capabilities.windowShade.ID] = { + clusters.ClosureControl.attributes.MainState, + clusters.ClosureControl.attributes.OverallCurrentState, + clusters.ClosureControl.attributes.OverallTargetState, + }, + [capabilities.doorControl.ID] = { + clusters.ClosureControl.attributes.MainState, + clusters.ClosureControl.attributes.OverallCurrentState, + clusters.ClosureControl.attributes.OverallTargetState, + }, + [capabilities.windowShadeLevel.ID] = { + clusters.ClosureDimension.attributes.CurrentState, + }, + [capabilities.level.ID] = { + clusters.ClosureDimension.attributes.CurrentState, + }, + [capabilities.battery.ID] = { + clusters.PowerSource.attributes.BatPercentRemaining, + }, + [capabilities.batteryLevel.ID] = { + clusters.PowerSource.attributes.BatChargeLevel, + }, + } + + local subscribe_request = im.InteractionRequest(im.InteractionRequest.RequestType.SUBSCRIBE, {}) + local capabilities_seen, attributes_seen = {}, {} + local additional_attributes = {} + + -- The refresh capability command handler in the lua libs uses this key to determine which attributes to read. + device:set_field(fields.SUBSCRIBED_ATTRIBUTES_KEY, attributes_seen) + + -- If the type of battery support has not yet been determined, add the PowerSource AttributeList to the list of + -- subscribed attributes in order to determine which if any battery capability should be used. + if device:get_field(fields.CLOSURE_BATTERY_SUPPORT) == nil then + local ib = im.InteractionInfoBlock(nil, clusters.PowerSource.ID, clusters.PowerSource.attributes.AttributeList.ID) + subscribe_request:with_info_block(ib) + end + + if device:get_field(fields.CLOSURE_TAG) == nil then + table.insert(additional_attributes, clusters.Descriptor.attributes.TagList) + end + + utils.populate_subscribe_request_for_device( + device, subscribe_request, capabilities_seen, attributes_seen, closure_subscribed_attributes + ) + + for _, attr in ipairs(additional_attributes) 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 + + if #subscribe_request.info_blocks > 0 then + device:send(subscribe_request) + end +end + +return utils diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/init.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/init.lua new file mode 100644 index 0000000000..638f561415 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/init.lua @@ -0,0 +1,140 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local log = require "log" +local version = require "version" + +if version.api < 20 then + clusters.ClosureControl = require "embedded_clusters.ClosureControl" + clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" +end + +local fields = require "sub_drivers.closure.closure_utils.fields" +local closure_utils = require "sub_drivers.closure.closure_utils.utils" +local attribute_handlers = require "sub_drivers.closure.closure_handlers.attribute_handlers" +local capability_handlers = require "sub_drivers.closure.closure_handlers.capability_handlers" + +-- --------------------------------------------------------------------------- +-- Lifecycle handlers +-- --------------------------------------------------------------------------- + +local ClosureLifecycleHandlers = {} + +function ClosureLifecycleHandlers.device_init(driver, device) + device:set_component_to_endpoint_fn(closure_utils.component_to_endpoint) + device:set_endpoint_to_component_fn(closure_utils.endpoint_to_component) + if device:supports_capability_by_id(capabilities.windowShadePreset.ID) and + device:get_latest_state("main", capabilities.windowShadePreset.ID, + capabilities.windowShadePreset.position.NAME) == nil then + device:emit_event(capabilities.windowShadePreset.supportedCommands( + {"presetPosition", "setPresetPosition"}, {visibility = {displayed = false}} + )) + local preset_position = device:get_field(fields.PRESET_LEVEL_KEY) or + (device.preferences ~= nil and device.preferences.presetPosition) or + fields.DEFAULT_PRESET_LEVEL + device:emit_event(capabilities.windowShadePreset.position( + preset_position, {visibility = {displayed = false}} + )) + device:set_field(fields.PRESET_LEVEL_KEY, preset_position, {persist = true}) + end + device:extend_device("subscribe", closure_utils.subscribe) + device:subscribe() +end + +function ClosureLifecycleHandlers.device_added(driver, device) + if device:supports_capability_by_id(capabilities.windowShade.ID) then + device:emit_event( + capabilities.windowShade.supportedWindowShadeCommands( + {"open", "close", "pause"}, {visibility = {displayed = false}} + ) + ) + end + device:set_field(fields.REVERSE_POLARITY, false, {persist = true}) +end + +function ClosureLifecycleHandlers.do_configure(driver, device) + if #device:get_endpoints(clusters.Descriptor.ID) == 0 then + log.warn( + "Descriptor cluster not implemented on ClosureControl endpoint, " .. + "cannot read TagList to determine closure type" + ) + device:set_field(fields.CLOSURE_TAG, fields.closure_tag_list.NA, {persist = true}) + end + + local battery_feature_eps = device:get_endpoints( + clusters.PowerSource.ID, + {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY} + ) + if #battery_feature_eps == 0 then + device:set_field(fields.CLOSURE_BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, {persist = true}) + closure_utils.match_profile(device) + end +end + +function ClosureLifecycleHandlers.info_changed(driver, device, event, args) + if not closure_utils.deep_equals( + device.profile, args.old_st_store.profile, {ignore_functions = true} + ) then + device:subscribe() + elseif args.old_st_store.preferences.reverse ~= device.preferences.reverse then + if device.preferences.reverse then + device:set_field(fields.REVERSE_POLARITY, true, {persist = true}) + else + device:set_field(fields.REVERSE_POLARITY, false, {persist = true}) + end + end +end + +-- --------------------------------------------------------------------------- +-- Subdriver template +-- --------------------------------------------------------------------------- + +local closure_handler = { + NAME = "Closure Handler", + lifecycle_handlers = { + init = ClosureLifecycleHandlers.device_init, + added = ClosureLifecycleHandlers.device_added, + doConfigure = ClosureLifecycleHandlers.do_configure, + infoChanged = ClosureLifecycleHandlers.info_changed, + }, + matter_handlers = { + attr = { + [clusters.ClosureControl.ID] = { + [clusters.ClosureControl.attributes.MainState.ID] = attribute_handlers.main_state_attr_handler, + [clusters.ClosureControl.attributes.OverallCurrentState.ID] = attribute_handlers.overall_current_state_attr_handler, + [clusters.ClosureControl.attributes.OverallTargetState.ID] = attribute_handlers.overall_target_state_attr_handler, + }, + [clusters.ClosureDimension.ID] = { + [clusters.ClosureDimension.attributes.CurrentState.ID] = attribute_handlers.closure_dimension_current_state_handler, + }, + [clusters.Descriptor.ID] = { + [clusters.Descriptor.attributes.TagList.ID] = attribute_handlers.tag_list_handler, + }, + [clusters.PowerSource.ID] = { + [clusters.PowerSource.attributes.AttributeList.ID] = attribute_handlers.power_source_attribute_list_handler, + }, + }, + }, + capability_handlers = { + [capabilities.windowShade.ID] = { + [capabilities.windowShade.commands.close.NAME] = capability_handlers.handle_close, + [capabilities.windowShade.commands.open.NAME] = capability_handlers.handle_open, + [capabilities.windowShade.commands.pause.NAME] = capability_handlers.handle_pause, + }, + [capabilities.doorControl.ID] = { + [capabilities.doorControl.commands.open.NAME] = capability_handlers.handle_open, + [capabilities.doorControl.commands.close.NAME] = capability_handlers.handle_close, + }, + [capabilities.windowShadeLevel.ID] = { + [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = capability_handlers.handle_shade_level, + }, + [capabilities.level.ID] = { + [capabilities.level.commands.setLevel.NAME] = capability_handlers.handle_level, + }, + }, + can_handle = require("sub_drivers.closure.can_handle"), +} + +return closure_handler diff --git a/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/can_handle.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/can_handle.lua similarity index 72% rename from drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/can_handle.lua rename to drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/can_handle.lua index ba9207f3d2..e3e12116e1 100644 --- a/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/can_handle.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/can_handle.lua @@ -6,11 +6,11 @@ local function is_matter_window_covering_position_updates_while_moving(opts, dri if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then return false end - local FINGERPRINTS = require("matter-window-covering-position-updates-while-moving.fingerprints") + local FINGERPRINTS = require("sub_drivers.matter-window-covering-position-updates-while-moving.fingerprints") for i, v in ipairs(FINGERPRINTS) do if device.manufacturer_info.vendor_id == v[1] and device.manufacturer_info.product_id == v[2] then - return true, require("matter-window-covering-position-updates-while-moving") + return true, require("sub_drivers.matter-window-covering-position-updates-while-moving") end end return false diff --git a/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/fingerprints.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/fingerprints.lua similarity index 100% rename from drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/fingerprints.lua rename to drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/fingerprints.lua diff --git a/drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/init.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua similarity index 100% rename from drivers/SmartThings/matter-window-covering/src/matter-window-covering-position-updates-while-moving/init.lua rename to drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua diff --git a/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua b/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua new file mode 100644 index 0000000000..95a4c6f112 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua @@ -0,0 +1,785 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local uint32 = require "st.matter.data_types.Uint32" +local version = require "version" + +if version.api < 20 then + clusters.ClosureControl = require "embedded_clusters.ClosureControl" + clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" +end + +-- --------------------------------------------------------------------------- +-- Mock device: Covering type (windowShade / windowShadeLevel) +-- --------------------------------------------------------------------------- + +local mock_device = test.mock_device.build_test_matter_device( + { + label = "Matter Closure", + profile = t_utils.get_profile_definition("covering.yml"), + manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, + matter_version = {hardware = 1, software = 1}, + endpoints = { + { + endpoint_id = 2, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0016, device_type_revision = 1} -- RootNode + } + }, + { + endpoint_id = 10, + clusters = { + { + cluster_id = clusters.ClosureControl.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 3, + }, + {cluster_id = clusters.Descriptor.ID, cluster_type = "SERVER", feature_map = 0}, + {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER", feature_map = 0x0002} + }, + device_types = { + {device_type_id = 0x0230, device_type_revision = 1} -- Closure + } + }, + { + endpoint_id = 11, + clusters = { + { + cluster_id = clusters.ClosureDimension.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0, + }, + }, + device_types = { + {device_type_id = 0x0231, device_type_revision = 1} -- ClosureDimension + } + }, + { + endpoint_id = 12, + clusters = { + { + cluster_id = clusters.ClosureDimension.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0, + }, + }, + device_types = { + {device_type_id = 0x0231, device_type_revision = 1} -- ClosureDimension + } + }, + }, + } +) + +-- --------------------------------------------------------------------------- +-- Mock device: Door type (doorControl / level) +-- --------------------------------------------------------------------------- + +local mock_door_device = test.mock_device.build_test_matter_device( + { + label = "Matter Door", + profile = t_utils.get_profile_definition("door.yml"), + manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, + matter_version = {hardware = 1, software = 1}, + endpoints = { + { + endpoint_id = 2, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0016, device_type_revision = 1} -- RootNode + } + }, + { + endpoint_id = 10, + clusters = { + { + cluster_id = clusters.ClosureControl.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 3, + }, + {cluster_id = clusters.Descriptor.ID, cluster_type = "SERVER", feature_map = 0}, + {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER", feature_map = 0x0002} + }, + device_types = { + {device_type_id = 0x0230, device_type_revision = 1} -- Closure + } + }, + { + endpoint_id = 11, + clusters = { + { + cluster_id = clusters.ClosureDimension.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0, + }, + }, + device_types = { + {device_type_id = 0x0231, device_type_revision = 1} -- ClosureDimension + } + }, + { + endpoint_id = 12, + clusters = { + { + cluster_id = clusters.ClosureDimension.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0, + }, + }, + device_types = { + {device_type_id = 0x0231, device_type_revision = 1} -- ClosureDimension + } + }, + }, + } +) + +local CLUSTER_SUBSCRIBE_LIST = { + clusters.ClosureControl.attributes.MainState, + clusters.ClosureControl.attributes.OverallCurrentState, + clusters.ClosureControl.attributes.OverallTargetState, +} + +-- additional clusters that will be subscribed to initially but not after the profile is matched. +local ADDITIONAL_SUBSCRIBE_LIST = { + clusters.PowerSource.attributes.AttributeList, + clusters.Descriptor.attributes.TagList, +} + +local function test_init() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShade.supportedWindowShadeCommands({"open", "close", "pause"}, + {visibility = {displayed = false}}) + ) + ) + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "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 + for _, clus in ipairs(ADDITIONAL_SUBSCRIBE_LIST) do + subscribe_request:merge(clus:subscribe(mock_device)) + 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 function update_profile() + test.socket.matter:__queue_receive({mock_device.id, clusters.PowerSource.attributes.AttributeList:build_test_report_data( + mock_device, 10, {uint32(clusters.PowerSource.attributes.BatPercentRemaining.ID)} + )}) + test.socket.matter:__queue_receive({mock_device.id, clusters.Descriptor.attributes.TagList:build_test_report_data( + mock_device, 10, {clusters.Global.types.SemanticTagStruct({mfg_code = 0x00, namespace_id = 0x44, tag = 0x00, name = "Covering"}) } + )}) + mock_device:expect_metadata_update({ + profile = "covering", + optional_component_capabilities = { + {"main", {"battery"}}, + {"windowShade1", {"windowShadeLevel"}}, + {"windowShade2", {"windowShadeLevel"}}, + } + }) + test.wait_for_events() + local updated_device_profile = t_utils.get_profile_definition("covering.yml", { + enabled_optional_capabilities = { + {"main", {"battery"}}, + {"windowShade1", {"windowShadeLevel"}}, + {"windowShade2", {"windowShadeLevel"}}, + } + }) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ profile = updated_device_profile })) + 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 + subscribe_request:merge(clusters.PowerSource.server.attributes.BatPercentRemaining:subscribe(mock_device)) + subscribe_request:merge(clusters.ClosureDimension.attributes.CurrentState:subscribe(mock_device)) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) +end + +local function test_init_door() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_door_device) + test.socket.device_lifecycle:__queue_receive({ mock_door_device.id, "added" }) + + test.socket.device_lifecycle:__queue_receive({ mock_door_device.id, "init" }) + + local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_door_device) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_door_device)) end + end + for _, clus in ipairs(ADDITIONAL_SUBSCRIBE_LIST) do + subscribe_request:merge(clus:subscribe(mock_door_device)) + end + test.socket.matter:__expect_send({mock_door_device.id, subscribe_request}) + + test.socket.device_lifecycle:__queue_receive({ mock_door_device.id, "doConfigure" }) + mock_door_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + +local function update_profile_door() + test.socket.matter:__queue_receive({mock_door_device.id, clusters.PowerSource.attributes.AttributeList:build_test_report_data( + mock_door_device, 10, {uint32(clusters.PowerSource.attributes.BatPercentRemaining.ID)} + )}) + test.socket.matter:__queue_receive({mock_door_device.id, clusters.Descriptor.attributes.TagList:build_test_report_data( + mock_door_device, 10, {clusters.Global.types.SemanticTagStruct({mfg_code = 0x00, namespace_id = 0x44, tag = 0x06, name = "Door"})} + )}) + mock_door_device:expect_metadata_update({ + profile = "door", + optional_component_capabilities = { + {"main", {"battery"}}, + {"door1", {"level"}}, + {"door2", {"level"}}, + } + }) + test.wait_for_events() + local updated_device_profile = t_utils.get_profile_definition("door.yml", { + enabled_optional_capabilities = { + {"main", {"battery"}}, + {"door1", {"level"}}, + {"door2", {"level"}}, + } + }) + test.socket.device_lifecycle:__queue_receive(mock_door_device:generate_info_changed({ profile = updated_device_profile })) + local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_door_device) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_door_device)) end + end + subscribe_request:merge(clusters.PowerSource.server.attributes.BatPercentRemaining:subscribe(mock_door_device)) + subscribe_request:merge(clusters.ClosureDimension.attributes.CurrentState:subscribe(mock_door_device)) + test.socket.matter:__expect_send({mock_door_device.id, subscribe_request}) +end + +test.register_coroutine_test( + "windowShade closed following MainState and OverallTargetState update", function() + update_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureControl.attributes.MainState:build_test_report_data(mock_device, 10, clusters.ClosureControl.types.MainStateEnum.MOVING), + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureControl.attributes.OverallTargetState:build_test_report_data(mock_device, 10, + clusters.ClosureControl.types.OverallTargetStateStruct({ + position = clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.MEDIUM + })) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) + ) + end +) + +test.register_coroutine_test( + "windowShade opening following MainState and OverallTargetState update", function() + update_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureControl.attributes.MainState:build_test_report_data(mock_device, 10, clusters.ClosureControl.types.MainStateEnum.MOVING), + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureControl.attributes.OverallTargetState:build_test_report_data(mock_device, 10, + clusters.ClosureControl.types.OverallTargetStateStruct({ + position = clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.MEDIUM + })) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + end +) + +test.register_coroutine_test( + "windowShade closed following OverallCurrentState FULLY_CLOSED", function() + update_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureControl.attributes.OverallCurrentState:build_test_report_data(mock_device, 10, + clusters.ClosureControl.types.OverallCurrentStateStruct({ + position = clusters.ClosureControl.types.CurrentPositionEnum.FULLY_CLOSED, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO, + secure_state = false + })) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + ) + end +) + +test.register_coroutine_test( + "windowShade open following OverallCurrentState FULLY_OPENED", function() + update_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureControl.attributes.OverallCurrentState:build_test_report_data(mock_device, 10, + clusters.ClosureControl.types.OverallCurrentStateStruct({ + position = clusters.ClosureControl.types.CurrentPositionEnum.FULLY_OPENED, + latch = true, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO, + secure_state = false + })) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) + ) + end +) + +test.register_coroutine_test( + "windowShade partially_open following OverallCurrentState PARTIALLY_OPENED", function() + update_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureControl.attributes.OverallCurrentState:build_test_report_data(mock_device, 10, + clusters.ClosureControl.types.OverallCurrentStateStruct({ + position = clusters.ClosureControl.types.CurrentPositionEnum.PARTIALLY_OPENED, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO, + secure_state = false + })) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + end +) + +test.register_coroutine_test( + "windowShade state transitions from closing to closed", function() + update_profile() + test.wait_for_events() + -- device starts moving toward closed + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureControl.attributes.MainState:build_test_report_data(mock_device, 10, clusters.ClosureControl.types.MainStateEnum.MOVING), + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureControl.attributes.OverallTargetState:build_test_report_data(mock_device, 10, + clusters.ClosureControl.types.OverallTargetStateStruct({ + position = clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.MEDIUM + })) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) + ) + test.wait_for_events() + -- device stops and reports fully closed + -- MainState STOPPED with no current position cached yet. no capability event emitted + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureControl.attributes.MainState:build_test_report_data(mock_device, 10, clusters.ClosureControl.types.MainStateEnum.STOPPED), + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureControl.attributes.OverallCurrentState:build_test_report_data(mock_device, 10, + clusters.ClosureControl.types.OverallCurrentStateStruct({ + position = clusters.ClosureControl.types.CurrentPositionEnum.FULLY_CLOSED, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO, + secure_state = false + })) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + ) + end +) + +test.register_coroutine_test( + "windowShade close command sends ClosureControl MoveTo FULLY_CLOSED", function() + test.socket.capability:__queue_receive({ + mock_device.id, + {capability = "windowShade", component = "main", command = "close", args = {}}, + }) + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ClosureControl.server.commands.MoveTo( + mock_device, 10, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED + ) + }) + end +) + +test.register_coroutine_test( + "windowShade open command sends ClosureControl MoveTo FULLY_OPEN", function() + test.socket.capability:__queue_receive({ + mock_device.id, + {capability = "windowShade", component = "main", command = "open", args = {}}, + }) + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ClosureControl.server.commands.MoveTo( + mock_device, 10, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN + ) + }) + end +) + +test.register_coroutine_test( + "windowShade pause command sends ClosureControl Stop", function() + test.socket.capability:__queue_receive({ + mock_device.id, + {capability = "windowShade", component = "main", command = "pause", args = {}}, + }) + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ClosureControl.server.commands.Stop(mock_device, 10) + }) + end +) + +test.register_coroutine_test( + "Battery percentage reported correctly for closure device", function() + update_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data(mock_device, 10, 150) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5))) + ) + end +) + +test.register_coroutine_test( + "setShadeLevel on windowShade1 sends SetTarget to endpoint 11", function() + update_profile() + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + {capability = "windowShadeLevel", component = "windowShade1", command = "setShadeLevel", args = {75}}, + }) + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ClosureDimension.server.commands.SetTarget(mock_device, 11, 75 * 100) + }) + end +) + +test.register_coroutine_test( + "setShadeLevel on windowShade2 sends SetTarget to endpoint 12", function() + update_profile() + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + {capability = "windowShadeLevel", component = "windowShade2", command = "setShadeLevel", args = {40}}, + }) + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ClosureDimension.server.commands.SetTarget(mock_device, 12, 40 * 100) + }) + end +) + +test.register_coroutine_test( + "ClosureDimension CurrentState on endpoint 11 emits shadeLevel on windowShade1", function() + update_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureDimension.attributes.CurrentState:build_test_report_data(mock_device, 11, + clusters.ClosureDimension.types.DimensionStateStruct({ + position = 6000, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO + }) + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("windowShade1", capabilities.windowShadeLevel.shadeLevel(60)) + ) + end +) + +test.register_coroutine_test( + "ClosureDimension CurrentState on endpoint 12 emits shadeLevel on windowShade2", function() + update_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureDimension.attributes.CurrentState:build_test_report_data(mock_device, 12, + clusters.ClosureDimension.types.DimensionStateStruct({ + position = 2500, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO + }) + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("windowShade2", capabilities.windowShadeLevel.shadeLevel(25)) + ) + end +) + +test.register_coroutine_test( + "ClosureDimension CurrentState with closed position emits shadeLevel 0", function() + update_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureDimension.attributes.CurrentState:build_test_report_data(mock_device, 11, + clusters.ClosureDimension.types.DimensionStateStruct({ + position = 0, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO + }) + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("windowShade1", capabilities.windowShadeLevel.shadeLevel(0)) + ) + end +) + +test.register_coroutine_test( + "ClosureDimension CurrentState with full-open position emits shadeLevel 100", function() + update_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ClosureDimension.attributes.CurrentState:build_test_report_data(mock_device, 11, + clusters.ClosureDimension.types.DimensionStateStruct({ + position = 10000, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO + }) + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("windowShade1", capabilities.windowShadeLevel.shadeLevel(100)) + ) + end +) + +-- --------------------------------------------------------------------------- +-- Door / garage-door / gate type tests +-- --------------------------------------------------------------------------- + +test.register_coroutine_test( + "doorControl closed following MainState and OverallTargetState update", function() + update_profile_door() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_door_device.id, + clusters.ClosureControl.attributes.MainState:build_test_report_data(mock_door_device, 10, clusters.ClosureControl.types.MainStateEnum.MOVING), + }) + test.socket.matter:__queue_receive({ + mock_door_device.id, + clusters.ClosureControl.attributes.OverallTargetState:build_test_report_data(mock_door_device, 10, + clusters.ClosureControl.types.OverallTargetStateStruct({ + position = clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.MEDIUM + })) + }) + test.socket.capability:__expect_send( + mock_door_device:generate_test_message("main", capabilities.doorControl.door.closing()) + ) + end, + {test_init = test_init_door} +) + +test.register_coroutine_test( + "doorControl opening following MainState and OverallTargetState update", function() + update_profile_door() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_door_device.id, + clusters.ClosureControl.attributes.MainState:build_test_report_data(mock_door_device, 10, clusters.ClosureControl.types.MainStateEnum.MOVING), + }) + test.socket.matter:__queue_receive({ + mock_door_device.id, + clusters.ClosureControl.attributes.OverallTargetState:build_test_report_data(mock_door_device, 10, + clusters.ClosureControl.types.OverallTargetStateStruct({ + position = clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.MEDIUM + })) + }) + test.socket.capability:__expect_send( + mock_door_device:generate_test_message("main", capabilities.doorControl.door.opening()) + ) + end, + {test_init = test_init_door} +) + +test.register_coroutine_test( + "doorControl closed following OverallCurrentState FULLY_CLOSED", function() + update_profile_door() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_door_device.id, + clusters.ClosureControl.attributes.OverallCurrentState:build_test_report_data(mock_door_device, 10, + clusters.ClosureControl.types.OverallCurrentStateStruct({ + position = clusters.ClosureControl.types.CurrentPositionEnum.FULLY_CLOSED, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO, + secure_state = false + })) + }) + test.socket.capability:__expect_send( + mock_door_device:generate_test_message("main", capabilities.doorControl.door.closed()) + ) + end, + {test_init = test_init_door} +) + +test.register_coroutine_test( + "doorControl open following OverallCurrentState FULLY_OPENED", function() + update_profile_door() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_door_device.id, + clusters.ClosureControl.attributes.OverallCurrentState:build_test_report_data(mock_door_device, 10, + clusters.ClosureControl.types.OverallCurrentStateStruct({ + position = clusters.ClosureControl.types.CurrentPositionEnum.FULLY_OPENED, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO, + secure_state = false + })) + }) + test.socket.capability:__expect_send( + mock_door_device:generate_test_message("main", capabilities.doorControl.door.open()) + ) + end, + {test_init = test_init_door} +) + +test.register_coroutine_test( + "doorControl open following OverallCurrentState PARTIALLY_OPENED (doorControl has no partially_open)", function() + update_profile_door() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_door_device.id, + clusters.ClosureControl.attributes.OverallCurrentState:build_test_report_data(mock_door_device, 10, + clusters.ClosureControl.types.OverallCurrentStateStruct({ + position = clusters.ClosureControl.types.CurrentPositionEnum.PARTIALLY_OPENED, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO, + secure_state = false + })) + }) + -- doorControl has no partially_open state; any non-fully-closed position maps to open + test.socket.capability:__expect_send( + mock_door_device:generate_test_message("main", capabilities.doorControl.door.open()) + ) + end, + {test_init = test_init_door} +) + +test.register_coroutine_test( + "doorControl close command sends ClosureControl MoveTo FULLY_CLOSED", function() + test.socket.capability:__queue_receive({ + mock_door_device.id, + {capability = "doorControl", component = "main", command = "close", args = {}}, + }) + test.socket.matter:__expect_send({ + mock_door_device.id, + clusters.ClosureControl.server.commands.MoveTo( + mock_door_device, 10, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED + ) + }) + end, + {test_init = test_init_door} +) + +test.register_coroutine_test( + "doorControl open command sends ClosureControl MoveTo FULLY_OPEN", function() + test.socket.capability:__queue_receive({ + mock_door_device.id, + {capability = "doorControl", component = "main", command = "open", args = {}}, + }) + test.socket.matter:__expect_send({ + mock_door_device.id, + clusters.ClosureControl.server.commands.MoveTo( + mock_door_device, 10, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN + ) + }) + end, + {test_init = test_init_door} +) + +test.register_coroutine_test( + "ClosureDimension CurrentState on endpoint 11 emits level on door1 for door device", function() + update_profile_door() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_door_device.id, + clusters.ClosureDimension.attributes.CurrentState:build_test_report_data(mock_door_device, 11, + clusters.ClosureDimension.types.DimensionStateStruct({ + position = 7500, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO + }) + ) + }) + test.socket.capability:__expect_send( + mock_door_device:generate_test_message("door1", capabilities.level.level(75)) + ) + end, + {test_init = test_init_door} +) + +test.register_coroutine_test( + "ClosureDimension CurrentState on endpoint 12 emits level on door2 for door device", function() + update_profile_door() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_door_device.id, + clusters.ClosureDimension.attributes.CurrentState:build_test_report_data(mock_door_device, 12, + clusters.ClosureDimension.types.DimensionStateStruct({ + position = 3000, + latch = false, + speed = clusters.Global.types.ThreeLevelAutoEnum.AUTO + }) + ) + }) + test.socket.capability:__expect_send( + mock_door_device:generate_test_message("door2", capabilities.level.level(30)) + ) + end, + {test_init = test_init_door} +) + +test.run_registered_tests() From 853b02b92b4f2acf49807af8a9b41154494694ff Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 13 May 2026 05:11:07 +0800 Subject: [PATCH 349/538] add MultiIR Siren MIR-SR100 (#2873) --- .../SmartThings/zigbee-siren/fingerprints.yml | 5 + ...er-warningduration-volume-no-fw-update.yml | 36 ++ .../zigbee-siren/src/MultiIR/can_handle.lua | 13 + .../zigbee-siren/src/MultiIR/fingerprints.lua | 6 + .../zigbee-siren/src/MultiIR/init.lua | 119 +++++++ .../zigbee-siren/src/sub_drivers.lua | 1 + .../test/test_multiir_zigbee_siren_tamper.lua | 312 ++++++++++++++++++ tools/localizations/cn.csv | 1 + 8 files changed, 493 insertions(+) create mode 100644 drivers/SmartThings/zigbee-siren/profiles/switch-alarm-tamper-warningduration-volume-no-fw-update.yml create mode 100644 drivers/SmartThings/zigbee-siren/src/MultiIR/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-siren/src/MultiIR/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-siren/src/MultiIR/init.lua create mode 100644 drivers/SmartThings/zigbee-siren/src/test/test_multiir_zigbee_siren_tamper.lua diff --git a/drivers/SmartThings/zigbee-siren/fingerprints.yml b/drivers/SmartThings/zigbee-siren/fingerprints.yml index 57b192b294..3361b8b717 100644 --- a/drivers/SmartThings/zigbee-siren/fingerprints.yml +++ b/drivers/SmartThings/zigbee-siren/fingerprints.yml @@ -29,3 +29,8 @@ zigbeeManufacturer : manufacturer: Sercomm Corp. model: SZ-SRN12N deviceProfileName: basic-alarm + - id: "MultIR/MIR-SR100" + deviceLabel: MultiIR Siren MIR-SR100 + manufacturer: MultIR + model: MIR-SR100 + deviceProfileName: switch-alarm-tamper-warningduration-volume-no-fw-update diff --git a/drivers/SmartThings/zigbee-siren/profiles/switch-alarm-tamper-warningduration-volume-no-fw-update.yml b/drivers/SmartThings/zigbee-siren/profiles/switch-alarm-tamper-warningduration-volume-no-fw-update.yml new file mode 100644 index 0000000000..5192f236dc --- /dev/null +++ b/drivers/SmartThings/zigbee-siren/profiles/switch-alarm-tamper-warningduration-volume-no-fw-update.yml @@ -0,0 +1,36 @@ +name: switch-alarm-tamper-warningduration-volume-no-fw-update +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: alarm + version: 1 + - id: tamperAlert + version: 1 + - id: refresh + version: 1 + categories: + - name: Siren +preferences: + - title: "警报时长/秒(warning duration/sec)" + name: warningDuration + description: "警报持续时间 单位:秒(warning duration unit:seconds)" + required: false + preferenceType: integer + definition: + minimum: 30 + maximum: 1800 + default: 1800 + - title: "警报音量(siren volume)" + name: sirenVolume + description: "警报音量大小(siren volume)" + required: false + preferenceType: enumeration + definition: + options: + 0: "低(low)" + 1: "中(medium)" + 2: "高(high)" + 3: "最大(max)" + default: 3 diff --git a/drivers/SmartThings/zigbee-siren/src/MultiIR/can_handle.lua b/drivers/SmartThings/zigbee-siren/src/MultiIR/can_handle.lua new file mode 100644 index 0000000000..d389619c78 --- /dev/null +++ b/drivers/SmartThings/zigbee-siren/src/MultiIR/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require "MultiIR.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("MultiIR") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-siren/src/MultiIR/fingerprints.lua b/drivers/SmartThings/zigbee-siren/src/MultiIR/fingerprints.lua new file mode 100644 index 0000000000..ef955eecc6 --- /dev/null +++ b/drivers/SmartThings/zigbee-siren/src/MultiIR/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "MultIR", model = "MIR-SR100" } +} diff --git a/drivers/SmartThings/zigbee-siren/src/MultiIR/init.lua b/drivers/SmartThings/zigbee-siren/src/MultiIR/init.lua new file mode 100644 index 0000000000..c9934936af --- /dev/null +++ b/drivers/SmartThings/zigbee-siren/src/MultiIR/init.lua @@ -0,0 +1,119 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local data_types = require "st.zigbee.data_types" +local zcl_clusters = require "st.zigbee.zcl.clusters" + +local IASWD = zcl_clusters.IASWD +local IASZone = zcl_clusters.IASZone +local IaswdLevel = IASWD.types.IaswdLevel +local SirenConfiguration = IASWD.types.SirenConfiguration +local WarningMode = IASWD.types.WarningMode +local Strobe = IASWD.types.Strobe +local capabilities = require "st.capabilities" +local ALARM_COMMAND = "alarmCommand" +local DEFAULT_MAX_WARNING_DURATION = 1800 +local ALARM_STROBE_DUTY_CYCLE = 40 + +local alarm_command = { + OFF = 0, + SIREN = 1, + STROBE = 2, + BOTH = 3 +} + +local function device_added (driver, device) + device:emit_event(capabilities.switch.switch.off()) + device:emit_event(capabilities.alarm.alarm.off()) + if(device:supports_capability(capabilities.tamperAlert)) then + device:emit_event(capabilities.tamperAlert.tamper.clear()) + end + device:send(IASWD.attributes.MaxDuration:read(device)) +end + +local function generate_event_from_zone_status(driver, device, zone_status, zb_rx) + if device:supports_capability(capabilities.tamperAlert) then + device:emit_event(zone_status:is_tamper_set() and capabilities.tamperAlert.tamper.detected() or capabilities.tamperAlert.tamper.clear()) + end +end + +local function ias_zone_status_change_handler(driver, device, zb_rx) + local zone_status = zb_rx.body.zcl_body.zone_status + generate_event_from_zone_status(driver, device, zone_status, zb_rx) +end + +local function send_siren_command(device, warning_mode, warning_siren_level, warning_duration, strobe_active, strobe_level) + local siren_configuration + + siren_configuration = SirenConfiguration(0x00) + siren_configuration:set_warning_mode(warning_mode) + siren_configuration:set_siren_level(warning_siren_level) + siren_configuration:set_strobe(strobe_active) + + device:send( + IASWD.server.commands.StartWarning( + device, + siren_configuration, + data_types.Uint16(warning_duration), + data_types.Uint8(ALARM_STROBE_DUTY_CYCLE), + data_types.Enum8(strobe_level) + ) + ) +end + +local function siren_switch_off_handler(driver, device, command) + device:set_field(ALARM_COMMAND, alarm_command.OFF, {persist = true}) + send_siren_command(device, WarningMode.STOP, IaswdLevel.LOW_LEVEL, DEFAULT_MAX_WARNING_DURATION, Strobe.NO_STROBE, IaswdLevel.LOW_LEVEL) +end + +local function siren_alarm_siren_handler(alarm_cmd, WarningMode, Strobe, strobe_level) + return function(driver, device, command) + device:set_field(ALARM_COMMAND, alarm_cmd, {persist = true}) + + local sirenVolume_msg = tonumber(device.preferences.sirenVolume) or IaswdLevel.VERY_HIGH_LEVEL + local warning_duration = tonumber(device.preferences.warningDuration) or DEFAULT_MAX_WARNING_DURATION + + send_siren_command(device, WarningMode, sirenVolume_msg, warning_duration, Strobe, strobe_level) + + device.thread:call_with_delay(warning_duration, function() -- Send command to switch from siren to off in the app when the siren is done + if(device:get_field(ALARM_COMMAND) ~= alarm_command.OFF) then + siren_switch_off_handler(driver, device, alarm_cmd) + end + end) + end +end + +local MultiIR_siren_driver = { + NAME = "MultiIR Zigbee siren", + lifecycle_handlers = { + added = device_added, + }, + capability_handlers = { + [capabilities.alarm.ID] = { + [capabilities.alarm.commands.off.NAME] = siren_switch_off_handler, + [capabilities.alarm.commands.siren.NAME] = siren_alarm_siren_handler(alarm_command.SIREN, WarningMode.BURGLAR, Strobe.NO_STROBE, IaswdLevel.LOW_LEVEL), + [capabilities.alarm.commands.both.NAME] = siren_alarm_siren_handler(alarm_command.BOTH, WarningMode.BURGLAR, Strobe.USE_STROBE , IaswdLevel.VERY_HIGH_LEVEL), + [capabilities.alarm.commands.strobe.NAME] = siren_alarm_siren_handler(alarm_command.STROBE, WarningMode.STOP, Strobe.USE_STROBE, IaswdLevel.VERY_HIGH_LEVEL) + }, + [capabilities.switch.ID] = { + [capabilities.switch.commands.on.NAME] = siren_alarm_siren_handler(alarm_command.BOTH, WarningMode.BURGLAR, Strobe.USE_STROBE , IaswdLevel.VERY_HIGH_LEVEL), + [capabilities.switch.commands.off.NAME] = siren_switch_off_handler + } + }, + zigbee_handlers = { + cluster = { + [IASZone.ID] = { + [IASZone.client.commands.ZoneStatusChangeNotification.ID] = ias_zone_status_change_handler + } + }, + attr = { + [IASZone.ID] = { + [IASZone.attributes.ZoneStatus.ID] = generate_event_from_zone_status + } + } + }, + can_handle = require("MultiIR.can_handle"), +} + +return MultiIR_siren_driver diff --git a/drivers/SmartThings/zigbee-siren/src/sub_drivers.lua b/drivers/SmartThings/zigbee-siren/src/sub_drivers.lua index 05d186b87e..5f5953b04e 100644 --- a/drivers/SmartThings/zigbee-siren/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-siren/src/sub_drivers.lua @@ -6,4 +6,5 @@ local lazy_load_if_possible = require "lazy_load_subdriver" return { lazy_load_if_possible("frient"), lazy_load_if_possible("ozom"), + lazy_load_if_possible("MultiIR"), } diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_multiir_zigbee_siren_tamper.lua b/drivers/SmartThings/zigbee-siren/src/test/test_multiir_zigbee_siren_tamper.lua new file mode 100644 index 0000000000..24dc8766f3 --- /dev/null +++ b/drivers/SmartThings/zigbee-siren/src/test/test_multiir_zigbee_siren_tamper.lua @@ -0,0 +1,312 @@ +-- 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. + +-- Mock out globals +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local OnOff = clusters.OnOff +local IASZone = clusters.IASZone +local IASWD = clusters.IASWD +local SirenConfiguration = IASWD.types.SirenConfiguration +local DEFAULT_MAX_WARNING_DURATION = 1800 +local ALARM_STROBE_DUTY_CYCLE = 40 +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local data_types = require "st.zigbee.data_types" +local t_utils = require "integration_test.utils" + +local IaswdLevel = IASWD.types.IaswdLevel +local WarningMode = IASWD.types.WarningMode +local Strobe = IASWD.types.Strobe + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("switch-alarm-tamper-warningduration-volume-no-fw-update.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "MultIR", + model = "MIR-SR100", + server_clusters = { IASWD.ID, IASZone.ID,OnOff.ID} + } + } + } +) + +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( + "Handle added lifecycle", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__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", + capabilities.switch.switch.off())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.alarm.alarm.off())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tamperAlert.tamper.clear())) + test.socket.zigbee:__expect_send({ mock_device.id, + IASWD.attributes.MaxDuration:read(mock_device)}) + end, + { + min_api_version = 19 + } +) + +local function send_startWarning( device, WarningMode, sirenVolume_msg, warning_duration, strobe_active, strobe_level ) + local expectedSirenONConfiguration = SirenConfiguration(0x00) + expectedSirenONConfiguration:set_warning_mode(WarningMode) --WarningMode.BURGLAR + expectedSirenONConfiguration:set_siren_level(sirenVolume_msg) --IaswdLevel.VERY_HIGH_LEVEL + expectedSirenONConfiguration:set_strobe(strobe_active) + + test.socket.zigbee:__expect_send({ + device.id, + IASWD.server.commands.StartWarning( + device, + expectedSirenONConfiguration, + data_types.Uint16(warning_duration), + data_types.Uint8(ALARM_STROBE_DUTY_CYCLE), + data_types.Enum8(strobe_level) + ) + }) +end + +test.register_coroutine_test( + "Capability(switch) command(on) should be handled", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "switch", component = "main", command = "on", args = {} } + }) + + local sirenVolume_msg = tonumber(mock_device.preferences.sirenVolume) or IaswdLevel.VERY_HIGH_LEVEL + local warning_duration = tonumber(mock_device.preferences.warningDuration) or DEFAULT_MAX_WARNING_DURATION + send_startWarning(mock_device, WarningMode.BURGLAR, sirenVolume_msg, warning_duration, Strobe.USE_STROBE, IaswdLevel.VERY_HIGH_LEVEL) + test.mock_time.advance_time(warning_duration) + send_startWarning(mock_device, WarningMode.STOP, IaswdLevel.LOW_LEVEL, DEFAULT_MAX_WARNING_DURATION, Strobe.NO_STROBE, IaswdLevel.LOW_LEVEL) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability(switch) command(off) should be handled", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "switch", component = "main", command = "off", args = {} } + }) + send_startWarning(mock_device, WarningMode.STOP, IaswdLevel.LOW_LEVEL, DEFAULT_MAX_WARNING_DURATION, Strobe.NO_STROBE, IaswdLevel.LOW_LEVEL) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability(alarm) command(both) should be handled", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "both", args = {} } + }) + + local sirenVolume_msg = tonumber(mock_device.preferences.sirenVolume) or IaswdLevel.VERY_HIGH_LEVEL + local warning_duration = tonumber(mock_device.preferences.warningDuration) or DEFAULT_MAX_WARNING_DURATION + send_startWarning(mock_device, WarningMode.BURGLAR, sirenVolume_msg, warning_duration, Strobe.USE_STROBE, IaswdLevel.VERY_HIGH_LEVEL) + test.mock_time.advance_time(warning_duration) + send_startWarning(mock_device, WarningMode.STOP, IaswdLevel.LOW_LEVEL, DEFAULT_MAX_WARNING_DURATION, Strobe.NO_STROBE, IaswdLevel.LOW_LEVEL) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability(alarm) command(off) should be handled", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "off", args = {} } + }) + + send_startWarning(mock_device, WarningMode.STOP, IaswdLevel.LOW_LEVEL, DEFAULT_MAX_WARNING_DURATION, Strobe.NO_STROBE, IaswdLevel.LOW_LEVEL) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability(alarm) command(siren) should be handled", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + + local sirenVolume_msg = tonumber(mock_device.preferences.sirenVolume) or IaswdLevel.VERY_HIGH_LEVEL + local warning_duration = tonumber(mock_device.preferences.warningDuration) or DEFAULT_MAX_WARNING_DURATION + send_startWarning(mock_device, WarningMode.BURGLAR, sirenVolume_msg, warning_duration, Strobe.NO_STROBE, IaswdLevel.LOW_LEVEL) + test.mock_time.advance_time(warning_duration) + send_startWarning(mock_device, WarningMode.STOP, IaswdLevel.LOW_LEVEL, DEFAULT_MAX_WARNING_DURATION, Strobe.NO_STROBE, IaswdLevel.LOW_LEVEL) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Capability(alarm) command(strobe) should be handled", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "strobe", args = {} } + }) + + local sirenVolume_msg = tonumber(mock_device.preferences.sirenVolume) or IaswdLevel.VERY_HIGH_LEVEL + local warning_duration = tonumber(mock_device.preferences.warningDuration) or DEFAULT_MAX_WARNING_DURATION + send_startWarning(mock_device, WarningMode.STOP, sirenVolume_msg, warning_duration, Strobe.USE_STROBE, IaswdLevel.VERY_HIGH_LEVEL) + test.mock_time.advance_time(warning_duration) + send_startWarning(mock_device, WarningMode.STOP, IaswdLevel.LOW_LEVEL, DEFAULT_MAX_WARNING_DURATION, Strobe.NO_STROBE, IaswdLevel.LOW_LEVEL) + end, + { + min_api_version = 19 + } +) + +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, + OnOff.attributes.OnOff:read(mock_device) + } + } + }, + { + inner_block_ordering = "relaxed", + min_api_version = 19 + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0004, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: tamper/clear", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported ZoneStatus should be handled: tamper/clear", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported ZoneStatus should be handled: tamper/detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0004) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + }, + { + min_api_version = 19 + } +) + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 63f5f93a42..a67fdfeddf 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -139,3 +139,4 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Contact Sensor MIR-MC100",麦乐克门窗开关传感器MIR-MC100 "MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 +"MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 From 9405cda8adc8b170e723c58953c5b6403e6cea57 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 13 May 2026 13:49:08 -0500 Subject: [PATCH 350/538] CHAD-16991: Removed zwave-button apiv6 subdriver --- .../src/apiv6_bugfix/can_handle.lua | 16 ------------- .../zwave-button/src/apiv6_bugfix/init.lua | 24 ------------------- .../zwave-button/src/sub_drivers.lua | 1 - 3 files changed, 41 deletions(-) delete mode 100644 drivers/SmartThings/zwave-button/src/apiv6_bugfix/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-button/src/apiv6_bugfix/init.lua diff --git a/drivers/SmartThings/zwave-button/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-button/src/apiv6_bugfix/can_handle.lua deleted file mode 100644 index 8a9b8cc6cc..0000000000 --- a/drivers/SmartThings/zwave-button/src/apiv6_bugfix/can_handle.lua +++ /dev/null @@ -1,16 +0,0 @@ --- 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-button/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-button/src/apiv6_bugfix/init.lua deleted file mode 100644 index fd309d8e29..0000000000 --- a/drivers/SmartThings/zwave-button/src/apiv6_bugfix/init.lua +++ /dev/null @@ -1,24 +0,0 @@ --- 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 wakeup_notification(driver, device, cmd) - device:refresh() -end - -local apiv6_bugfix = { - zwave_handlers = { - [cc.WAKE_UP] = { - [WakeUp.NOTIFICATION] = wakeup_notification - } - }, - NAME = "apiv6_bugfix", - can_handle = require("apiv6_bugfix.can_handle"), - shared_device_thread_enabled = true, -} - -return apiv6_bugfix diff --git a/drivers/SmartThings/zwave-button/src/sub_drivers.lua b/drivers/SmartThings/zwave-button/src/sub_drivers.lua index 57e87ad8ad..64a9813468 100644 --- a/drivers/SmartThings/zwave-button/src/sub_drivers.lua +++ b/drivers/SmartThings/zwave-button/src/sub_drivers.lua @@ -4,6 +4,5 @@ local lazy_load_if_possible = require "lazy_load_subdriver" local sub_drivers = { lazy_load_if_possible("zwave-multi-button"), - lazy_load_if_possible("apiv6_bugfix"), } return sub_drivers From 449e2798d0c03c252d93ce078bde74bd547a5c0d Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 13 May 2026 13:51:17 -0500 Subject: [PATCH 351/538] CHAD-16991: Removed zwave-lock apiv6 subdriver --- .../src/apiv6_bugfix/can_handle.lua | 16 -------------- .../zwave-lock/src/apiv6_bugfix/init.lua | 21 ------------------- .../zwave-lock/src/sub_drivers.lua | 1 - 3 files changed, 38 deletions(-) delete mode 100644 drivers/SmartThings/zwave-lock/src/apiv6_bugfix/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-lock/src/apiv6_bugfix/init.lua diff --git a/drivers/SmartThings/zwave-lock/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-lock/src/apiv6_bugfix/can_handle.lua deleted file mode 100644 index 8a9b8cc6cc..0000000000 --- a/drivers/SmartThings/zwave-lock/src/apiv6_bugfix/can_handle.lua +++ /dev/null @@ -1,16 +0,0 @@ --- 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-lock/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-lock/src/apiv6_bugfix/init.lua deleted file mode 100644 index 94dc5975ab..0000000000 --- a/drivers/SmartThings/zwave-lock/src/apiv6_bugfix/init.lua +++ /dev/null @@ -1,21 +0,0 @@ --- 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 wakeup_notification(driver, device, cmd) - device:refresh() -end - -local apiv6_bugfix = { - zwave_handlers = { - [cc.WAKE_UP] = { - [WakeUp.NOTIFICATION] = wakeup_notification - } - }, - NAME = "apiv6_bugfix", - can_handle = require("apiv6_bugfix.can_handle"), -} - -return apiv6_bugfix diff --git a/drivers/SmartThings/zwave-lock/src/sub_drivers.lua b/drivers/SmartThings/zwave-lock/src/sub_drivers.lua index 46700ce154..627fe99514 100644 --- a/drivers/SmartThings/zwave-lock/src/sub_drivers.lua +++ b/drivers/SmartThings/zwave-lock/src/sub_drivers.lua @@ -7,6 +7,5 @@ local sub_drivers = { lazy_load_if_possible("schlage-lock"), lazy_load_if_possible("samsung-lock"), lazy_load_if_possible("keywe-lock"), - lazy_load_if_possible("apiv6_bugfix"), } return sub_drivers From a80ca3d3e5a5520ece73a3e4fdc83cad523d9f1b Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 13 May 2026 13:51:17 -0500 Subject: [PATCH 352/538] CHAD-16991: Removed zwave-sensor apiv6 subdriver --- .../src/apiv6_bugfix/can_handle.lua | 35 ------------------- .../zwave-sensor/src/apiv6_bugfix/init.lua | 22 ------------ .../zwave-sensor/src/sub_drivers.lua | 1 - 3 files changed, 58 deletions(-) delete mode 100644 drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua diff --git a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua deleted file mode 100644 index 4913e9a25e..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/can_handle.lua +++ /dev/null @@ -1,35 +0,0 @@ --- 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 }) - --- doing refresh would cause incorrect state for device, see comments in wakeup-no-poll -local NORTEK_FP = {mfr = 0x014F, prod = 0x2001, model = 0x0102} -- NorTek open/close sensor -local POPP_THERMOSTAT_FP = {mfr = 0x0002, prod = 0x0115, model = 0xA010} --Popp thermostat -local AEOTEC_MULTISENSOR_6_FP = {mfr = 0x0086, model = 0x0064} --Aeotec multisensor 6 -local AEOTEC_MULTISENSOR_7_FP = {mfr = 0x0371, model = 0x0018} --Aeotec multisensor 7 -local ENERWAVE_MOTION_FP = {mfr = 0x011A} --Enerwave motion sensor -local HOMESEER_MULTI_SENSOR_FP = {mfr = 0x001E, prod = 0x0002, model = 0x0001} -- Homeseer multi sensor HSM100 -local SENSATIVE_STRIP_FP = {mfr = 0x019A, model = 0x000A} -local FPS = {NORTEK_FP, POPP_THERMOSTAT_FP, - AEOTEC_MULTISENSOR_6_FP, AEOTEC_MULTISENSOR_7_FP, - ENERWAVE_MOTION_FP, HOMESEER_MULTI_SENSOR_FP, SENSATIVE_STRIP_FP} - -local function can_handle(opts, driver, device, cmd, ...) - local version = require "version" - if version.api == 6 and - cmd.cmd_class == cc.WAKE_UP and - cmd.cmd_id == WakeUp.NOTIFICATION then - - for _, fp in ipairs(FPS) do - if device:id_match(fp.mfr, fp.prod, fp.model) then return false end - end - local subdriver = require("apiv6_bugfix") - return true, subdriver - else - return false - end -end - -return can_handle diff --git a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua deleted file mode 100644 index 38b8a6612f..0000000000 --- a/drivers/SmartThings/zwave-sensor/src/apiv6_bugfix/init.lua +++ /dev/null @@ -1,22 +0,0 @@ --- 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 wakeup_notification(driver, device, cmd) - device:refresh() -end - -local apiv6_bugfix = { - zwave_handlers = { - [cc.WAKE_UP] = { - [WakeUp.NOTIFICATION] = wakeup_notification - } - }, - NAME = "apiv6_bugfix", - can_handle = require("apiv6_bugfix.can_handle"), - shared_device_thread_enabled = true, -} - -return apiv6_bugfix diff --git a/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua b/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua index 9504479304..9500731597 100644 --- a/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua +++ b/drivers/SmartThings/zwave-sensor/src/sub_drivers.lua @@ -22,5 +22,4 @@ return { 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"), } From d84fe48b69d9c219688d9995ad00ac764337df10 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 13 May 2026 13:51:17 -0500 Subject: [PATCH 353/538] CHAD-16991: Removed zwave-siren apiv6 subdriver --- .../src/apiv6_bugfix/can_handle.lua | 17 -------------- .../zwave-siren/src/apiv6_bugfix/init.lua | 23 ------------------- .../zwave-siren/src/sub_drivers.lua | 1 - 3 files changed, 41 deletions(-) delete mode 100644 drivers/SmartThings/zwave-siren/src/apiv6_bugfix/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-siren/src/apiv6_bugfix/init.lua diff --git a/drivers/SmartThings/zwave-siren/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-siren/src/apiv6_bugfix/can_handle.lua deleted file mode 100644 index 3f4b44c1e0..0000000000 --- a/drivers/SmartThings/zwave-siren/src/apiv6_bugfix/can_handle.lua +++ /dev/null @@ -1,17 +0,0 @@ --- 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 deleted file mode 100644 index 2e7e3ca3b8..0000000000 --- a/drivers/SmartThings/zwave-siren/src/apiv6_bugfix/init.lua +++ /dev/null @@ -1,23 +0,0 @@ --- 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 wakeup_notification(driver, device, cmd) - device:refresh() -end - -local apiv6_bugfix = { - zwave_handlers = { - [cc.WAKE_UP] = { - [WakeUp.NOTIFICATION] = wakeup_notification - } - }, - NAME = "apiv6_bugfix", - can_handle = require("apiv6_bugfix.can_handle"), -} - -return apiv6_bugfix diff --git a/drivers/SmartThings/zwave-siren/src/sub_drivers.lua b/drivers/SmartThings/zwave-siren/src/sub_drivers.lua index 12ce423ba5..52d5035a16 100644 --- a/drivers/SmartThings/zwave-siren/src/sub_drivers.lua +++ b/drivers/SmartThings/zwave-siren/src/sub_drivers.lua @@ -14,6 +14,5 @@ local sub_drivers = { 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 From 6480128257c6bfa894a0e40d64d2d3ab9e3c87c2 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 13 May 2026 13:51:17 -0500 Subject: [PATCH 354/538] CHAD-16991: Removed zwave-smoke-alarm apiv6 subdriver --- .../src/apiv6_bugfix/can_handle.lua | 16 -------------- .../src/apiv6_bugfix/init.lua | 21 ------------------- .../zwave-smoke-alarm/src/sub_drivers.lua | 1 - 3 files changed, 38 deletions(-) delete mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/init.lua diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/can_handle.lua deleted file mode 100644 index 8a9b8cc6cc..0000000000 --- a/drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/can_handle.lua +++ /dev/null @@ -1,16 +0,0 @@ --- 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-smoke-alarm/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/init.lua deleted file mode 100644 index 94dc5975ab..0000000000 --- a/drivers/SmartThings/zwave-smoke-alarm/src/apiv6_bugfix/init.lua +++ /dev/null @@ -1,21 +0,0 @@ --- 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 wakeup_notification(driver, device, cmd) - device:refresh() -end - -local apiv6_bugfix = { - zwave_handlers = { - [cc.WAKE_UP] = { - [WakeUp.NOTIFICATION] = wakeup_notification - } - }, - NAME = "apiv6_bugfix", - can_handle = require("apiv6_bugfix.can_handle"), -} - -return apiv6_bugfix diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/sub_drivers.lua b/drivers/SmartThings/zwave-smoke-alarm/src/sub_drivers.lua index f0a2d96412..39901aaf21 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/sub_drivers.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/sub_drivers.lua @@ -6,6 +6,5 @@ local sub_drivers = { lazy_load_if_possible("zwave-smoke-co-alarm-v1"), lazy_load_if_possible("zwave-smoke-co-alarm-v2"), lazy_load_if_possible("fibaro-smoke-sensor"), - lazy_load_if_possible("apiv6_bugfix"), } return sub_drivers From 24d35057d28ee217edcd102946e05aa1e30d3173 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 13 May 2026 13:51:17 -0500 Subject: [PATCH 355/538] CHAD-16991: Removed zwave-thermostat apiv6 subdriver --- .../src/apiv6_bugfix/can_handle.lua | 25 ------------------- .../src/apiv6_bugfix/fingerprints.lua | 9 ------- .../src/apiv6_bugfix/init.lua | 22 ---------------- .../zwave-thermostat/src/sub_drivers.lua | 1 - 4 files changed, 57 deletions(-) delete mode 100644 drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/can_handle.lua delete mode 100644 drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/init.lua diff --git a/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/can_handle.lua deleted file mode 100644 index 079eeee5d3..0000000000 --- a/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/can_handle.lua +++ /dev/null @@ -1,25 +0,0 @@ --- 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 deleted file mode 100644 index e87a5990e2..0000000000 --- a/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/fingerprints.lua +++ /dev/null @@ -1,9 +0,0 @@ --- 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 deleted file mode 100644 index 52c419a590..0000000000 --- a/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/init.lua +++ /dev/null @@ -1,22 +0,0 @@ --- 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 wakeup_notification(driver, device, cmd) - device:refresh() -end - -local apiv6_bugfix = { - zwave_handlers = { - [cc.WAKE_UP] = { - [WakeUp.NOTIFICATION] = wakeup_notification - } - }, - NAME = "apiv6_bugfix", - can_handle = require("apiv6_bugfix.can_handle"), -} - -return apiv6_bugfix diff --git a/drivers/SmartThings/zwave-thermostat/src/sub_drivers.lua b/drivers/SmartThings/zwave-thermostat/src/sub_drivers.lua index dc30091b79..38b6d5de87 100644 --- a/drivers/SmartThings/zwave-thermostat/src/sub_drivers.lua +++ b/drivers/SmartThings/zwave-thermostat/src/sub_drivers.lua @@ -10,6 +10,5 @@ local sub_drivers = { 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 From 758245bf208ac02d89fedac08faaf558352e65a0 Mon Sep 17 00:00:00 2001 From: noidlet Date: Mon, 18 May 2026 10:46:11 -0400 Subject: [PATCH 356/538] Fix duplicate profile check to handle deleted files The check_duplicates.py script was failing when a PR deleted a profile file. The script would try to open the deleted file for comparison, causing a FileNotFoundError. This fix adds an existence check before attempting to open the file, skipping deleted files with an informative message. --- .github/scripts/check_duplicates.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/scripts/check_duplicates.py b/.github/scripts/check_duplicates.py index b4bf01649d..ae0a26f687 100644 --- a/.github/scripts/check_duplicates.py +++ b/.github/scripts/check_duplicates.py @@ -112,9 +112,15 @@ def compare_components(prof1, prof2): print('\nNEW PROFILE:\n%s is a profile! Comparing to other profiles...' % file) os.chdir(file_directory) - for current_profile in os.listdir("./"): - new_profile = file_basename + new_profile = file_basename + + # Skip deleted files + if not os.path.exists(new_profile): + print("Skipping %s - file was deleted" % new_profile) + os.chdir(cwd) + continue + for current_profile in os.listdir("./"): # compare to YAML files that are not the same file # Compare only .yml files and only files that have not already been found to be a duplicate if current_profile != new_profile and Path(current_profile).suffix == ".yml" and (current_profile, new_profile) not in duplicate_pairs: From 732cc20f54b1c5067828d818b94432ea1f3f8b2c Mon Sep 17 00:00:00 2001 From: noidlet Date: Mon, 18 May 2026 11:15:52 -0400 Subject: [PATCH 357/538] Emit warning when profile files are deleted in PR --- .github/scripts/check_duplicates.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/scripts/check_duplicates.py b/.github/scripts/check_duplicates.py index ae0a26f687..65e2ae2ea6 100644 --- a/.github/scripts/check_duplicates.py +++ b/.github/scripts/check_duplicates.py @@ -5,6 +5,7 @@ cwd = os.getcwd() duplicate_pairs = [] +deleted_profiles = [] def compare_component_capabilities_unordered(comp1, comp2): for cap1 in comp1["capabilities"]: @@ -114,9 +115,10 @@ def compare_components(prof1, prof2): os.chdir(file_directory) new_profile = file_basename - # Skip deleted files + # Skip deleted files and track them for warning if not os.path.exists(new_profile): print("Skipping %s - file was deleted" % new_profile) + deleted_profiles.append(file) os.chdir(cwd) continue @@ -151,7 +153,12 @@ def compare_components(prof1, prof2): for duplicate in duplicate_pairs: f.write("%s == %s\n" % (duplicate[0], duplicate [1])) else: - f.write("Duplicate profile check: Passed - no duplicate profiles detected.") + f.write("Duplicate profile check: Passed - no duplicate profiles detected.\n") + + if deleted_profiles: + f.write("\n:warning: **Deleted profile files detected:**\n") + for deleted in deleted_profiles: + f.write("- `%s`\n" % deleted) with open("profile-comment-body.md", "r") as f: print("\n" + f.read()) From 1607bb32b46f86077fd8d2300cbe7d50b687410b Mon Sep 17 00:00:00 2001 From: cjswedes Date: Fri, 15 May 2026 12:25:13 -0500 Subject: [PATCH 358/538] Add AGENTS.md and a set of skills to help agents in this repository These are based on what I have setup for myself locally, but are not exactly the same, so are somewhat untested. Some of the skills and potentially even the AGENTS.md file may be too large for very small models, but I think this will really help anyone using tools like opencode, claude code, codex, etc when working with agents in this repo. --- .agents/skills/dev-workflow/SKILL.md | 264 +++++++++++++ .agents/skills/linting-and-style/SKILL.md | 96 +++++ .agents/skills/testing-edge-drivers/SKILL.md | 349 +++++++++++++++++ .../understanding-lua-libraries/SKILL.md | 295 ++++++++++++++ .../skills/understanding-profiles/SKILL.md | 369 ++++++++++++++++++ .github/copilot-instructions.md | 52 +++ AGENTS.md | 130 ++++++ 7 files changed, 1555 insertions(+) create mode 100644 .agents/skills/dev-workflow/SKILL.md create mode 100644 .agents/skills/linting-and-style/SKILL.md create mode 100644 .agents/skills/testing-edge-drivers/SKILL.md create mode 100644 .agents/skills/understanding-lua-libraries/SKILL.md create mode 100644 .agents/skills/understanding-profiles/SKILL.md create mode 100644 .github/copilot-instructions.md create mode 100644 AGENTS.md diff --git a/.agents/skills/dev-workflow/SKILL.md b/.agents/skills/dev-workflow/SKILL.md new file mode 100644 index 0000000000..23f4ce17df --- /dev/null +++ b/.agents/skills/dev-workflow/SKILL.md @@ -0,0 +1,264 @@ +--- +name: dev-workflow +description: Setting up the development environment, deploying Edge Drivers to hubs, and sharing drivers with other users via channels and invites +--- + +# SmartThings Edge Driver Development Workflow + +This skill covers environment setup, driver deployment to hubs, and sharing +drivers with other users through channels and invite links. + +--- + +## Environment Setup + +### 1. Install Lua 5.3 + +Edge Drivers are Lua-based. Install the Lua 5.3 runtime for local development +and linting: + +```bash +# Ubuntu / Debian +sudo apt install lua5.3 + +# macOS +brew install lua@5.3 + +# Windows +# Download the Lua 5.3 binary from https://luabinaries.sourceforge.net/download.html +# Or install via scoop: +scoop install lua +# Or via chocolatey: +choco install lua53 +``` + +### 2. lua_libs Directory + +The `lua_libs/` directory contains the SmartThings Lua libraries that are +available on the hub at runtime. These correspond to the assets attached to the +latest release on GitHub: + + + +Download the lua_libs archive from the release assets and +extract it into the repository root if it is missing or needs updating. + +### 3. Configure LUA_PATH + +Set `LUA_PATH` so that `require` resolves both your driver modules and the +SmartThings library modules in `lua_libs/`: + +```bash +export LUA_PATH="./?.lua;./?/init.lua;$(pwd)/lua_libs/?.lua;$(pwd)/lua_libs/?/init.lua;;" +``` + +Run it from the repository root so `$(pwd)` resolves correctly. + + +### 4. Install the SmartThings CLI + +The CLI is required for packaging, deploying, and managing drivers and +channels on the platform. + +```bash +# Via npm (requires Node.js >= 24.8.0) +npm install -g @smartthings/cli + +# macOS via Homebrew +brew install smartthingscommunity/smartthings/smartthings + +# Linux / Windows +# Download the binary or installer from: +# https://github.com/SmartThingsCommunity/smartthings-cli/releases +``` + +Verify the installation: + +```bash +smartthings --version +``` + +The CLI uses browser-based OAuth login by default. Run `smartthings devices` to trigger +the login flow. + +### 5. Python Requirements (Testing) + +Some test and tooling scripts require Python dependencies: + +```bash +pip install -r tools/requirements.txt +``` + +### 6. Install Luacheck (Linting) + +Luacheck provides static analysis for Lua source files. It requires LuaRocks +(the Lua package manager). + +**Install LuaRocks first:** + +```bash +# Ubuntu / Debian +sudo apt install luarocks + +# macOS +brew install luarocks + +# Windows +# Download the installer from https://luarocks.org/releases/ +# Or via chocolatey: +choco install luarocks +``` + +**Then install Luacheck:** + +```bash +# Via LuaRocks (all platforms) +luarocks install luacheck + +# macOS alternative (installs both luarocks and luacheck) +brew install luacheck +``` + +Run it against a driver directory: + +```bash +luacheck --config .github/workflows/.luacheckrc drivers/SmartThings/zigbee-switch/ +``` + +--- + +## Deploying Drivers + +### Overview + +Deploying a driver to a physical hub requires three things: + +1. A **channel** you own. +2. The hub **enrolled** in that channel. +3. The driver **packaged and uploaded** through the CLI. + +### Step 1: Create a Channel + +```bash +smartthings edge:channels:create +``` + +You will be prompted for a name and description. Note the returned channel ID. + +### Step 2: Enroll Your Hub + +```bash +smartthings edge:channels:enroll +``` + +Select the channel when prompted, or pass `--channel `. + +Find your hub ID with: + +```bash +smartthings devices --type HUB +``` + +### Step 3: Package and Install the Driver + +The `edge:drivers:package` command can build, upload, assign to a channel, and +install in one step: + +```bash +smartthings edge:drivers:package \ + --hub= \ + --channel= +``` + +For example: + +```bash +smartthings edge:drivers:package drivers/SmartThings/zwave-switch \ + --hub=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee \ + --channel=11111111-2222-3333-4444-555555555555 +``` + +### Other Useful Deployment Commands + +```bash +# List drivers installed on a hub +smartthings edge:drivers:installed --hub= + +# Stream logs from a driver on the hub +smartthings edge:drivers:logcat --hub= + +# Uninstall a driver from a hub +smartthings edge:drivers:uninstall --hub= + +# Remove unused drivers from a hub +smartthings edge:drivers:prune --hub= + +# Switch a device to a different driver +smartthings edge:drivers:switch +``` + +--- + +## Sharing Drivers + +### Creating an Invite Link + +Invite links let other users install your driver from your channel without +giving them ownership of the driver or channel. + +```bash +smartthings edge:channels:invites:create +``` + +You will be prompted to select a channel and a driver. The command returns an +invite URL of the form: + +``` +https://bestow-regional.api.smartthings.com/invite/ +``` + +Share this URL with users. They open it in a browser or the SmartThings mobile +app to accept the invitation. + +### Enrollment Flow for Recipients + +1. The recipient opens the invite link. +2. They log in to their Samsung / SmartThings account. +3. They select a hub to enroll in the channel. +4. The driver can be selected to install to that hub. + +### Managing Invites + +```bash +# List existing invites +smartthings edge:channels:invites + +# Delete an invite +smartthings edge:channels:invites:delete +``` + +### Managing Channel Assignments + +```bash +# Assign a specific driver version to a channel +smartthings edge:channels:assign + +# List drivers assigned to a channel +smartthings edge:channels:drivers + +# Remove a driver from a channel +smartthings edge:channels:unassign +``` + +--- + +## Quick Reference + +| Task | Command | +|------|---------| +| Create channel | `smartthings edge:channels:create` | +| Enroll hub | `smartthings edge:channels:enroll ` | +| Package & deploy | `smartthings edge:drivers:package --hub= --channel=` | +| Stream logs | `smartthings edge:drivers:logcat --hub=` | +| Create invite | `smartthings edge:channels:invites:create` | +| List installed drivers | `smartthings edge:drivers:installed --hub=` | diff --git a/.agents/skills/linting-and-style/SKILL.md b/.agents/skills/linting-and-style/SKILL.md new file mode 100644 index 0000000000..83578854ce --- /dev/null +++ b/.agents/skills/linting-and-style/SKILL.md @@ -0,0 +1,96 @@ +--- +name: linting-and-style +description: Running luacheck for Lua linting and following code style conventions in Edge Driver development +--- + +# Linting and Code Style for Edge Drivers + +## Running Luacheck + +```bash +luacheck --config .github/workflows/.luacheckrc +``` + +### Examples + +```bash +# Lint a specific driver +luacheck --config .github/workflows/.luacheckrc drivers/SmartThings/zigbee-switch/ + +# Lint a single file +luacheck --config .github/workflows/.luacheckrc drivers/SmartThings/zigbee-switch/src/init.lua + +# Lint the entire repo +luacheck --config .github/workflows/.luacheckrc . +``` + +Luacheck runs automatically in CI on pull requests that modify files under `drivers/` (see `.github/workflows/luacheck.yml`). + +## Code Style Conventions + +These conventions are observed across the Edge Driver codebase: + +### General + +- **Indentation**: 2 spaces, no tabs +- **Strings**: Use double quotes `"string"` for module requires and general strings +- **Local variables**: Always use `local` for variables and functions at module scope +- **Line length**: No enforced limit, but most code stays under 120 characters + +### Naming + +- **Variables and functions**: `snake_case` (e.g., `local mock_device`, `local function test_init()`) +- **Constants**: `UPPER_SNAKE_CASE` for true constants (e.g., `SENSOR_BINARY`) +- **Modules**: Return a table at the end of the file (`return module_name`) + +### Requires and Imports + +```lua +-- Standard library requires first +local capabilities = require "st.capabilities" +local zw = require "st.zwave" + +-- Then test/integration requires +local test = require "integration_test" +local t_utils = require "integration_test.utils" + +-- Then protocol-specific requires +local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 }) +``` + +### Function Style + +- Prefer `local function name()` over `local name = function()` +- Handler functions typically receive `(driver, device, ...)` arguments +- Use early returns for guard clauses + +### Tables + +- Trailing commas are common and acceptable in multi-line tables +- Align table entries for readability in test manifests + +### Comments + +- Use `--` for single-line comments +- Minimal inline comments; code should be self-documenting + +Copyright header at the top of every file: +```lua +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +``` + +### File Organization for Drivers + +``` +driver-name/ + src/ + init.lua -- Main driver entry point + .lua -- Additional driver modules + test/ + test_*.lua -- Test files (must start with test_) + profiles/ + *.yml -- Device profiles + fingerprints.yml -- Device fingerprints + config.yml -- Driver configuration +``` diff --git a/.agents/skills/testing-edge-drivers/SKILL.md b/.agents/skills/testing-edge-drivers/SKILL.md new file mode 100644 index 0000000000..a34a22c901 --- /dev/null +++ b/.agents/skills/testing-edge-drivers/SKILL.md @@ -0,0 +1,349 @@ +--- +name: testing-edge-drivers +description: Running and writing integration tests for SmartThings Edge Drivers using the Python test harness and Lua integration test framework +--- + +# Testing SmartThings Edge Drivers + +## Running Tests + +Tests are run via the Python test harness: + +```bash +python3 tools/run_driver_tests.py [options] +``` + +### Options + +| Flag | Description | +|------|-------------| +| `-v` | Print individual test names and pass/fail status | +| `-vv` | Print test names, status, and full logs on failures (recommended) | +| `-vvv` | Print all logs from all tests | +| `-f ` | Only run tests whose file path matches the regex filter | +| `-j ` | Output JUnit XML results to the specified file | +| `-c [files]` | Run with luacov code coverage | +| `--html` | Generate HTML coverage reports (use with `-c`) | + +### Filter Examples + +```bash +# Run all tests for a specific driver +python3 tools/run_driver_tests.py -vv -f "zwave-smoke-alarm" + +# Run a specific test file +python3 tools/run_driver_tests.py -vv -f "test_zwave_smoke_detector" + +# Run all zigbee switch tests +python3 tools/run_driver_tests.py -vv -f "zigbee-switch" + +# Run all virtual device tests +python3 tools/run_driver_tests.py -vv -f "virtual" +``` + +The filter is a regex applied to the full file path. The harness searches for files matching `drivers/*/*/src/test/test_*.lua`. + +### Python Requirements + +Install dependencies before running tests: + +```bash +pip install -r tools/requirements.txt +``` + +Required packages: `junit_xml`, `requests`, `PyYAML`, `regex`. + +### How Tests Execute + +The Python harness (`tools/run_driver_tests.py`): +1. Globs for all `test_*.lua` files under `drivers/*/src/test/` +2. Filters by the `-f` regex if provided +3. Changes directory to the driver's `src/` directory (two levels up from the test file) +4. Runs each test file with `lua ` +5. Parses stdout for `Running test`, `PASSED`, `FAILED`, and summary lines +6. Reports totals and exits with code 1 if any tests failed + +## Integration Test Framework + +The framework lives in `lua_libs/integration_test/` and is required as `integration_test` in test files. It provides: + +### Core Modules + +| Module | Purpose | +|--------|---------| +| `integration_test` (init.lua) | Main test runner, registration, mock device builder | +| `integration_test.utils` | Utility functions like `get_profile_definition()` | +| `integration_test.mock_device` | Build mock Zigbee, Z-Wave, Matter, or generic devices | +| `integration_test.zwave_test_utils` | Z-Wave specific helpers (e.g., `zwave_test_build_receive_command`) | +| `integration_test.zigbee_test_utils` | Zigbee specific helpers | +| `integration_test.mock_socket` | Mock socket layer with channel-based message routing | + +### Channels + +The test framework uses channels to simulate communication between the driver and the platform: + +- `zwave` - Z-Wave protocol messages +- `zigbee` - Zigbee protocol messages +- `matter` - Matter protocol messages +- `capability` - SmartThings capability events (commands from cloud, events to cloud) +- `device_lifecycle` - Device lifecycle events (init, added, removed, etc.) +- `driver_lifecycle` - Driver lifecycle events +- `timer` - Timer-related events + +Each channel supports two directions: +- `receive` - Messages sent TO the driver (incoming commands, device reports) +- `send` - Messages sent FROM the driver (capability events, protocol commands) + +## Writing Tests + +### Test File Structure + +Every test file follows this pattern: + +```lua +local test = require "integration_test" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" + +-- 1. Build mock device(s) +local mock_device = test.mock_device.build_test_generic_device({ + profile = t_utils.get_profile_definition("my-profile.yml"), +}) + +-- 2. Define test init function (runs before each test) +local function test_init() + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +-- 3. Register tests (message tests or coroutine tests) + +-- 4. Run all registered tests +test.run_registered_tests() +``` + +### Building Mock Devices + +```lua +-- Generic device (no protocol) +local mock = test.mock_device.build_test_generic_device({ + profile = t_utils.get_profile_definition("profile-name.yml"), + preferences = { ["certifiedpreferences.somePref"] = true }, +}) + +-- Z-Wave device +local zw = require "st.zwave" +local mock = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("profile-name.yml"), + zwave_endpoints = { + { + command_classes = { + { value = zw.SENSOR_BINARY }, + { value = zw.NOTIFICATION }, + } + } + } +}) + +-- Zigbee device +local mock = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("profile-name.yml"), + zigbee_endpoints = { ... } +}) +``` + +### Message Tests (`register_message_test`) + +Message tests define an ordered sequence of receive/send message pairs. Each receive triggers the driver handler, and the subsequent sends are the expected outputs. + +```lua +test.register_message_test( + "Test description", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "switch", component = "main", command = "on", args = {} } + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) + } + }, + { + min_api_version = 17 -- optional version constraint + } +) +``` + +The manifest is an array of message entries. The framework groups them into blocks: each block starts with a `receive` followed by zero or more `send` entries. The receives are queued on the mock channel; the sends are set as expectations. The driver processes the receive and the framework asserts the expected sends occurred. + +### Coroutine Tests (`register_coroutine_test`) + +For more complex test logic (multiple interactions, state changes, conditional assertions, timer manipulation): + +```lua +test.register_coroutine_test( + "Test with complex logic", + function() + -- Queue a lifecycle event + test.socket.device_lifecycle():__queue_receive({ mock_device.id, "init" }) + test.socket.device_lifecycle():__queue_receive( + mock_device:generate_info_changed({ + preferences = { ["certifiedpreferences.somePref"] = false } + }) + ) + test.wait_for_events() + + -- Now send a capability command and expect a response + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "switch", component = "main", command = "on", args = {} } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.switch.switch.on()) + ) + end, + { + min_api_version = 17 + } +) +``` + +Key coroutine test APIs: +- `test.socket.:__queue_receive(msg)` - Queue a message for the driver to receive +- `test.socket.:__expect_send(msg)` - Set an expectation for a message the driver should send +- `test.wait_for_events()` - Yield to let the driver process queued messages and check expectations +- `test.mock_time.advance_time(seconds)` - Advance the mock clock + +### Real Example: Z-Wave Smoke Detector Test + +From `drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_smoke_detector.lua`: + +```lua +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 t_utils = require "integration_test.utils" + +local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 }) + +local sensor_endpoints = { + { + command_classes = { + { value = zw.SENSOR_BINARY }, + { value = zw.SENSOR_ALARM }, + { value = zw.NOTIFICATION }, + } + } +} + +local mock_device = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("smoke-battery-temperature-tamperalert-temperaturealarm.yml"), + zwave_endpoints = sensor_endpoints +}) + +local function test_init() + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +test.register_message_test( + "Sensor Binary report (smoke) should be handled", + { + { + channel = "zwave", + direction = "receive", + message = { + mock_device.id, + zw_test_utils.zwave_test_build_receive_command( + SensorBinary:Report({ + sensor_type = SensorBinary.sensor_type.SMOKE, + sensor_value = SensorBinary.sensor_value.DETECTED_AN_EVENT + }) + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.smokeDetector.smoke.detected()) + } + }, + { min_api_version = 17 } +) + +test.run_registered_tests() +``` + +## Common Test Patterns + +### Testing Capability Commands (cloud -> device) + +Receive on `capability` channel, expect protocol message on `zwave`/`zigbee`/`matter`: + +```lua +{ + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "switch", component = "main", command = "on", args = {} } } +}, +{ + channel = "zwave", + direction = "send", + message = ... -- expected Z-Wave command +} +``` + +### Testing Device Reports (device -> cloud) + +Receive on protocol channel, expect capability event on `capability`: + +```lua +{ + channel = "zwave", + direction = "receive", + message = { mock_device.id, zw_test_utils.zwave_test_build_receive_command(...) } +}, +{ + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) +} +``` + +### Testing Lifecycle Events + +```lua +test.socket.device_lifecycle():__queue_receive({ mock_device.id, "added" }) +test.socket.device_lifecycle():__queue_receive({ mock_device.id, "init" }) +test.socket.device_lifecycle():__queue_receive({ mock_device.id, "doConfigure" }) +``` + +### Testing Preference Changes + +```lua +test.socket.device_lifecycle():__queue_receive( + mock_device:generate_info_changed({ + preferences = { ["certifiedpreferences.myPref"] = new_value } + }) +) +``` + +### Optional Test Parameters + +The `opts` table passed to `register_message_test` or `register_coroutine_test` supports: + +| Field | Description | +|-------|-------------| +| `min_api_version` | Skip test if API version is below this (commonly set to 17) | +| `max_api_version` | Skip test if API version is above this | +| `test_init` | Per-test init function (overrides the global `set_test_init_function`) | +| `expected_error` | String or array of Lua patterns for expected errors | +| `inner_block_ordering` | Set to `"relaxed"` to allow sends in any order within a block | diff --git a/.agents/skills/understanding-lua-libraries/SKILL.md b/.agents/skills/understanding-lua-libraries/SKILL.md new file mode 100644 index 0000000000..235f637b69 --- /dev/null +++ b/.agents/skills/understanding-lua-libraries/SKILL.md @@ -0,0 +1,295 @@ +--- +name: understanding-lua-libraries +description: Understanding the SmartThings Edge Driver Lua libraries - driver lifecycle, message dispatchers, default handlers, and protocol message objects +--- + +# SmartThings Edge Driver Lua Library Architecture + +## 1. Driver Initialization and Run Loop + +A driver is created by calling `Driver("name", template)` (or a protocol-specific variant like `ZigbeeDriver("name", template)`). The template is a Lua table containing handler tables and configuration. + +The base `Driver.init` (in `lua_libs/st/driver.lua`) does the following: +- Sets `out_driver.NAME` from the name argument +- Initializes handler tables: `capability_handlers`, `lifecycle_handlers`, `message_handlers` +- Opens communication channels via cosock sockets: `capability_channel`, `environment_channel`, `lifecycle_channel`, `driver_lifecycle_channel`, and optionally `discovery_channel` +- Initializes a datastore and device cache tables +- Calls `Driver.standardize_sub_drivers()` to normalize the `sub_drivers` list +- Builds the `lifecycle_dispatcher` and `capability_dispatcher` from handlers + sub_drivers +- Registers channel handlers so inbound messages get routed to the correct handler function + +The `driver:run()` call starts the cosock event loop, which runs forever processing messages from all registered channels. + +## 2. Message Dispatchers + +The dispatcher system (`lua_libs/st/dispatcher.lua`) is a hierarchical message routing tree. The base class `MessageDispatcher` provides: + +- **`default_handlers`** - handlers at this level of the hierarchy. +- **`child_dispatchers`** - sub-dispatchers (from sub_drivers) that may override defaults +- **`can_handle(driver, device, ...)`** - returns true if this dispatcher or a child can handle the message +- **`dispatch(driver, device, ...)`** - finds and executes the matching handler + +### Dispatch logic + +1. The dispatcher calls `can_handle` on each child dispatcher +2. If any children can handle: **only the children handle it** (parent defaults are NOT called) +3. If multiple children match: ALL matching children receive the message +4. If NO children match: parent defaults are used +5. This is recursive -- sub-drivers can have sub-drivers + +### Dispatcher types + +| Dispatcher | Class | Handles | +|------------|-------|---------| +| `capability_dispatcher` | `CapabilityCommandDispatcher` | Capability commands from the platform (on, off, setLevel, etc.) | +| `lifecycle_dispatcher` | `DeviceLifecycleDispatcher` | Device lifecycle events (added, init, removed, etc.) | +| `zigbee_message_dispatcher` | `ZigbeeMessageDispatcher` | Incoming Zigbee messages (attribute reports, cluster commands, ZDO) | +| `zwave_dispatcher` | `ZwaveDispatcher` | Incoming Z-Wave commands | +| `matter_dispatcher` | `MatterMessageDispatcher` | Incoming Matter interaction responses | +| `secret_data_dispatcher` | `SecretDataDispatcher` | Security/secret data events | + +Each protocol-specific driver (ZigbeeDriver, ZwaveDriver, MatterDriver) adds its own dispatcher on top of the base Driver's capability and lifecycle dispatchers. + +**Zigbee handler structure:** +```lua +zigbee_handlers = { + attr = { -- attribute reports / read responses + [ClusterID] = { + [AttributeID] = handler_function, + } + }, + global = { -- global ZCL commands + [ClusterID] = { + [CommandID] = handler_function, + } + }, + cluster = { -- cluster-specific commands + [ClusterID] = { + [CommandID] = handler_function, + } + }, + zdo = { -- ZDO commands + [ClusterID] = handler_function, + } +} +``` + +**Z-Wave handler structure:** +```lua +zwave_handlers = { + [cc.SWITCH_BINARY] = { -- command class + [SwitchBinary.REPORT] = handler_function, -- command ID + }, +} +``` + +**Matter handler structure:** +```lua +matter_handlers = { + attr = { + [ClusterID] = { + [AttributeID] = handler_function, + } + }, + cmd_response = { ... }, + event = { ... }, + fallback = handler_function, +} +``` + +**Capability handler structure:** +```lua +capability_handlers = { + [capabilities.switch.ID] = { + [capabilities.switch.commands.on.NAME] = handle_on, + [capabilities.switch.commands.off.NAME] = handle_off, + }, + [capabilities.switchLevel.ID] = { + [capabilities.switchLevel.commands.setLevel.NAME] = handle_set_level, + }, +} +``` + +## 3. Sub-Drivers Pattern + +Sub-drivers allow device-specific behavior overrides gated by a `can_handle` function. A sub-driver is a table with: +- `NAME` (string) +- `can_handle(opts, driver, device, ...) -> boolean` +- Protocol handlers (zigbee_handlers, zwave_handlers, matter_handlers) +- `capability_handlers`, `lifecycle_handlers` +- Optional nested `sub_drivers` + +In practice, sub-drivers are often organized as separate files under `src/sub_drivers/` for clarity, and required in the main driver template. + + +### Dispatch Logic + +1. The dispatcher calls `can_handle` on each child dispatcher +2. If any children can handle: **only the children handle it** (parent defaults are NOT called) +3. If multiple children match: ALL matching children receive the message +4. If NO children match: parent defaults are used +5. This is recursive -- sub-drivers can have sub-drivers + +### Lazy Loading + +Sub-drivers support lazy loading for memory optimization: +- `Driver.lazy_load_sub_driver(sub_driver)`: Strips handlers, keeps only `can_handle` and `NAME` +- `Driver.lazy_load_sub_driver_v2(require_path)`: Even more efficient; only requires `can_handle` and `sub_drivers` modules separately +- A sub-driver with no handlers defined is automatically treated as lazy-loadable + +New sub-drivers must be: +1. Listed in the parent's `sub_drivers.lua` (or the equivalent sub_drivers table) +2. Have a `can_handle.lua` that correctly identifies the target devices +3. Have an `init.lua` that returns the sub-driver table + +If any of these are missing, the sub-driver will not be loaded. + + +## 4. Lifecycle Events + +Device lifecycle events are dispatched through the `DeviceLifecycleDispatcher`. The key events: + +1. **`init`** -- Called for every device on driver startup (existing devices) and after `added` for new devices. Used for setting up component/endpoint mappings and device fields. +2. **`added`** -- Called only when a device is first paired. NOT called for existing devices when a driver is updated. After `added`, a synthetic `init` is automatically dispatched. +3. **`doConfigure`** -- Called when the device needs configuration (typically after pairing). +4. **`infoChanged`** -- Called when device metadata changes (e.g., preferences updated). Receives `args.old_st_store` for comparison. +5. **`removed`** -- Called when device is removed. +6. **`driverSwitched`** -- Called when device switches to this driver. + +Register lifecycle handlers in the template: +```lua +lifecycle_handlers = { + init = device_init, + added = device_added, + removed = device_removed, + doConfigure = device_do_configure, + infoChanged = info_changed_handler, +} +``` + +Handler signature: `function(driver, device, event, args)` + +**Default behaviors provided by the framework:** +- `driverSwitched`: Base Driver marks device as `NONFUNCTIONAL`. ZigbeeDriver overrides this to check capability matching and marks as `PROVISIONED` if all capabilities match. +- `doConfigure`: ZigbeeDriver defaults to `device_management.configure` which sends attribute reporting configuration. +- `added`: After a successful `added` callback, the framework automatically queues a synthetic `init` event. +- `doConfigure`: After success, the framework transitions the device to `PROVISIONED` state. +- Unhandled lifecycle events log a trace message and are otherwise ignored (fallback handler). + +**Critical timing knowledge for lifecycle events** + +1. **`init` on driver startup**. After hub restart the radio may not be ready and sending Zigbee/Z-Wave commands in `init` can fail. +2. **`added` is NOT called for existing devices** on driver update. Only called on first pair. Code that must run for existing devices should go in `init` (for non-radio operations) or use `driverSwitched`. +3. **`doConfigure` is called any time a device is added with the TYPED provisioning state** and is the right place for device-specific configuration commands. +4. **`infoChanged` receives `args.old_st_store`** for comparing old vs new preferences. Drivers should check if a preference actually changed before acting on it. + +## 5. Key Imports and Require Paths + +```lua +-- Base driver (for virtual/LAN devices) +local Driver = require "st.driver" + +-- Protocol-specific drivers +local ZigbeeDriver = require "st.zigbee" +local ZwaveDriver = require "st.zwave.driver" +local MatterDriver = require "st.matter.driver" + +-- Capabilities +local capabilities = require "st.capabilities" + +-- Zigbee defaults (pre-built handlers for common capabilities) +local defaults = require "st.zigbee.defaults" + +-- Zigbee clusters (for building commands/reading attributes) +local zcl_clusters = require "st.zigbee.zcl" + +-- Z-Wave command classes +local cc = require "st.zwave.CommandClass" +local SwitchBinary = require "st.zwave.CommandClass.SwitchBinary" + +-- Matter clusters +local clusters = require "st.matter.clusters" + +-- Utilities +local utils = require "st.utils" +local json = require "st.json" +local log = require "log" + +-- Coroutine runtime +local cosock = require "cosock" + +-- LAN utils +local socket = cosock.socket +local luncheon = require "luncheon" +local luxure = require "luxure" +local lustre = require "lustre" +``` + +### Zigbee driver example (from zigbee-switch) + +```lua +local capabilities = require "st.capabilities" +local ZigbeeDriver = require "st.zigbee" +local defaults = require "st.zigbee.defaults" + +local template = { + supported_capabilities = { + capabilities.switch, + capabilities.switchLevel, + capabilities.colorControl, + capabilities.colorTemperature, + }, + sub_drivers = require("sub_drivers"), + lifecycle_handlers = { + init = device_init, + added = device_added, + }, +} + +-- Register default Zigbee handlers for all supported capabilities +defaults.register_for_default_handlers(template, + template.supported_capabilities, + {native_capability_cmds_enabled = true, native_capability_attrs_enabled = true} +) + +local driver = ZigbeeDriver("zigbee_switch", template) +driver:run() +``` + +This pattern - declare supported capabilities, register defaults, add overrides via sub_drivers and lifecycle_handlers, then construct and run - is the standard structure +for all protocol-based Edge drivers. + +## 6. Default Handlers and Protocol-Specific Default Functionality + +When a driver declares `supported_capabilities` in its template, the framework automatically registers default handlers for each capability. The registration uses `or`-merge +logic: **driver-defined handlers always take precedence over defaults.** If the driver already registered a handler for a given cluster/attribute/command slot, the default +is silently skipped. + +Registration happens in `st.{zigbee,zwave,matter}.defaults.init.lua` via `register_for_default_handlers(driver, capabilities, opts)`: +1. Iterates `supported_capabilities` +2. For each capability, requires the corresponding defaults module +3. Merges `zigbee_handlers`, `zwave_handlers`, or `matter_handlers` (only where driver hasn't defined one) +4. Also merges `attribute_configurations` (Zigbee), `get_refresh_commands` (Z-Wave), or `subscribed_attributes` (Matter) + +### Zigbee specific default functionality + +The default `doConfigure` handler (`device_management.configure`): +1. Sends a `refresh` command (reads all configured attributes) +2. Calls `device:configure()` which iterates all configured attributes and for each: + - Sends a ZDO Bind Request + - Sends a Configure Reporting command with the attribute's min/max interval, data type, and reportable change +3. Also handles IAS Zone enrollment if the device supports cluster `0x0500` + +### Z-Wave specific default functionality + +doConfigure calls `device:default_configure()` which calls `device:refresh()`. The default refresh iterates `get_refresh_commands` from all default capability modules and sends Get commands for each supported CC. +Refresh collects `get_refresh_commands` from all default modules, sends Get commands + +### Matter specific default functionality + +TODO + +## 7. Unit Test Framework + +Load the `testing-edge-drivers` skill for details on the built in unit test framework for to test Zigbee, Z-Wave, and Matter drivers. + diff --git a/.agents/skills/understanding-profiles/SKILL.md b/.agents/skills/understanding-profiles/SKILL.md new file mode 100644 index 0000000000..0f169057e5 --- /dev/null +++ b/.agents/skills/understanding-profiles/SKILL.md @@ -0,0 +1,369 @@ +--- +name: understanding-profiles +description: Understanding and defining SmartThings capabilities, device profiles, preferences, and embedded device configurations for Edge Drivers +--- + +# SmartThings Capabilities, Profiles, and Preferences + +## 1. What Are Capabilities? + +Capabilities are the fundamental abstraction in SmartThings. They define what a device can do and what state it can report. Each capability consists of: + +- **Attributes**: State/status values (e.g., `switch` has attribute `switch` with values `on`/`off`) +- **Commands**: Actions that control the device (e.g., `on()`, `off()`, `setLevel(level)`) + +A capability definition specifies data types, units, and constraints for its attributes and commands. + +### Data Types +| Type | Example | Description | +|------|---------|-------------| +| string | `"locked"` | May have enum or pattern constraints | +| integer | `5` | Whole number, may have min/max | +| number | `5.5` | Fractional values allowed | +| boolean | `true` | true or false | +| object | `{x: 12}` | Map of name-value pairs | +| array | `["heat","cool"]` | List of single type | + +### Common Capabilities +- `switch` - on/off control +- `switchLevel` - dimming (0-100) +- `temperatureMeasurement` - temperature reading +- `battery` - battery percentage +- `contactSensor` - open/closed +- `motionSensor` - active/inactive +- `lock` - locked/unlocked +- `thermostatMode`, `thermostatHeatingSetpoint`, `thermostatCoolingSetpoint` +- `colorTemperature`, `colorControl` +- `refresh` - request device state update +- `firmwareUpdate` - OTA firmware management +- `healthCheck` - device connectivity monitoring + +Full reference: https://developer.smartthings.com/docs/devices/capabilities/capabilities-reference + +## 2. Standard vs Custom Capabilities + +### Standard Capabilities +Standard capabilities live under the `smartthings` namespace but are referenced without a namespace prefix: +```yaml +- id: switch + version: 1 +- id: temperatureMeasurement + version: 1 +``` + +### Custom Capabilities +Custom capabilities use the format `namespace.capabilityName`: +```yaml +- id: perfectlife6617.customGarageDoor + version: 1 +``` + +A namespace is auto-generated per developer account (e.g., `perfectlife6617`). Custom capabilities are created via the SmartThings CLI: +``` +smartthings capabilities:create -i capability.json +``` + +Custom capabilities require a Capability Presentation to render properly in the app. + +## 3. Device Profile YAML Format + +Device profiles define which capabilities a device exposes, organized into components. They live in `profiles/` directories within driver packages. + +### Basic Profile Example (from `zwave-lock`) +```yaml +name: base-lock +components: +- id: main + capabilities: + - id: lock + version: 1 + - id: lockCodes + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartLock +``` + +### Multi-Component Profile (from `zigbee-fan`) +```yaml +name: fan-light +components: + - id: main + label: Fan + capabilities: + - id: switch + version: 1 + - id: fanSpeed + version: 1 + config: + values: + - key: "fanSpeed.value" + range: [0, 3] + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Fan + - id: light + label: Light + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + config: + values: + - key: "level.value" + range: [0, 100] + - id: refresh + version: 1 + categories: + - name: Light +``` + +### Profile with Embedded Config and Preferences (from `zigbee-contact`) +```yaml +name: multi-sensor +components: +- id: main + capabilities: + - id: contactSensor + version: 1 + - id: temperatureMeasurement + version: 1 + - id: threeAxis + version: 1 + - id: accelerationSensor + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: MultiFunctionalSensor +preferences: + - preferenceId: tempOffset + explicit: true + - preferenceId: certifiedpreferences.garageSensor + explicit: true +``` + +### Key Profile Rules +- Must have at least one component; the primary is always `id: main` +- Use multiple components when the same capability is needed more than once (e.g., multi-gang switch) +- Each component needs at least one capability +- `categories` determines the device icon in the app (e.g., `SmartLock`, `Fan`, `Light`, `Thermostat`, `MultiFunctionalSensor`) +- `version: 1` is always used (only version supported) + +## 4. Embedded Device Configurations + +Embedded device configs let you customize the SmartThings app UI directly in the profile YAML, without creating a separate Device Presentation. Only supported by Edge Drivers. + +### Range Constraint +```yaml +- id: colorTemperature + config: + values: + - key: "colorTemperature.value" + range: [2600, 6200] +``` + +### Enabled Values (filter enum options) +```yaml +- id: thermostatOperatingState + version: 1 + config: + values: + - key: "thermostatOperatingState.value" + enabledValues: + - heating + - cooling + - fan only + - idle +``` + +### Separate Attribute vs Command Values +```yaml +- id: thermostatMode + config: + values: + - key: thermostatMode.value + enabledValues: + - off + - heat + - eco + - key: setThermostatMode + enabledValues: + - off + - heat +``` + +### Enum Commands +```yaml +- id: alarm + config: + values: + - key: alarm.value + enabledValues: + - off + - siren + - key: "{{enumCommands}}" + enabledValues: + - off + - siren +``` + +When you package the driver, the platform auto-generates a Device Presentation from these configs. + +## 5. Preferences + +Preferences let users configure device behavior from Settings in the SmartThings app. + +### Two Types + +**Explicit (shared/reusable):** Defined externally, referenced by ID in the profile: +```yaml +preferences: + - preferenceId: tempOffset + explicit: true +``` + +Standard explicit preferences include: `tempOffset`, `humidityOffset`, `motionSensitivity`, `reportingInterval`, `reverse`, `presetPosition`, `username`, `password`. + +`tempOffset` and `humidityOffset` are automatically applied by the platform to attribute values - no driver code needed. + +**Embedded (inline in profile):** Defined directly in the profile YAML: +```yaml +preferences: + - title: "IP Address" + name: ipAddress + description: "IP address of the Pi-Hole" + required: true + preferenceType: string + definition: + minLength: 7 + maxLength: 15 + stringType: text + default: localhost +``` + +### Preference Types +| Type | Definition Fields | +|------|------------------| +| boolean | `default` | +| integer | `minimum`, `maximum`, `default` | +| number | `minimum`, `maximum`, `default` | +| string | `stringType` (text/paragraph/password), `minLength`, `maxLength`, `default` | +| enumeration | `options` (key-value map), `default` (must match a key) | + +### Accessing Preferences in Lua + +Query current value: +```lua +local offset = device.preferences.tempOffset +local level = command.args.level + device.preferences.levelOffset +``` + +Handle preference changes via `infoChanged` lifecycle: +```lua +local function device_info_changed(driver, device, event, args) + if args.old_st_store.preferences.sensitivityLevel ~= device.preferences.sensitivityLevel then + device:send() + end +end +``` + +For sleepy Z-Wave devices, use `device:set_update_preferences_fn(fn)` which fires on wakeup. + +## 6. config.yml + +The `config.yml` file is the driver package manifest. It lives at the root of each driver directory. + +```yaml +name: 'Zigbee Thermostat' +defaultProfile: 'thermostat-battery-powerSource' +packageKey: 'zigbee-thermostat' +permissions: + zigbee: {} +description: "SmartThings driver for Zigbee thermostat devices" +vendorSupportInformation: "https://support.smartthings.com" +``` + +### Fields +| Field | Description | +|-------|-------------| +| `name` | Human-readable driver name | +| `packageKey` | Unique package identifier | +| `permissions` | Protocol access: `zigbee: {}`, `zwave: {}`, `lan: {}`, `matter: {}` | +| `description` | Driver description | +| `defaultProfile` | Profile name used when no fingerprint match specifies one | +| `vendorSupportInformation` | Support URL | + +## 7. Fingerprints + +Fingerprints map physical devices to profiles. They live in `fingerprints.yml` at the driver root. + +### Zigbee Fingerprints +```yaml +zigbeeManufacturer: + - id: "LUMI/lumi.motion.ac02" + deviceLabel: Aqara Motion Sensor P1 + manufacturer: LUMI + model: lumi.motion.ac02 + deviceProfileName: motion-illuminance-battery-aqara + - id: "SmartThings/motionv5" + deviceLabel: Motion Sensor + manufacturer: SmartThings + model: motionv5 + deviceProfileName: motion-temp-battery + +zigbeeGeneric: + - id: kickstarter/motion/1 + deviceLabel: SmartThings Motion Sensor + zigbeeProfiles: + - 0xFC01 + deviceIdentifiers: + - 0x013A + deviceProfileName: smartsense-motion +``` + +### Key Fingerprint Fields +| Field | Description | +|-------|-------------| +| `id` | Unique identifier for the fingerprint | +| `deviceLabel` | Default label shown to users | +| `manufacturer` | Device manufacturer string | +| `model` | Device model string | +| `deviceProfileName` | Which profile from `profiles/` to use | +| `zigbeeProfiles` | (zigbeeGeneric) Zigbee profile IDs | +| `deviceIdentifiers` | (zigbeeGeneric) Zigbee device type IDs | + +Z-Wave fingerprints use `manufacturerId`, `productType`, and `productId` instead. + +## 8. Relationship: config.yml + Profiles + Fingerprints + +``` +driver/ +├── config.yml # Package manifest, declares defaultProfile +├── fingerprints.yml # Maps hardware → profile by deviceProfileName +├── profiles/ +│ ├── basic-device.yml # Profile A +│ └── advanced-device.yml # Profile B +└── src/ + └── init.lua # Driver logic +``` + +Flow: +1. A device joins the hub +2. The platform matches it against `fingerprints.yml` entries +3. The matched fingerprint's `deviceProfileName` selects which profile to use +4. If no fingerprint matches, `defaultProfile` from `config.yml` is used +5. The profile defines capabilities, components, categories, and preferences +6. Embedded `config` in the profile customizes the app UI +7. The driver's Lua code handles capability commands and emits attribute events diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..cf17ce46b3 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,52 @@ + You are working in the SmartThings Edge Drivers repository. Drivers are written in **Lua 5.3** and + run on SmartThings hubs. They translate Zigbee, Z-Wave, Matter, and LAN protocol messages into + SmartThings capability commands and events. + + For full context, read `AGENTS.md` at the repository root. It covers driver structure, lifecycle, + profiles, and available skills for deeper domain knowledge. + + ## Standard Commands + + ```bash + # Run tests + python3 tools/run_driver_tests.py -vv -f + + # Lint + luacheck --config .github/workflows/.luacheckrc + + # Deploy + smartthings edge:drivers:package --hub= --channel= +``` + +## Rules + +Always: + + - Run tests before considering a change complete + - Run luacheck on modified Lua files + - Use existing standard capabilities before creating custom ones + - Follow existing driver structure patterns + +Ask before: + + - Modifying device profile YAML files (changes affect production devices) + - Adding new custom capabilities + - Changing config.yml permissions + +Never: + + - Commit hardcoded API keys or tokens + - Skip tests for driver changes + - Use Lua features beyond 5.3 + +## Skills + +Load these files for deeper knowledge when working in each area: + +| Task | Skill file | +|------|-----------| +| Driver lifecycle, dispatch, default handlers | .agents/skills/understanding-lua-libraries/SKILL.md | +| Profiles, capabilities, preferences, fingerprints | .agents/skills/understanding-profiles/SKILL.md | +| Writing and running tests | .agents/skills/testing-edge-drivers/SKILL.md | +| Luacheck / code style | .agents/skills/linting-and-style/SKILL.md | +| Environment setup, deploying, sharing via channels | .agents/skills/dev-workflow/SKILL.md | diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..ecb9478f81 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,130 @@ +# SmartThings Edge Drivers — Agent Instructions + +You are an expert Lua 5.3 engineer and SmartThings Edge Driver maintainer. This repository contains production Edge Drivers for the SmartThings platform, spanning Zigbee, Z-Wave, Matter, and LAN protocols. + +Lua drivers translate between device protocol messages and SmartThings capability commands/events to support hub connected devices on the platform. + +## Repository Structure + +``` +drivers/ # Edge Drivers organized by vendor (SmartThings/, Aqara/, etc.) + // + config.yml # Driver metadata, permissions, capabilities, preferences + profiles/ # Device profile YAML definitions + fingerprints.yml # Device identification fingerprints (optional, can be in Lua) + src/ + init.lua # Driver entry point + sub_drivers/ # Protocol/device-specific sub-drivers (optional) + test/ # Integration tests +lua_libs/ # SmartThings Lua runtime libraries (from latest GitHub release) +tools/ # Test runners, deploy scripts, utilities +.github/workflows/ # CI: tests, luacheck, packaging +``` + +## Standard Commands + +### Run Tests +```bash +python3 tools/run_driver_tests.py -vv -f +``` +The filter matches against driver directory/file names. Load the `testing-edge-drivers` skill for details. + +### Lint +```bash +luacheck --config .github/workflows/.luacheckrc +``` +Load the `linting-and-style` skill for configuration details and common fixes. + +### Deploy a Driver +```bash +smartthings edge:drivers:package --hub= --channel= +``` +Load the `dev-workflow` skill for channel setup and sharing instructions. + +## Driver Anatomy + +Drivers live under `drivers///`. The canonical layout is: + +``` +drivers/// + config.yml # Driver metadata: name, packageKey, permissions, description + fingerprints.yml # Device matching rules (Zigbee, Z-Wave, Matter only) + search-parameters.yml # SSDP/mDNS discovery hints (LAN drivers only) + profiles/ + .yml # One file per device profile + src/ + init.lua # Driver entry point; creates template and calls :run() + sub_drivers.lua # Optional: list of sub-driver require paths + / + init.lua # Sub-driver table: NAME, can_handle, handlers + can_handle.lua # Optional: separated device-matching function + fingerprints.lua # Optional: Lua-side fingerprint list for can_handle +``` + +Load the `understanding-lua-libraries` skill for detailed information on the driver framework. + +### Fingerprints (`fingerprints.yml`) + +Fingerprints tell the platform which driver to assign to a newly-joined device. +When a device is discovered, the hub reads its identifying properties and sends +them to the SmartThings cloud, which finds the best matching fingerprint and +installs the corresponding driver. + +Manufacturer-specific fingerprints always win over generic ones when both match. + +LAN drivers do **not** use `fingerprints.yml`. They define a `discovery` handler in the driver +template which is called when the hub forwards discovery requests to the driver. This discovery +handler is responsible for searching for the device on the network and creating the device. + +### Device Profiles (`profiles/*.yml`) + +A profile declares the SmartThings **capabilities** a device exposes, grouped into +**components**. The `main` component is the primary one. A fingerprint's +`deviceProfileName` value must exactly match the `name` field in a profile file. + +Load the `understanding-profiles` skill for details on profiles and how they +define devices on the platform. + +## Lua Libraries (`lua_libs/`) + +The `lua_libs/` directory at the repository root is setup by the developer and not committed +to the repository. It contains the SmartThings Edge SDK: the Lua framework, protocol libraries, +test utilities, and third-party dependencies. **This directory must be present for tests to run.** +Load the `dev-workflow` skill to help with initial setup. + +Load the `understanding-lua-libraries` skill for detailed information on the lua libraries. + +--- + +## Rules + +### ✅ ALWAYS +- Run tests before considering a change complete +- Run luacheck on modified Lua files +- Use existing capabilities from the SmartThings reference before creating custom ones +- Follow the existing driver structure patterns in this repo +- Use `require` paths relative to `src/` for driver code, and `lua_libs/` for library code + +### ⚠️ ASK FIRST +- Before modifying device profile YAML files (changes affect production devices) +- Before adding new custom capabilities +- Before changing `config.yml` permissions +- Before modifying shared library code in `lua_libs/` + +### 🚫 NEVER +- Commit hardcoded API keys, tokens, or hub UUIDs +- Modify files in `lua_libs/` (these come from upstream releases) +- Skip tests for driver changes +- Use Lua features beyond 5.3 (the hub runtime is Lua 5.3) + +## Available Skills + +Load these for deeper domain knowledge: + +| Skill | When to Use | Skill file | +|-------|-------------|------------| +| `understanding-profiles` | Defining or modifying capabilities, profiles, preferences, or device configurations | .agents/skills/understanding-profiles/SKILL.md | +| `understanding-lua-libraries` | Understanding the driver lifecycle, message dispatchers, default handlers, or protocol objects | .agents/skills/understanding-lua-libraries/SKILL.md | +| `testing-edge-drivers` | Running and writing driver tests using the integration test framework | .agents/skills/testing-edge-drivers/SKILL.md | +| `linting-and-style` | Running luacheck or fixing style issues | .agents/skills/linting-and-style/SKILL.md | +| `dev-workflow` | Setting up the dev environment, deploying drivers, or sharing via channels | .agents/skills/dev-workflow/SKILL.md | From 5e428adc3ecec3429b31667457fa18d7232a1c8e Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Mon, 18 May 2026 17:22:31 -0500 Subject: [PATCH 359/538] WWSTCERT-11638 Govee Floor Lamp 3 (#2972) * WWSTCERT-11638 Govee Floor Lamp 3 * Apply suggestion from @ctowns Co-authored-by: Cooper Towns --------- Co-authored-by: Cooper Towns --- .../SmartThings/matter-switch/fingerprints.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index b73ac0e98f..2eeb26b3d4 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -813,6 +813,21 @@ matterManufacturer: vendorId: 0x1387 productId: 0x1741 deviceProfileName: light-color-level + - id: "4999/5808" + deviceLabel: Govee Floor Lamp 3 + vendorId: 0x1387 + productId: 0x16B0 + deviceProfileName: light-color-level + - id: "4999/5824" + deviceLabel: Govee Floor Lamp 3 Lite + vendorId: 0x1387 + productId: 0x16C0 + deviceProfileName: light-color-level + - id: "4999/4720" + deviceLabel: Govee Ceiling Light Ultra (21inch) + vendorId: 0x1387 + productId: 0x1270 + deviceProfileName: light-color-level # Hager - id: "4741/8" deviceLabel: Hager matter 2 buttons (battery) From 4485f2b59dcbb5b11f95d3bef273888c50c5f1bd Mon Sep 17 00:00:00 2001 From: West Zhao <55424074+Oniums@users.noreply.github.com> Date: Tue, 19 May 2026 23:57:17 +0800 Subject: [PATCH 360/538] WWSTCERT-10701 Add Sonoff SNZB-01M Smart Scene Button into zigbee-button. (#2510) * Add Sonoff SNZB-01M Smart Scene Button into zigbee-button. * modify Copyright Date * modify test_sonoff.py fix PR error * Update copyright year from 2022 to 2026,remove remove whitespace-only lines Update copyright year from 2022 to 2026,remove remove whitespace-only lines * remove log.info remove log.info * Use defaults for Sonoff battery and added lifecycle - Remove custom battery attribute handler and added lifecycle in Sonoff subdriver - Align Sonoff tests with default added behavior and keep battery report coverage * remove log * Fix Sonoff multi-button sub-driver registration and test execution * update copyright sections for sonoff multi-button * fix: address review feedback for Sonoff SNZB-01M button support - Reuse existing four-buttons-battery profile instead of custom sonoff-buttons-battery - Mirror button events to main component via emit_event for device list visibility - Update test profile reference and add main component event assertions - Remove unused sonoff-buttons-battery.yml profile * fix: resolve Sonoff SNZB-01M unit test failures - replace unsupported build_custom_report_attribute usage with an explicit custom Zigbee attribute report builder - add missing main component expectations for the added lifecycle test - add min_api_version guards to Sonoff test cases - keep Sonoff button event assertions aligned with the four-buttons-battery profile behavior * Removed the SONOFF entry from the generic supported values table and moved its button metadata initialization into the SONOFF sub-driver. * Apply suggestion from @KKlimczukS Co-authored-by: Konrad K <33450498+KKlimczukS@users.noreply.github.com> * Apply suggestion from @KKlimczukS Co-authored-by: Konrad K <33450498+KKlimczukS@users.noreply.github.com> * Apply suggestion from @KKlimczukS Co-authored-by: Konrad K <33450498+KKlimczukS@users.noreply.github.com> * Apply suggestion from @KKlimczukS Co-authored-by: Konrad K <33450498+KKlimczukS@users.noreply.github.com> --------- Co-authored-by: Konrad K <33450498+KKlimczukS@users.noreply.github.com> --- .../zigbee-button/fingerprints.yml | 6 + .../src/test/test_sonoff_button.lua | 279 ++++++++++++++++++ .../src/zigbee-multi-button/fingerprints.lua | 1 + .../src/zigbee-multi-button/init.lua | 1 - .../zigbee-multi-button/sonoff/can_handle.lua | 16 + .../sonoff/fingerprints.lua | 8 + .../src/zigbee-multi-button/sonoff/init.lua | 63 ++++ .../src/zigbee-multi-button/sub_drivers.lua | 1 + 8 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-button/src/test/test_sonoff_button.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sonoff/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sonoff/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sonoff/init.lua diff --git a/drivers/SmartThings/zigbee-button/fingerprints.yml b/drivers/SmartThings/zigbee-button/fingerprints.yml index 09e915321b..347fa818df 100644 --- a/drivers/SmartThings/zigbee-button/fingerprints.yml +++ b/drivers/SmartThings/zigbee-button/fingerprints.yml @@ -267,6 +267,12 @@ zigbeeManufacturer: manufacturer: WALL HERO model: ACL-401SCA4 deviceProfileName: thirty-buttons + # SONOFF + - id: "SONOFF/SNZB-01M" + deviceLabel: SNZB-01M + manufacturer: SONOFF + model: SNZB-01M + deviceProfileName: four-buttons-battery - id: "MultIR/MIR-SO100" deviceLabel: MultiIR Smart button MIR-SO100 manufacturer: MultIR diff --git a/drivers/SmartThings/zigbee-button/src/test/test_sonoff_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_sonoff_button.lua new file mode 100644 index 0000000000..22f76bc383 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/test/test_sonoff_button.lua @@ -0,0 +1,279 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local zb_const = require "st.zigbee.constants" +local messages = require "st.zigbee.messages" +local data_types = require "st.zigbee.data_types" +local zcl_messages = require "st.zigbee.zcl" +local report_attr = require "st.zigbee.zcl.global_commands.report_attribute" + +local SONOFF_PRIVATE_BUTTON_CLUSTER = 0xFC12 +local SONOFF_PRIVATE_ATTR = 0x0000 + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("four-buttons-battery.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "SONOFF", + model = "SNZB-01M", + server_clusters = { 0x0001, 0xFC12 } + }, + [2] = { + id = 2, + manufacturer = "SONOFF", + model = "SNZB-01M", + server_clusters = { 0x0001, 0xFC12 } + }, + [3] = { + id = 3, + manufacturer = "SONOFF", + model = "SNZB-01M", + server_clusters = { 0x0001, 0xFC12 } + }, + [4] = { + id = 4, + manufacturer = "SONOFF", + model = "SNZB-01M", + server_clusters = { 0x0001, 0xFC12 } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function build_test_attr_report(device, endpoint, value) + local report_body = report_attr.ReportAttribute({ + report_attr.ReportAttributeAttributeRecord(SONOFF_PRIVATE_ATTR, data_types.Uint8.ID, value) + }) + local zclh = zcl_messages.ZclHeader({ + cmd = data_types.ZCLCommandId(report_body.ID) + }) + local addrh = messages.AddressHeader( + device:get_short_address(), + endpoint, + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + zb_const.HA_PROFILE_ID, + SONOFF_PRIVATE_BUTTON_CLUSTER + ) + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zclh, + zcl_body = report_body + }) + + return messages.ZigbeeMessageRx({ + address_header = addrh, + body = message_body + }) +end + +local function test_init() + test.mock_device.add_test_device(mock_device) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "added lifecycle event", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.button.supportedButtonValues({ "pushed", "double", "held", "pushed_3x" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.button.numberOfButtons({ value = 4 }, { visibility = { displayed = false } }) + ) + ) + + -- Check initial events for button 1 + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "button1", + capabilities.button.supportedButtonValues({ "pushed", "double", "held", "pushed_3x" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "button1", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }) + ) + ) + + -- Check initial events for button 2 + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "button2", + capabilities.button.supportedButtonValues({ "pushed", "double", "held", "pushed_3x" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "button2", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }) + ) + ) + + -- Check initial events for button 3 + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "button3", + capabilities.button.supportedButtonValues({ "pushed", "double", "held", "pushed_3x" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "button3", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }) + ) + ) + + -- Check initial events for button 4 + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "button4", + capabilities.button.supportedButtonValues({ "pushed", "double", "held", "pushed_3x" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "button4", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = false })) + ) + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Button pushed message should generate event", + function() + -- 0xFC12, 0x0000, 0x01 = pushed + local attr_report = build_test_attr_report(mock_device, 1, data_types.Uint8(0x01)) + + test.socket.zigbee:__queue_receive({ mock_device.id, attr_report }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button1", capabilities.button.button.pushed({ state_change = true })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true })) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Button double message should generate event", + function() + -- 0xFC12, 0x0000, 0x02 = double + local attr_report = build_test_attr_report(mock_device, 1, data_types.Uint8(0x02)) + + test.socket.zigbee:__queue_receive({ mock_device.id, attr_report }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button1", capabilities.button.button.double({ state_change = true })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.button.button.double({ state_change = true })) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Button held message should generate event", + function() + -- 0xFC12, 0x0000, 0x03 = held + local attr_report = build_test_attr_report(mock_device, 1, data_types.Uint8(0x03)) + + test.socket.zigbee:__queue_receive({ mock_device.id, attr_report }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button1", capabilities.button.button.held({ state_change = true })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.button.button.held({ state_change = true })) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Button pushed_3x message should generate event", + function() + -- 0xFC12, 0x0000, 0x04 = pushed_3x + local attr_report = build_test_attr_report(mock_device, 1, data_types.Uint8(0x04)) + + test.socket.zigbee:__queue_receive({ mock_device.id, attr_report }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button1", capabilities.button.button.pushed_3x({ state_change = true })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.button.button.pushed_3x({ state_change = true })) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Button 2 pushed message should generate event on button2 component", + function() + -- Endpoint 2 test + local attr_report = build_test_attr_report(mock_device, 2, data_types.Uint8(0x01)) + + test.socket.zigbee:__queue_receive({ mock_device.id, attr_report }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button2", capabilities.button.button.pushed({ state_change = true })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true })) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Battery percentage report should generate event", + function() + local battery_report = clusters.PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device, 180) + + test.socket.zigbee:__queue_receive({ mock_device.id, battery_report }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.battery.battery(90)) + ) + end, + { + min_api_version = 17 + } +) + +test.run_registered_tests() + diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/fingerprints.lua index cf3903152d..05ef834306 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/fingerprints.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/fingerprints.lua @@ -36,6 +36,7 @@ local ZIGBEE_MULTI_BUTTON_FINGERPRINTS = { { mfr = "Vimar", model = "RemoteControl_v1.0" }, { mfr = "Linxura", model = "Smart Controller" }, { mfr = "Linxura", model = "Aura Smart Button" }, + { mfr = "SONOFF", model = "SNZB-01M" }, { mfr = "zunzunbee", model = "SSWZ8T" } } diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/init.lua index 84dc2af26e..daaa24a8b6 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/init.lua @@ -8,7 +8,6 @@ local supported_values = require "zigbee-multi-button.supported_values" local button_utils = require "button_utils" - local function added_handler(self, device) local config = supported_values.get_device_parameters(device) for _, component in pairs(device.profile.components) do diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sonoff/can_handle.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sonoff/can_handle.lua new file mode 100644 index 0000000000..a1f689f4de --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sonoff/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function sonoff_can_handle(opts, driver, device, ...) + local fingerprints = require("zigbee-multi-button.sonoff.fingerprints") + + for _, fingerprint in ipairs(fingerprints) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-multi-button.sonoff") + end + end + + return false +end + +return sonoff_can_handle diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sonoff/fingerprints.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sonoff/fingerprints.lua new file mode 100644 index 0000000000..efba976feb --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sonoff/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local SONOFF_FINGERPRINTS = { + { mfr = "SONOFF", model = "SNZB-01M" } +} + +return SONOFF_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sonoff/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sonoff/init.lua new file mode 100644 index 0000000000..41390b4d44 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sonoff/init.lua @@ -0,0 +1,63 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local button_utils = require "button_utils" + +local SONOFF_CLUSTER_ID = 0xFC12 +local SONOFF_ATTR_ID = 0x0000 +local SONOFF_SUPPORTED_BUTTON_VALUES = { "pushed", "double", "held", "pushed_3x" } +local SONOFF_NUMBER_OF_BUTTONS = 4 + +local EVENT_MAP = { + [0x01] = capabilities.button.button.pushed, + [0x02] = capabilities.button.button.double, + [0x03] = capabilities.button.button.held, + [0x04] = capabilities.button.button.pushed_3x +} + +local function added_handler(self, device) + for _, component in pairs(device.profile.components) do + local number_of_buttons = component.id == "main" and SONOFF_NUMBER_OF_BUTTONS or 1 + device:emit_component_event(component, + capabilities.button.supportedButtonValues(SONOFF_SUPPORTED_BUTTON_VALUES, { visibility = { displayed = false } })) + device:emit_component_event(component, + capabilities.button.numberOfButtons({ value = number_of_buttons }, { visibility = { displayed = false } })) + end + + button_utils.emit_event_if_latest_state_missing(device, "main", capabilities.button, + capabilities.button.button.NAME, capabilities.button.button.pushed({ state_change = false })) +end + +local function sonoff_attr_handler(driver, device, value, zb_rx) + local attr_val = value.value + local endpoint = zb_rx.address_header.src_endpoint.value + local button_name = "button" .. tostring(endpoint) + local event_func = EVENT_MAP[attr_val] + if event_func then + local comp = device.profile.components[button_name] + if comp then + local event = event_func({ state_change = true }) + device:emit_component_event(comp, event) + device:emit_event(event) + end + end +end + +local sonoff_handler = { + NAME = "SONOFF Multi-Button Handler", + lifecycle_handlers = { + added = added_handler + }, + zigbee_handlers = { + attr = { + [SONOFF_CLUSTER_ID] = { + [SONOFF_ATTR_ID] = sonoff_attr_handler + } + } + }, + can_handle = require("zigbee-multi-button.sonoff.can_handle") +} + +return sonoff_handler + diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sub_drivers.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sub_drivers.lua index d8d3611ba3..bd7828c466 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/sub_drivers.lua @@ -6,6 +6,7 @@ local sub_drivers = { lazy_load_if_possible("zigbee-multi-button.ikea"), lazy_load_if_possible("zigbee-multi-button.somfy"), lazy_load_if_possible("zigbee-multi-button.ecosmart"), + lazy_load_if_possible("zigbee-multi-button.sonoff"), lazy_load_if_possible("zigbee-multi-button.centralite"), lazy_load_if_possible("zigbee-multi-button.adurosmart"), lazy_load_if_possible("zigbee-multi-button.heiman"), From 7bdbb97422fc143a2cde9ad447a9b1bc625e1252 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 19 May 2026 09:06:23 -0700 Subject: [PATCH 361/538] WWSTCERT-9844 Aqara Smart Lock U400 (#2805) --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 0c12932a2b..4ac4fc25b4 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -20,6 +20,11 @@ matterManufacturer: vendorId: 0x115F productId: 0x286A deviceProfileName: lock-user-pin + - id: "4447/10244" + deviceLabel: Aqara Smart Lock U400 + vendorId: 0x115F + productId: 0x2804 + deviceProfileName: lock #Eufy - id: "5427/1" deviceLabel: eufy Smart Lock E31 From e2d6a8e4bb412e81b700c28209b39d9aeb2ae988 Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Tue, 12 May 2026 13:45:12 -0500 Subject: [PATCH 362/538] Fix handling for cameras without per-zone sensitivity handling --- .../camera_handlers/attribute_handlers.lua | 3 +- .../camera_handlers/capability_handlers.lua | 13 +- .../src/test/test_matter_camera.lua | 185 ++++++++++++++++++ 3 files changed, 194 insertions(+), 7 deletions(-) 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 index b32b1dd55c..53b35712e7 100644 --- 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 @@ -417,7 +417,8 @@ function CameraAttributeHandlers.triggers_handler(driver, device, ib, response) 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 + sensitivity = camera_utils.feature_supported(device, clusters.ZoneManagement.ID, + clusters.ZoneManagement.types.Feature.PER_ZONE_SENSITIVITY) and trigger.sensitivity.value or nil }) end device:emit_event_for_endpoint(ib, capabilities.zoneManagement.triggers(triggers)) 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 index 443c83956b..a26afd0ca7 100644 --- 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 @@ -272,9 +272,11 @@ function CameraCapabilityHandlers.handle_remove_zone(driver, device, cmd) end function CameraCapabilityHandlers.handle_create_or_update_trigger(driver, device, cmd) + local per_zone_sensitivity_supported = camera_utils.feature_supported( + device, clusters.ZoneManagement.ID, clusters.ZoneManagement.types.Feature.PER_ZONE_SENSITIVITY + ) 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 + (per_zone_sensitivity_supported 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 {} @@ -284,8 +286,7 @@ function CameraCapabilityHandlers.handle_create_or_update_trigger(driver, device 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 + if per_zone_sensitivity_supported and not cmd.args.sensitivity then cmd.args.sensitivity = v.sensitivity end found_trigger = true @@ -306,7 +307,7 @@ function CameraCapabilityHandlers.handle_create_or_update_trigger(driver, device augmentation_duration = cmd.args.augmentationDuration, max_duration = cmd.args.maxDuration, blind_duration = cmd.args.blindDuration, - sensitivity = cmd.args.sensitivity + sensitivity = per_zone_sensitivity_supported and cmd.args.sensitivity or nil -- omit even if provided by client if per-zone sensitivity is not supported } ) )) @@ -320,7 +321,7 @@ 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)) + device:send(clusters.ZoneManagement.attributes.Sensitivity:write(device, endpoint_id, cmd.args.sensitivity)) else device.log.warn(string.format("Can't set global zone sensitivity setting, per zone sensitivity enabled.")) end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index fceaa4ccf5..057bd3ae7f 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -119,6 +119,45 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) +local mock_device_no_per_zone_sensitivity = 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, + cluster_type = "SERVER" + }, + { + cluster_id = clusters.ZoneManagement.ID, + feature_map = clusters.ZoneManagement.types.Feature.TWO_DIMENSIONAL_CARTESIAN_ZONE, + cluster_type = "SERVER" + }, + { + cluster_id = clusters.PushAvStreamTransport.ID, + cluster_type = "SERVER" + } + }, + device_types = { + {device_type_id = 0x0142, device_type_revision = 1} -- Camera + } + } + } +}) + local subscribe_request local subscribed_attributes = { clusters.CameraAvStreamManagement.attributes.AttributeList, @@ -169,6 +208,27 @@ end test.set_test_init_function(test_init) + +local subscribe_request_no_per_zone_sensitivity +local subscribed_attributes_no_per_zone_sensitivity = { + clusters.CameraAvStreamManagement.attributes.AttributeList, +} + +local function test_init_no_per_zone_sensitivity() + test.mock_device.add_test_device(mock_device_no_per_zone_sensitivity) + test.socket.device_lifecycle:__queue_receive({ mock_device_no_per_zone_sensitivity.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_no_per_zone_sensitivity.id, "init" }) + subscribe_request_no_per_zone_sensitivity = subscribed_attributes_no_per_zone_sensitivity[1]:subscribe(mock_device_no_per_zone_sensitivity) + subscribe_request_no_per_zone_sensitivity:merge(cluster_base.subscribe(mock_device_no_per_zone_sensitivity, nil, camera_fields.CameraAVSMFeatureMapAttr.cluster, camera_fields.CameraAVSMFeatureMapAttr.ID)) + subscribe_request_no_per_zone_sensitivity:merge(cluster_base.subscribe(mock_device_no_per_zone_sensitivity, nil, camera_fields.ZoneManagementFeatureMapAttr.cluster, camera_fields.ZoneManagementFeatureMapAttr.ID)) + for i, attr in ipairs(subscribed_attributes_no_per_zone_sensitivity) do + if i > 1 then subscribe_request_no_per_zone_sensitivity:merge(attr:subscribe(mock_device_no_per_zone_sensitivity)) end + end + test.socket.matter:__expect_send({mock_device_no_per_zone_sensitivity.id, subscribe_request_no_per_zone_sensitivity}) + test.socket.device_lifecycle:__queue_receive({ mock_device_no_per_zone_sensitivity.id, "doConfigure" }) + mock_device_no_per_zone_sensitivity:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + local additional_subscribed_attributes = { clusters.CameraAvStreamManagement.attributes.HDRModeEnabled, clusters.CameraAvStreamManagement.attributes.ImageRotation, @@ -349,6 +409,83 @@ local function update_device_profile() test.socket.capability:__expect_send(mock_device:generate_test_message("doorbell", capabilities.button.button.pushed({state_change = false}))) end +local additional_subscribed_attributes_no_per_zone_sensitivity = { + clusters.CameraAvStreamManagement.attributes.StatusLightBrightness, + clusters.CameraAvStreamManagement.attributes.StatusLightEnabled, + clusters.CameraAvStreamManagement.attributes.RateDistortionTradeOffPoints, + clusters.CameraAvStreamManagement.attributes.MaxEncodedPixelRate, + clusters.CameraAvStreamManagement.attributes.VideoSensorParams, + clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams, + clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams, + clusters.CameraAvStreamManagement.attributes.MinViewportResolution, + clusters.CameraAvStreamManagement.attributes.Viewport, + clusters.CameraAvStreamManagement.attributes.AttributeList, + 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, +} + +local function update_device_profile_no_per_zone_sensitivity() + local expected_metadata = { + optional_component_capabilities = { + { + "main", + { + "videoCapture2", + "cameraViewportSettings", + "videoStreamSettings", + "zoneManagement", + } + }, + { + "statusLed", + { + "switch", + "mode" + } + } + }, + profile = "camera" +} + + test.socket.matter:__queue_receive({ + mock_device_no_per_zone_sensitivity.id, + clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device_no_per_zone_sensitivity, CAMERA_EP, { + uint32(clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID), + uint32(clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) + }) + }) + mock_device_no_per_zone_sensitivity:expect_metadata_update(expected_metadata) + 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_no_per_zone_sensitivity:generate_info_changed({ profile = updated_device_profile })) + + test.socket.capability:__expect_send( + mock_device_no_per_zone_sensitivity:generate_test_message("main", capabilities.zoneManagement.supportedFeatures( + {"triggerAugmentation"} + )) + ) + + test.socket.capability:__expect_send( + mock_device_no_per_zone_sensitivity:generate_test_message("main", capabilities.videoStreamSettings.supportedFeatures( + {"liveStreaming", "clipRecording", "perStreamViewports"} + )) + ) + + for _, attr in ipairs(additional_subscribed_attributes_no_per_zone_sensitivity) do + subscribe_request_no_per_zone_sensitivity:merge(attr:subscribe(mock_device_no_per_zone_sensitivity)) + end + test.socket.matter:__expect_send({mock_device_no_per_zone_sensitivity.id, subscribe_request_no_per_zone_sensitivity}) +end + -- Matter Handler UTs test.register_coroutine_test( @@ -3008,5 +3145,53 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Zone Management trigger reports should omit sensitivity when per-zone sensitivity is unsupported, even if provided by client", + function() + update_device_profile_no_per_zone_sensitivity() + test.wait_for_events() + -- Create a trigger with + test.socket.capability:__queue_receive({ + mock_device_no_per_zone_sensitivity.id, + { capability = "zoneManagement", component = "main", command = "createOrUpdateTrigger", args = { + 1, 10, 3, 15, 3, 5 + }} + }) + test.socket.matter:__expect_send({ + mock_device_no_per_zone_sensitivity.id, clusters.ZoneManagement.server.commands.CreateOrUpdateTrigger(mock_device_no_per_zone_sensitivity, CAMERA_EP, { + zone_id = 1, + initial_duration = 10, + augmentation_duration = 3, + max_duration = 15, + blind_duration = 3 + }) + }) + end, + { + test_init = test_init_no_per_zone_sensitivity, + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Zone Management setSensitivity command should handle global sensitivity when per-zone sensitivity is unsupported", + function() + update_device_profile_no_per_zone_sensitivity() + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device_no_per_zone_sensitivity.id, + { capability = "zoneManagement", component = "main", command = "setSensitivity", args = { 5 } } + }) + test.socket.matter:__expect_send({ + mock_device_no_per_zone_sensitivity.id, + clusters.ZoneManagement.attributes.Sensitivity:write(mock_device_no_per_zone_sensitivity, CAMERA_EP, 5) + }) + end, + { + test_init = test_init_no_per_zone_sensitivity, + min_api_version = 17 + } +) + -- run the tests test.run_registered_tests() From 8c3cf23a64a4c0a075e9b0a25db19b9a4378761f Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 19 May 2026 12:51:20 -0500 Subject: [PATCH 363/538] WWSTCERT-11708 Govee Ceiling Light Pro (#2984) --- drivers/SmartThings/matter-switch/fingerprints.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 2eeb26b3d4..20be11f9d1 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -674,7 +674,7 @@ matterManufacturer: productId: 0x61B9 deviceProfileName: light-color-level - id: "4999/24742" - deviceLabel: Govee Ceiling Light Pro (15 inch) + deviceLabel: Govee Ceiling Light Pro vendorId: 0x1387 productId: 0x60A6 deviceProfileName: light-color-level From 66d9df4198279e4db72f97262676263882510213 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 19 May 2026 12:53:23 -0500 Subject: [PATCH 364/538] WWSTCERT-11719 Linkind Smart Light Bulb (#2985) --- .../matter-switch/fingerprints.yml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 20be11f9d1..ede868ac0f 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -186,6 +186,26 @@ matterManufacturer: vendorId: 0x1396 productId: 0x10B1 deviceProfileName: light-level-colorTemperature + - id: "5014/4164" + deviceLabel: Linkind Smart Light Bulb + vendorId: 0x1396 + productId: 0x1044 + deviceProfileName: light-color-level + - id: "5014/4118" + deviceLabel: Linkind Smart Filament Bulb + vendorId: 0x1396 + productId: 0x1016 + deviceProfileName: light-color-level + - id: "5014/4116" + deviceLabel: Linkind Smart Light Bulb + vendorId: 0x1396 + productId: 0x1014 + deviceProfileName: light-color-level + - id: "5014/4165" + deviceLabel: Linkind Smart Downlight + vendorId: 0x1396 + productId: 0x1045 + deviceProfileName: light-color-level #Bosch Smart Home - id: "4617/12310" deviceLabel: Plug Compact [M] From 8b1cecd9f8d1653cbd6c0a61b31db8f451a53251 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 19 May 2026 16:33:17 -0500 Subject: [PATCH 365/538] Matter Switch: remove ghost events, provision after driver switch (#2978) --- drivers/SmartThings/matter-switch/src/init.lua | 1 + .../matter-switch/src/sub_drivers/aqara_cube/init.lua | 4 +++- .../matter-switch/src/sub_drivers/camera/init.lua | 1 + .../matter-switch/src/sub_drivers/eve_energy/init.lua | 4 +++- .../src/sub_drivers/ikea_scroll/init.lua | 1 + .../ikea_scroll/scroll_utils/device_configuration.lua | 1 - .../src/sub_drivers/third_reality_mk1/init.lua | 1 - .../src/switch_utils/device_configuration.lua | 1 - .../src/test/test_aqara_climate_sensor_w100.lua | 3 --- .../src/test/test_aqara_light_switch_h2.lua | 8 -------- .../matter-switch/src/test/test_ikea_scroll.lua | 4 ---- .../matter-switch/src/test/test_matter_button.lua | 1 - .../matter-switch/src/test/test_matter_camera.lua | 2 -- .../src/test/test_matter_multi_button.lua | 9 --------- .../src/test/test_matter_multi_button_motion.lua | 11 ----------- .../src/test/test_matter_multi_button_switch_mcd.lua | 10 +--------- .../matter-switch/src/test/test_third_reality_mk1.lua | 1 - 17 files changed, 10 insertions(+), 53 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index dea56b4a21..e2a04e103a 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -61,6 +61,7 @@ function SwitchLifecycleHandlers.driver_switched(driver, device) if device.network_type == device_lib.NETWORK_TYPE_MATTER and not switch_utils.detect_bridge(device) then device_cfg.match_profile(driver, device) end + device:try_update_metadata({provisioning_state = "PROVISIONED"}) end function SwitchLifecycleHandlers.info_changed(driver, device, event, args) 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 4e6b729fa3..ca9dfa76cf 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 @@ -184,7 +184,9 @@ end local function do_configure(driver, device) end -- override driver_switched to prevent it running in the main driver -local function driver_switched(driver, device) end +local function driver_switched(driver, device) + device:try_update_metadata({provisioning_state = "PROVISIONED"}) +end local function initial_press_event_handler(driver, device, ib, response) if get_field_for_endpoint(device, INITIAL_PRESS_ONLY, ib.endpoint_id) then diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index a72aa0b234..2aa698a08a 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -43,6 +43,7 @@ function CameraLifecycleHandlers.driver_switched(driver, device) if #device:get_endpoints(clusters.CameraAvStreamManagement.ID) == 0 then camera_cfg.match_profile(device) end + device:try_update_metadata({provisioning_state = "PROVISIONED"}) end function CameraLifecycleHandlers.info_changed(driver, device, event, args) 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 8222fc1378..59f0cbb9c2 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 @@ -246,7 +246,9 @@ end local function do_configure(driver, device) end -- override driver_switched to prevent it running in the main driver -local function driver_switched(driver, device) end +local function driver_switched(driver, device) + device:try_update_metadata({provisioning_state = "PROVISIONED"}) +end local function handle_refresh(self, device) requestData(device) 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 index 32b23ccaae..6d6410dffa 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua @@ -25,6 +25,7 @@ end function IkeaScrollLifecycleHandlers.driver_switched(driver, device) scroll_cfg.match_profile(driver, device) + device:try_update_metadata({provisioning_state = "PROVISIONED"}) end function IkeaScrollLifecycleHandlers.info_changed(driver, device, event, args) 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 index 457804d495..0ad8a68826 100644 --- 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 @@ -22,7 +22,6 @@ function IkeaScrollConfiguration.configure_buttons(device) for _, ep in ipairs(scroll_fields.ENDPOINTS_PUSH) 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 for _, ep in ipairs(scroll_fields.ENDPOINTS_UP_SCROLL) do -- and by extension, ENDPOINTS_DOWN_SCROLL device:emit_event_for_endpoint(ep, capabilities.knob.supportedAttributes({"rotateAmount"}, {visibility = {displayed = false}})) 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 1572886089..9c80c247be 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 @@ -36,7 +36,6 @@ local function configure_buttons(device) device.log.info(string.format("Configuring Supported Values for generic switch endpoint %d", ep)) local supportedButtonValues_event = capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}) device:emit_event_for_endpoint(ep, supportedButtonValues_event) - device:emit_event_for_endpoint(ep, capabilities.button.button.pushed({state_change = false})) else device.log.info(string.format("Component not found for generic switch endpoint %d. Skipping Supported Value configuration", ep)) end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 085de2d351..f757a41871 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -184,7 +184,6 @@ function ButtonDeviceConfiguration.configure_buttons(device, momentary_switch_ep 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 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 1e23ffff4a..50713626f2 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 @@ -97,13 +97,10 @@ local aqara_mock_device = test.mock_device.build_test_matter_device({ local function configure_buttons() test.socket.matter:__expect_send({aqara_mock_device.id, clusters.Switch.attributes.MultiPressMax:read(aqara_mock_device, 3)}) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button1", capabilities.button.button.pushed({state_change = false}))) test.socket.matter:__expect_send({aqara_mock_device.id, clusters.Switch.attributes.MultiPressMax:read(aqara_mock_device, 4)}) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button2", capabilities.button.button.pushed({state_change = false}))) test.socket.matter:__expect_send({aqara_mock_device.id, clusters.Switch.attributes.MultiPressMax:read(aqara_mock_device, 5)}) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button3", capabilities.button.button.pushed({state_change = false}))) end local function test_init() 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 345afd08ce..f763cc769f 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 @@ -7,7 +7,6 @@ local capabilities = require "st.capabilities" local utils = require "st.utils" local dkjson = require "dkjson" local clusters = require "st.matter.clusters" -local button_attr = capabilities.button.button local version = require "version" if version.api < 11 then @@ -147,16 +146,9 @@ local cumulative_report_val_39 = { local function configure_buttons() test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("main", button_attr.pushed({state_change = false}))) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button2", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button2", button_attr.pushed({state_change = false}))) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button3", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button3", button_attr.pushed({state_change = false}))) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button4", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) - test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button4", button_attr.pushed({state_change = false}))) end local function test_init() diff --git a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua index 5a33424e1f..806b25f33f 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua @@ -156,13 +156,9 @@ local function ikea_scroll_subscribe() 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}))) test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("main", capabilities.knob.supportedAttributes({"rotateAmount"}, {visibility = {displayed = false}}))) test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group2", capabilities.knob.supportedAttributes({"rotateAmount"}, {visibility = {displayed = false}}))) test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group3", capabilities.knob.supportedAttributes({"rotateAmount"}, {visibility = {displayed = false}}))) 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 775f391e8e..061f2e2f47 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua @@ -49,7 +49,6 @@ local expected_initial_press_only_state = true local function expect_configure_button(device) test.socket.capability:__expect_send(device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) - test.socket.capability:__expect_send(device:generate_test_message("main", button_attr.pushed({state_change = false}))) end local function test_init_for_lifecycle_tests() diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 057bd3ae7f..9198dc253f 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -406,7 +406,6 @@ local function update_device_profile() 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 local additional_subscribed_attributes_no_per_zone_sensitivity = { @@ -3138,7 +3137,6 @@ test.register_coroutine_test( } mock_device:expect_metadata_update(updated_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, { min_api_version = 17 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 2392800019..d7d5efde97 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 @@ -179,19 +179,10 @@ local mock_device_battery = test.mock_device.build_test_matter_device( local function expect_configure_buttons(device) test.socket.capability:__expect_send(device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) - test.socket.capability:__expect_send(device:generate_test_message("main", button_attr.pushed({state_change = false}))) - test.socket.capability:__expect_send(device:generate_test_message("button2", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) - test.socket.capability:__expect_send(device:generate_test_message("button2", button_attr.pushed({state_change = false}))) - test.socket.capability:__expect_send(device:generate_test_message("button3", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) - test.socket.capability:__expect_send(device:generate_test_message("button3", button_attr.pushed({state_change = false}))) - test.socket.matter:__expect_send({device.id, clusters.Switch.attributes.MultiPressMax:read(device, 50)}) - test.socket.capability:__expect_send(device:generate_test_message("button4", button_attr.pushed({state_change = false}))) - test.socket.matter:__expect_send({device.id, clusters.Switch.attributes.MultiPressMax:read(device, 60)}) - test.socket.capability:__expect_send(device:generate_test_message("button5", button_attr.pushed({state_change = false}))) end local function update_profile() 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 index 5500eca24e..44ee11861e 100644 --- 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 @@ -125,22 +125,11 @@ local CLUSTER_SUBSCRIBE_LIST ={ 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 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 1c7cccea48..d469f72036 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 @@ -198,13 +198,8 @@ local CLUSTER_SUBSCRIBE_LIST_WITH_CHILD ={ local function expect_configure_buttons() test.socket.capability:__expect_send(mock_device:generate_test_message("button1", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("button1", 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}))) end local function test_init() @@ -422,7 +417,6 @@ test.register_coroutine_test( parent_assigned_child_key = string.format("%d", 7) }) 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" }) @@ -446,10 +440,7 @@ test.register_coroutine_test( 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, @@ -471,6 +462,7 @@ test.register_coroutine_test( 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_metadata_update({ provisioning_state = "PROVISIONED" }) end, { min_api_version = 17 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 82a8d4d641..b7e94797f6 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 @@ -185,7 +185,6 @@ local function configure_buttons() local component = "F" .. key if key == 1 then component = "main" end test.socket.capability:__expect_send(mock_device:generate_test_message(component, capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message(component, capabilities.button.button.pushed({state_change = false}))) end end From f66648955bbaf0f68f977d37611079426c720cfc Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 19 May 2026 16:33:28 -0500 Subject: [PATCH 366/538] Provision driver switch in Matter Sensor (#2981) --- .../sub_drivers/air_quality_sensor/init.lua | 1 + ...test_matter_air_quality_sensor_modular.lua | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+) 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 index e8d0cd4701..383af643a6 100644 --- 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 @@ -53,6 +53,7 @@ function AirQualitySensorLifecycleHandlers.driver_switched(driver, device) local legacy_device_cfg = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.legacy_device_configuration" legacy_device_cfg.match_profile(device) end + device:try_update_metadata({provisioning_state = "PROVISIONED"}) end function AirQualitySensorLifecycleHandlers.device_init(driver, device) 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 9525992eb3..27921e0dec 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 @@ -334,6 +334,28 @@ local function test_aqs_device_type_update_modular_profile(generic_mock_device, test.socket.matter:__expect_send({generic_mock_device.id, subscribe_request}) end +test.register_coroutine_test( + "Handle driverSwitched event", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_modular_fingerprint.id, "driverSwitched" }) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit:read()}) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit:read()}) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasurementUnit:read()}) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, clusters.OzoneConcentrationMeasurement.attributes.MeasurementUnit:read()}) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit:read()}) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, clusters.Pm1ConcentrationMeasurement.attributes.MeasurementUnit:read()}) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit:read()}) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit:read()}) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit:read()}) + test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit:read()}) + mock_device_modular_fingerprint:expect_metadata_update({ profile = "aqs-modular", optional_component_capabilities = {{"main", {"tvocMeasurement"}}} }) + mock_device_modular_fingerprint:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + test_init = test_init_modular_fingerprint, + } +) + test.register_coroutine_test( "Device with modular profile should enable correct optional capabilities - all clusters", function() From cb154a8522e79e99a1b08dbc64b2992af7f17b39 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 19 May 2026 16:33:39 -0500 Subject: [PATCH 367/538] Provision driver switch in Matter RVC (#2980) --- drivers/SmartThings/matter-rvc/src/init.lua | 1 + .../matter-rvc/src/test/test_matter_rvc.lua | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/SmartThings/matter-rvc/src/init.lua b/drivers/SmartThings/matter-rvc/src/init.lua index 1e52f321ee..2badac1413 100644 --- a/drivers/SmartThings/matter-rvc/src/init.lua +++ b/drivers/SmartThings/matter-rvc/src/init.lua @@ -120,6 +120,7 @@ local function driver_switched(driver, device) match_profile(driver, device) device:set_field(SERVICE_AREA_PROFILED, true, { persist = true }) device:send(clusters.RvcOperationalState.attributes.AcceptedCommandList:read()) + device:try_update_metadata({provisioning_state = "PROVISIONED"}) end local function info_changed(driver, device, event, args) diff --git a/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua b/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua index 3b03b1a2e1..626f7fea60 100644 --- a/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua +++ b/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua @@ -218,6 +218,19 @@ local function operating_state_init() ) end +test.register_coroutine_test( + "Handle driverSwitched event", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "driverSwitched" }) + test.socket.matter:__expect_send({mock_device.id, clusters.RvcOperationalState.attributes.AcceptedCommandList:read()}) + mock_device:expect_metadata_update({ profile = "rvc-clean-mode-service-area" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17 + } +) + test.register_coroutine_test( "Assert profile applied over doConfigure", function() From 3015acec135b1938d18c7345865c4355207b438c Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 19 May 2026 16:33:48 -0500 Subject: [PATCH 368/538] Provision driver switch in Matter Lock (#2979) --- .../matter-lock/src/new-matter-lock/init.lua | 1 + .../src/test/test_new_matter_lock.lua | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) 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 adcbc04283..670b9dc63b 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -400,6 +400,7 @@ end local function driver_switched(driver, device) match_profile(driver, device, false) + device:try_update_metadata({provisioning_state = "PROVISIONED"}) end -- Matter Handler diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index defeafe8e9..0b7535223b 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -82,6 +82,22 @@ end test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle driverSwitched event", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "driverSwitched" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) + ) + mock_device:expect_metadata_update({ profile = "lock-user-pin-schedule" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17 + } +) + test.register_coroutine_test( "Handle received OperatingMode(Normal, Vacation) from Matter device.", function() From 5bdca1ec25935e34754c758186f8e5de0757037d Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 20 May 2026 15:08:01 -0500 Subject: [PATCH 369/538] Matter Switch: Register setColorTemperature native handler (#2988) --- .../switch_handlers/attribute_handlers.lua | 10 +- .../switch_handlers/capability_handlers.lua | 5 +- .../matter-switch/src/switch_utils/fields.lua | 3 +- .../src/test/test_matter_light_fan.lua | 97 ------------------- .../test_matter_multi_button_switch_mcd.lua | 33 ------- .../test/test_matter_on_off_parent_child.lua | 8 ++ .../src/test/test_matter_switch.lua | 24 +++++ 7 files changed, 43 insertions(+), 137 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index 0fdc9cc822..838f932d86 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -128,12 +128,12 @@ function AttributeHandlers.color_temperature_mireds_handler(driver, device, ib, if device:get_field(fields.IS_PARENT_CHILD_DEVICE) == true then temp_device = switch_utils.find_child(device, ib.endpoint_id) or device end - local most_recent_temp = temp_device:get_field(fields.MOST_RECENT_TEMP) + local latest_requested_kelvin = temp_device:get_field(fields.LATEST_REQUESTED_KELVIN) -- this is to avoid rounding errors from the round-trip conversion of Kelvin to mireds - if most_recent_temp ~= nil and - most_recent_temp <= st_utils.round(fields.MIRED_KELVIN_CONVERSION_CONSTANT/(temp_in_mired - 1)) and - most_recent_temp >= st_utils.round(fields.MIRED_KELVIN_CONVERSION_CONSTANT/(temp_in_mired + 1)) then - temp = most_recent_temp + if latest_requested_kelvin and + latest_requested_kelvin <= st_utils.round(fields.MIRED_KELVIN_CONVERSION_CONSTANT/(temp_in_mired - 1)) and + latest_requested_kelvin >= st_utils.round(fields.MIRED_KELVIN_CONVERSION_CONSTANT/(temp_in_mired + 1)) then + temp = latest_requested_kelvin end device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorTemperature.colorTemperature(temp)) end diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua index 6d035c3dc7..573d2b791b 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua @@ -107,6 +107,9 @@ end -- [[ COLOR TEMPERATURE CAPABILITY COMMANDS ]] -- function CapabilityHandlers.handle_set_color_temperature(driver, device, cmd) + if type(device.register_native_capability_cmd_handler) == "function" then + device:register_native_capability_cmd_handler(cmd.capability, cmd.command) + end local endpoint_id = device:component_to_endpoint(cmd.component) local temp_in_kelvin = cmd.args.temperature -- note: the field containing the color temp bounds will be associated with a parent device @@ -121,7 +124,7 @@ function CapabilityHandlers.handle_set_color_temperature(driver, device, cmd) temp_in_mired = switch_utils.get_field_for_endpoint(field_device, fields.COLOR_TEMP_BOUND_RECEIVED_MIRED..fields.COLOR_TEMP_MIN, endpoint_id) end local req = clusters.ColorControl.server.commands.MoveToColorTemperature(device, endpoint_id, temp_in_mired, fields.ZERO_TRANSITION_TIME, fields.OPTIONS_MASK, fields.HANDLE_COMMAND_IF_OFF) - device:set_field(fields.MOST_RECENT_TEMP, cmd.args.temperature, {persist = true}) + device:set_field(fields.LATEST_REQUESTED_KELVIN, cmd.args.temperature) device:send(req) end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index a8749a6480..f70a7e2160 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -5,7 +5,7 @@ local clusters = require "st.matter.clusters" local SwitchFields = {} -SwitchFields.MOST_RECENT_TEMP = "mostRecentTemp" +SwitchFields.LATEST_REQUESTED_KELVIN = "mostRecentTemp" SwitchFields.RECEIVED_X = "receivedX" SwitchFields.RECEIVED_Y = "receivedY" SwitchFields.HUESAT_SUPPORT = "huesatSupport" @@ -100,6 +100,7 @@ SwitchFields.updated_fields = { { current_field_name = "__energy_management_endpoint", updated_field_name = nil }, { current_field_name = "__total_imported_energy", updated_field_name = nil }, { current_field_name = "__last_imported_report_timestamp", updated_field_name = nil }, + { current_field_name = "mostRecentTemp", updated_field_name = nil }, } SwitchFields.vendor_overrides = { 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 3dad8bf56d..32023a36cb 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 @@ -5,11 +5,6 @@ local capabilities = require "st.capabilities" local clusters = require "st.matter.generated.zap_clusters" local t_utils = require "integration_test.utils" local test = require "integration_test" -local version = require "version" - -local TRANSITION_TIME = 0 -local OPTIONS_MASK = 0x01 -local HANDLE_COMMAND_IF_OFF = 0x01 local mock_device_ep1 = 1 local mock_device_ep2 = 2 @@ -173,98 +168,6 @@ test.register_coroutine_test( { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } ) - -test.register_coroutine_test( - "Switch capability should send the appropriate commands", function() - test.socket.capability:__queue_receive( - { - mock_child.id, - { capability = "switch", component = "main", command = "on", args = { } } - } - ) - if version.api >= 11 then - test.socket.devices:__expect_send( - { - "register_native_capability_cmd_handler", - { 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, mock_device_ep1) - } - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, mock_device_ep1, true) - } - ) - test.socket.devices:__expect_send( - { - "register_native_capability_attr_handler", - { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } - } - ) - test.socket.capability:__expect_send( - mock_child:generate_test_message( - "main", capabilities.switch.switch.on() - ) - ) - end, - { - min_api_version = 17 - } -) - -test.register_message_test( - "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_ep1, 556, TRANSITION_TIME, OPTIONS_MASK, HANDLE_COMMAND_IF_OFF) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature:build_test_command_response(mock_device, mock_device_ep1) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, mock_device_ep1, 556) - } - }, - { - channel = "capability", - direction = "send", - message = mock_child:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) - }, - }, - { - min_api_version = 17 - } -) - local FanMode = clusters.FanControl.attributes.FanMode test.register_message_test( "Fan mode reports should generate correct messages", 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 d469f72036..70f9666542 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 @@ -7,12 +7,8 @@ local t_utils = require "integration_test.utils" local clusters = require "st.matter.generated.zap_clusters" -local TRANSITION_TIME = 0 -local OPTIONS_MASK = 0x01 -local HANDLE_COMMAND_IF_OFF = 0x01 local button_attr = capabilities.button.button - local mock_device_ep1 = 1 local mock_device_ep2 = 2 local mock_device_ep3 = 3 @@ -360,35 +356,6 @@ test.register_coroutine_test( } ) -test.register_coroutine_test( - "Switch child device: Set color temperature should send the appropriate commands", - 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, HANDLE_COMMAND_IF_OFF) - }) - 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, - { - min_api_version = 17 - } -) - test.register_coroutine_test( "Test MCD configuration not including switch for unsupported switch device type, create child device instead", function() diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua index 1531632d24..ff8238a52c 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua @@ -415,6 +415,14 @@ test.register_message_test( { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {1800} } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_children[extended_color_ep_id].id, capability_id = "colorTemperature", capability_cmd_id = "setColorTemperature" } + } + }, { channel = "matter", direction = "send", 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 c34cbb8ed6..c3e472268f 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -244,6 +244,14 @@ test.register_message_test( { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {1800} } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "colorTemperature", capability_cmd_id = "setColorTemperature" } + } + }, { channel = "matter", direction = "send", @@ -444,6 +452,14 @@ test.register_message_test( { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {6100} } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "colorTemperature", capability_cmd_id = "setColorTemperature" } + } + }, { channel = "matter", direction = "send", @@ -460,6 +476,14 @@ test.register_message_test( { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {2700} } } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "colorTemperature", capability_cmd_id = "setColorTemperature" } + } + }, { channel = "matter", direction = "send", From 7997c4a2cae1986c2303c239463814502480be0c Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 21 May 2026 15:17:13 -0500 Subject: [PATCH 370/538] WWSTCERT-11548 Dreame NAVO Smart Lock A10 (#2963) * WWSTCERT-11548 New Product * Update device label for Dreame NAVO Smart Lock A10 --- drivers/SmartThings/matter-lock/fingerprints.yml | 6 ++++++ .../matter-lock/src/new-matter-lock/fingerprints.lua | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 4ac4fc25b4..14db5d755e 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -161,6 +161,12 @@ matterManufacturer: vendorId: 0x101D productId: 0x8110 deviceProfileName: lock-user-pin-schedule-battery + #Dreame + - id: "5420/38144" + deviceLabel: Dreame NAVO Smart Lock A10 + vendorId: 0x152C + productId: 0x9500 + deviceProfileName: lock matterGeneric: - id: "matter/door-lock" deviceLabel: Matter Door Lock diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index ac8aa0c07c..4518189b64 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -35,7 +35,8 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x1421, 0x0081}, -- Kwikset Aura Reach {0x1236, 0xa538}, -- Schlage Sense Pro {0x1236, 0x3800}, -- Schlage - {0x1236, 0xA738} -- Schlage + {0x1236, 0xA738}, -- Schlage + {0x152C, 0x9500} -- Dreame NAVO Smart Lock A10 } return NEW_MATTER_LOCK_PRODUCTS From f1dbe34180067088bb16580ba633653071c80e7d Mon Sep 17 00:00:00 2001 From: seojune79 <119141897+seojune79@users.noreply.github.com> Date: Fri, 22 May 2026 05:54:56 +0900 Subject: [PATCH 371/538] [Aqara] add Aqara Bath Heater T1 (#2942) * add Aqara Bath Heater T1 * add refresh capability and testcase * Fix unresponsive refresh issue * Addressed nit to keep elements interlocked and removed redundant condition since hi32 remains 0xFFFFFFFF, making (hi32 & 0xFFFF) always 0xFFFF. * refactor: address code review feedback on handlers and event timing - Add register_for_default_handlers and call handle_refresh to eliminate duplicated code. - Update event emission to trigger after ac_code is sent for consistency across all command handlers. * refactor(capability): wait for confirmed device state before emitting - Remove optimistic emit to prevent temporary inconsistent states. - Exception: Keep optimistic emit for thermostat mode changes to match Aqara app UX. * include thermostat and fan modes for default driver behavior --- drivers/Aqara/aqara-bath-heater/config.yml | 6 + .../Aqara/aqara-bath-heater/fingerprints.yml | 6 + .../profiles/aqara-bath-heater.yml | 180 ++++ .../aqara-bath-heater/src/aqara_cluster.lua | 14 + drivers/Aqara/aqara-bath-heater/src/init.lua | 537 +++++++++++ .../src/test/test_aqara_bath_heater.lua | 852 ++++++++++++++++++ 6 files changed, 1595 insertions(+) create mode 100644 drivers/Aqara/aqara-bath-heater/config.yml create mode 100644 drivers/Aqara/aqara-bath-heater/fingerprints.yml create mode 100644 drivers/Aqara/aqara-bath-heater/profiles/aqara-bath-heater.yml create mode 100644 drivers/Aqara/aqara-bath-heater/src/aqara_cluster.lua create mode 100644 drivers/Aqara/aqara-bath-heater/src/init.lua create mode 100644 drivers/Aqara/aqara-bath-heater/src/test/test_aqara_bath_heater.lua diff --git a/drivers/Aqara/aqara-bath-heater/config.yml b/drivers/Aqara/aqara-bath-heater/config.yml new file mode 100644 index 0000000000..4465c61440 --- /dev/null +++ b/drivers/Aqara/aqara-bath-heater/config.yml @@ -0,0 +1,6 @@ +name: Aqara Bath Heater +packageKey: aqara-bath-heater +permissions: + zigbee: {} +description: "SmartThings driver for Aqara Bath Heater devices" +vendorSupportInformation: "https://www.aqara.com/en/support/" diff --git a/drivers/Aqara/aqara-bath-heater/fingerprints.yml b/drivers/Aqara/aqara-bath-heater/fingerprints.yml new file mode 100644 index 0000000000..ecef588f02 --- /dev/null +++ b/drivers/Aqara/aqara-bath-heater/fingerprints.yml @@ -0,0 +1,6 @@ +zigbeeManufacturer: + - id: "Aqara/lumi.bhf_light.acn001" + deviceLabel: Aqara Bath Heater T1 + manufacturer: Aqara + model: lumi.bhf_light.acn001 + deviceProfileName: "aqara-bath-heater" diff --git a/drivers/Aqara/aqara-bath-heater/profiles/aqara-bath-heater.yml b/drivers/Aqara/aqara-bath-heater/profiles/aqara-bath-heater.yml new file mode 100644 index 0000000000..d130be5947 --- /dev/null +++ b/drivers/Aqara/aqara-bath-heater/profiles/aqara-bath-heater.yml @@ -0,0 +1,180 @@ +name: aqara-bath-heater +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: colorTemperature + version: 1 + config: + values: + - key: "colorTemperature.value" + range: [2700, 6500] + - id: thermostatMode + version: 1 + config: + values: + - key: "thermostatMode.value" + enabledValues: + - "off" + - "heat" + - "dryair" + - "cool" + - "fanonly" + - id: thermostatHeatingSetpoint + version: 1 + config: + values: + - key: "thermostatHeatingSetpoint.value" + range: [16, 45] + unit: "C" + - id: fanOscillationMode + version: 1 + config: + values: + - key: "fanOscillationMode.value" + enabledValues: + - "swing" + - "fixed" + - id: fanMode + version: 1 + config: + values: + - key: "fanMode.value" + enabledValues: + - "low" + - "medium" + - "high" + - id: refresh + version: 1 + categories: + - name: Thermostat +deviceConfig: + dashboard: + states: + - component: main + capability: switch + version: 1 + - component: main + capability: fanMode + version: 1 + actions: + - component: main + capability: switch + version: 1 + detailView: + - component: main + capability: switch + version: 1 + - component: main + capability: switchLevel + version: 1 + - component: main + capability: colorTemperature + version: 1 + - component: main + capability: thermostatMode + version: 1 + - component: main + capability: thermostatHeatingSetpoint + version: 1 + visibleCondition: + component: main + capability: thermostatMode + version: 1 + value: thermostatMode.value + operator: EQUALS + operand: "heat" + - component: main + capability: fanOscillationMode + version: 1 + visibleCondition: + component: main + capability: thermostatMode + version: 1 + value: thermostatMode.value + operator: ONE_OF + operand: '["heat", "dryair", "cool"]' + - component: main + capability: fanMode + version: 1 + visibleCondition: + component: main + capability: thermostatMode + version: 1 + value: thermostatMode.value + operator: ONE_OF + operand: '["heat", "dryair", "cool", "fanonly"]' + - component: main + capability: refresh + version: 1 + automation: + conditions: + - component: main + capability: switch + version: 1 + - component: main + capability: switchLevel + version: 1 + - component: main + capability: colorTemperature + version: 1 + - component: main + capability: thermostatMode + version: 1 + - component: main + capability: thermostatHeatingSetpoint + version: 1 + - component: main + capability: fanOscillationMode + version: 1 + - component: main + capability: fanMode + version: 1 + values: + - key: "fanMode.value" + enabledValues: + - "low" + - "medium" + - "high" + actions: + - component: main + capability: switch + version: 1 + - component: main + capability: switchLevel + version: 1 + - component: main + capability: colorTemperature + version: 1 + - component: main + capability: thermostatMode + version: 1 + - component: main + capability: thermostatHeatingSetpoint + version: 1 + - component: main + capability: fanOscillationMode + version: 1 + - component: main + capability: fanMode + version: 1 + values: + - key: "setFanMode.fanMode" + enabledValues: + - "low" + - "medium" + - "high" +preferences: + - preferenceId: stse.nightLightMode + explicit: true + - preferenceId: stse.nightLightStartTime + explicit: true + - preferenceId: stse.nightLightEndTime + explicit: true + - preferenceId: stse.muteBeep + explicit: true + - preferenceId: stse.thermostatCtrl + explicit: true diff --git a/drivers/Aqara/aqara-bath-heater/src/aqara_cluster.lua b/drivers/Aqara/aqara-bath-heater/src/aqara_cluster.lua new file mode 100644 index 0000000000..90fd6d0655 --- /dev/null +++ b/drivers/Aqara/aqara-bath-heater/src/aqara_cluster.lua @@ -0,0 +1,14 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +local M = {} + +M.CLUSTER_ID = 0xFCC0 +M.MFG_CODE = 0x115F -- Lumi/Aqara manufacturer code + +M.ATTR_AC_CODE = 0x024F +M.ATTR_THERMOSTAT_CTRL_SW = 0x02BE +M.ATTR_DND_BEEP = 0x0256 +M.ATTR_DND_TIME = 0x0257 +M.ATTR_NIGHT_LIGHT = 0x0518 + +return M diff --git a/drivers/Aqara/aqara-bath-heater/src/init.lua b/drivers/Aqara/aqara-bath-heater/src/init.lua new file mode 100644 index 0000000000..f543a704a8 --- /dev/null +++ b/drivers/Aqara/aqara-bath-heater/src/init.lua @@ -0,0 +1,537 @@ +-- Copyright 2026 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 cluster_base = require "st.zigbee.cluster_base" +local zcl_clusters = require "st.zigbee.zcl.clusters" +local data_types = require "st.zigbee.data_types" + +local aqara = require "aqara_cluster" + +local OnOff = zcl_clusters.OnOff +local Level = zcl_clusters.Level +local ColorControl = zcl_clusters.ColorControl + +-- Aqara manufacturer-specific preference keys +local nightLightMode = "stse.nightLightMode" +local nightLightEndTime = "stse.nightLightEndTime" +local nightLightStartTime = "stse.nightLightStartTime" +local muteBeep = "stse.muteBeep" +local thermostatCtrl = "stse.thermostatCtrl" + +-- AC code field values (see send_ac_code for the bit layout) +local PWR = { OFF = 0x0, ON = 0x1 } +local MODE = { HEAT = 0x0, DRYAIR = 0x3, COOL = 0x4, FANONLY = 0x5, INVALID = 0xF } +local FAN_LOW = 0x0 +local FAN_MID = 0x1 +local FAN_HIGH = 0x2 +local FAN_INVALID = 0xF +local SWING_ON = 0x0 +local SWING_OFF = 0x1 + +-- SmartThings fanMode capability values +local SPEED = { + LOW = "low", + MEDIUM = "medium", + HIGH = "high", +} +local MODE_TO_FAN = { [SPEED.LOW] = FAN_LOW, [SPEED.MEDIUM] = FAN_MID, [SPEED.HIGH] = FAN_HIGH } +local FAN_TO_MODE = { [FAN_LOW] = SPEED.LOW, [FAN_MID] = SPEED.MEDIUM, [FAN_HIGH] = SPEED.HIGH } + +-- SmartThings fanOscillationMode capability values +local OSC = { + SWING = "swing", + FIXED = "fixed", +} +local ST_FAN_TO_SWING = { + [OSC.SWING] = SWING_ON, + [OSC.FIXED] = SWING_OFF, +} + +-- SmartThings thermostatMode capability values +local ST_MODE = { + OFF = "off", + HEAT = "heat", + DRYAIR = "dryair", + COOL = "cool", + FANONLY = "fanonly", +} + +-- SmartThings thermostatMode -> AC parameters +local ST_TO_AC = { + [ST_MODE.OFF] = { pwr = PWR.OFF, mode = MODE.INVALID, fan = FAN_INVALID }, + [ST_MODE.HEAT] = { pwr = PWR.ON, mode = MODE.HEAT, fan = FAN_MID }, + [ST_MODE.DRYAIR] = { pwr = PWR.ON, mode = MODE.DRYAIR, fan = FAN_MID }, + [ST_MODE.COOL] = { pwr = PWR.ON, mode = MODE.COOL, fan = FAN_MID }, + [ST_MODE.FANONLY] = { pwr = PWR.ON, mode = MODE.FANONLY, fan = FAN_MID }, +} + +-- AC mode bits -> SmartThings thermostatMode +local AC_MODE_TO_ST = { + [0x0] = ST_MODE.HEAT, + [0x3] = ST_MODE.DRYAIR, + [0x4] = ST_MODE.COOL, + [0x5] = ST_MODE.FANONLY, +} + +local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end + +-- Encode and send the 64-bit AC control code as an Aqara manufacturer attribute. +-- A nibble of 0xF means "no change"; the default 0xFFFFFFFFFFFFFFFF leaves +-- every field untouched so callers only need to set what they want to change. +-- pwr : bits31-28 0=off 1=on +-- mode : bits27-24 0=heat 3=dryair 4=cool 5=fanonly +-- fan : bits23-20 0=low 1=mid 2=high 3=auto +-- swing : bits17-16 0=swing 1=fixed +-- setpoint : bits63-48 Celsius x 100 +local function send_ac_code(device, params) + local hi32 = 0xFFFFFFFF + local lo32 = 0xFFFFFFFF + + if params.setpoint ~= nil then + local sp_raw = math.floor(clamp(params.setpoint, 16, 45) * 100) & 0xFFFF + hi32 = (sp_raw << 16) | 0xFFFF + end + + if params.pwr ~= nil then + lo32 = (lo32 & 0x0FFFFFFF) | ((params.pwr & 0xF) << 28) + end + + if params.mode ~= nil then + lo32 = (lo32 & 0xF0FFFFFF) | ((params.mode & 0xF) << 24) + end + + if params.fan ~= nil then + lo32 = (lo32 & 0xFF0FFFFF) | ((params.fan & 0xF) << 20) + end + + if params.swing ~= nil then + lo32 = (lo32 & 0xFFFCFFFF) | ((params.swing & 0x3) << 16) + end + + local bytes = string.char( + (hi32 >> 24) & 0xFF, + (hi32 >> 16) & 0xFF, + (hi32 >> 8) & 0xFF, + hi32 & 0xFF, + (lo32 >> 24) & 0xFF, + (lo32 >> 16) & 0xFF, + (lo32 >> 8) & 0xFF, + lo32 & 0xFF + ) + + device:send(cluster_base.write_manufacturer_specific_attribute( + device, aqara.CLUSTER_ID, aqara.ATTR_AC_CODE, aqara.MFG_CODE, + data_types.Uint64, bytes + )) +end + +-- Per-mode state persistence: remember the last swing/fan used in each +-- thermostat mode so they can be restored when the user returns to it. +-- Setpoint is shared across modes and read directly from the capability state. +local FIELD = { + SWING = "swing", + FAN_MODE = "fan_mode", +} + +-- Fields tracked per mode (modes not listed here have no per-mode state). +local MODE_FIELDS = { + [ST_MODE.HEAT] = { FIELD.SWING, FIELD.FAN_MODE }, + [ST_MODE.COOL] = { FIELD.SWING, FIELD.FAN_MODE }, + [ST_MODE.DRYAIR] = { FIELD.SWING, FIELD.FAN_MODE }, + [ST_MODE.FANONLY] = { FIELD.FAN_MODE }, +} + +-- Initial values when no saved state exists yet for a mode. +local MODE_DEFAULTS = { + [ST_MODE.HEAT] = { swing = OSC.SWING, fan_mode = SPEED.MEDIUM }, + [ST_MODE.COOL] = { swing = OSC.SWING, fan_mode = SPEED.MEDIUM }, + [ST_MODE.DRYAIR] = { swing = OSC.SWING, fan_mode = SPEED.MEDIUM }, + [ST_MODE.FANONLY] = { fan_mode = SPEED.MEDIUM }, +} + +local function save_mode_state(device, mode, field, value) + device:set_field("mode_state." .. mode .. "." .. field, value, { persist = true }) +end + +local function load_mode_state(device, mode, field) + return device:get_field("mode_state." .. mode .. "." .. field) +end + +local function save_current_mode_field(device, field, value) + local mode = device:get_field("thermostat_mode") or ST_MODE.OFF + local fields = MODE_FIELDS[mode] + if fields then + for _, f in ipairs(fields) do + if f == field then + save_mode_state(device, mode, field, value) + return + end + end + end +end + +-- Capture the current capability state for each field tracked by the given +-- mode. Used right before switching modes so any in-flight changes (e.g., a +-- setpoint set via the UI but not yet confirmed by the device) are preserved. +local function snapshot_mode_state(device, mode) + local fields = MODE_FIELDS[mode] + if not fields then return end + + for _, field in ipairs(fields) do + local v + if field == FIELD.SWING then + v = device:get_latest_state("main", + capabilities.fanOscillationMode.ID, + capabilities.fanOscillationMode.fanOscillationMode.NAME) + elseif field == FIELD.FAN_MODE then + v = device:get_latest_state("main", + capabilities.fanMode.ID, + capabilities.fanMode.fanMode.NAME) + end + if v ~= nil then + save_mode_state(device, mode, field, v) + end + end +end + +-- Re-emit saved values for the entered mode and push them to the AC in a +-- single batched code, falling back to MODE_DEFAULTS on first use. +local function restore_mode_state(device, st_mode) + local fields = MODE_FIELDS[st_mode] + if not fields then return end + + local mode_defaults = MODE_DEFAULTS[st_mode] or {} + local swing, fan = nil, nil + + for _, field in ipairs(fields) do + if field == FIELD.SWING then + local v = load_mode_state(device, st_mode, FIELD.SWING) or mode_defaults.swing + if v ~= nil then + swing = ST_FAN_TO_SWING[v] + device:set_field("fan_mode", v) + device:emit_event(capabilities.fanOscillationMode.fanOscillationMode(v)) + end + elseif field == FIELD.FAN_MODE then + local v = load_mode_state(device, st_mode, FIELD.FAN_MODE) or mode_defaults.fan_mode + if v ~= nil then + fan = MODE_TO_FAN[v] + device:set_field("fan_mode_ac", fan) + device:emit_event(capabilities.fanMode.fanMode(v)) + end + end + end + + if swing ~= nil or fan ~= nil then + send_ac_code(device, { swing = swing, fan = fan }) + end +end + + +-- Capability handlers +local function handle_thermostat_mode(driver, device, cmd) + local st_mode = cmd.args.mode + local ac = ST_TO_AC[st_mode] + if not ac then return end + + local prev_mode = device:get_field("thermostat_mode") + if prev_mode and prev_mode ~= st_mode then + snapshot_mode_state(device, prev_mode) + end + + local pwr = (st_mode == ST_MODE.OFF) and PWR.OFF or PWR.ON + -- Setpoint is shared across modes; on entry to HEAT, push the last value + -- the user set so the device matches what the UI is currently showing. + local setpoint = nil + if st_mode == ST_MODE.HEAT then + local state = device:get_latest_state("main", + capabilities.thermostatHeatingSetpoint.ID, + capabilities.thermostatHeatingSetpoint.heatingSetpoint.NAME) + if state ~= nil then + setpoint = clamp(state, 16, 45) + end + end + + send_ac_code(device, { pwr = pwr, mode = ac.mode, setpoint = setpoint }) + device:set_field("thermostat_mode", st_mode) + device:emit_event(capabilities.thermostatMode.thermostatMode(st_mode)) + restore_mode_state(device, st_mode) + + if st_mode ~= ST_MODE.OFF then + device:set_field("pending_on_mode", st_mode) + else + device:set_field("pending_on_mode", nil) + end +end + +local function handle_heating_setpoint(driver, device, cmd) + local temp_c = clamp(cmd.args.setpoint, 16, 45) + device:set_field("heating_setpoint", temp_c) + + local cur = device:get_field("thermostat_mode") or ST_MODE.OFF + if cur == ST_MODE.HEAT then + send_ac_code(device, { setpoint = temp_c }) + end +end + +local function handle_fan_oscillation_mode(driver, device, cmd) + local st_fan = cmd.args.fanOscillationMode + local swing = ST_FAN_TO_SWING[st_fan] or SWING_ON + + device:set_field("fan_mode", st_fan) + send_ac_code(device, { swing = swing }) +end + +local function handle_fan_mode(driver, device, cmd) + local fan_mode = cmd.args.fanMode + local fan = MODE_TO_FAN[fan_mode] or FAN_MID + device:set_field("fan_mode_ac", fan) + send_ac_code(device, { fan = fan }) +end + +local function handle_refresh(driver, device) + device:send(OnOff.attributes.OnOff:read(device)) + device:send(Level.attributes.CurrentLevel:read(device)) + device:send(ColorControl.attributes.ColorTemperatureMireds:read(device)) +end + +-- Zigbee attribute handlers +-- Decode the AC code reported by the device, emit matching capability events, +-- and persist the per-mode state so values are restored when the user returns +-- to that mode. +local function ac_code_attr_handler(driver, device, value, zb_rx) + local raw = value.value + local hi32, lo32 + + -- The attribute is a Uint64 but may arrive either as a raw integer or as + -- the 8-byte big-endian payload depending on the runtime path. + if type(raw) == "string" then + local b = { string.byte(raw, 1, 8) } + hi32 = ((b[1] or 0) << 24) | ((b[2] or 0) << 16) | ((b[3] or 0) << 8) | (b[4] or 0) + lo32 = ((b[5] or 0) << 24) | ((b[6] or 0) << 16) | ((b[7] or 0) << 8) | (b[8] or 0) + else + hi32 = (raw >> 32) & 0xFFFFFFFF + lo32 = raw & 0xFFFFFFFF + end + + local pwr = (lo32 >> 28) & 0xF + local mode = (lo32 >> 24) & 0xF + local fan_set = (lo32 >> 20) & 0xF + local b15_8 = (lo32 >> 8) & 0xFF + local b7_0 = lo32 & 0xFF + local bits7_2 = (b7_0 >> 2) & 0x3F + + -- The setpoint nibbles are only trustworthy when the surrounding sentinel + -- bytes match this pattern; otherwise the device is reporting "no change". + local hi_valid = (b15_8 >= 0xFE) and (bits7_2 == 63) + local setpoint_raw = (hi32 >> 16) & 0xFFFF + + if hi_valid and setpoint_raw ~= 0xFFFF then + local sp = setpoint_raw / 100.0 + device:set_field("heating_setpoint", sp) + device:emit_event(capabilities.thermostatHeatingSetpoint.heatingSetpoint( + { value = sp, unit = "C" } + )) + end + + -- fan speed (bits23-20): 0=low, 1=mid, 2=high; 3=auto and 0xF are ignored. + if fan_set <= 2 then + local fan_mode = FAN_TO_MODE[fan_set] or SPEED.MEDIUM + device:set_field("fan_mode_ac", fan_set) + save_current_mode_field(device, FIELD.FAN_MODE, fan_mode) + device:emit_event(capabilities.fanMode.fanMode(fan_mode)) + end + + -- swing mode (bits17-16): 0=swing, 1=fixed; other values are ignored. + local swing_bit = (lo32 >> 16) & 0x3 + if swing_bit == 0 then + device:set_field("fan_mode", OSC.SWING) + save_current_mode_field(device, FIELD.SWING, OSC.SWING) + device:emit_event(capabilities.fanOscillationMode.fanOscillationMode(OSC.SWING)) + elseif swing_bit == 1 then + device:set_field("fan_mode", OSC.FIXED) + save_current_mode_field(device, FIELD.SWING, OSC.FIXED) + device:emit_event(capabilities.fanOscillationMode.fanOscillationMode(OSC.FIXED)) + end + + -- 0xF in the pwr nibble is the "no change" sentinel; mode bits are unreliable. + if pwr == 0xF then return end + + local st_mode + if pwr == 0x0 then + st_mode = ST_MODE.OFF + else + st_mode = AC_MODE_TO_ST[mode] or ST_MODE.HEAT + end + + -- Suppress a transient "off" report that arrives between a mode change + -- request and the device confirming the new mode. + local pending = device:get_field("pending_on_mode") + if st_mode ~= ST_MODE.OFF then + device:set_field("pending_on_mode", nil) + else + if pending ~= nil then return end + end + + local current = device:get_field("thermostat_mode") + if current ~= st_mode then + device:set_field("thermostat_mode", st_mode) + device:emit_event(capabilities.thermostatMode.thermostatMode(st_mode)) + end +end + +local SUPPORTED_THERMOSTAT_MODES = { + capabilities.thermostatMode.thermostatMode.off.NAME, + capabilities.thermostatMode.thermostatMode.heat.NAME, + capabilities.thermostatMode.thermostatMode.dryair.NAME, + capabilities.thermostatMode.thermostatMode.cool.NAME, + capabilities.thermostatMode.thermostatMode.fanonly.NAME +} + +local SUPPORTED_FAN_MODES = { + capabilities.fanOscillationMode.fanOscillationMode.swing.NAME, + capabilities.fanOscillationMode.fanOscillationMode.fixed.NAME +} + +local SUPPORTED_SPEED_MODES = { SPEED.LOW, SPEED.MEDIUM, SPEED.HIGH } + +-- Lifecycle handlers +local function device_init(driver, device) + device:emit_event(capabilities.thermostatMode.supportedThermostatModes( + SUPPORTED_THERMOSTAT_MODES, { visibility = { displayed = false } } + )) + device:emit_event(capabilities.fanOscillationMode.supportedFanOscillationModes( + SUPPORTED_FAN_MODES, { visibility = { displayed = false } } + )) + device:emit_event(capabilities.fanMode.supportedFanModes( + SUPPORTED_SPEED_MODES, { visibility = { displayed = false } } + )) + device:emit_event(capabilities.thermostatHeatingSetpoint.heatingSetpointRange( + { value = { minimum = 16, maximum = 45, step = 1 }, unit = "C" } + )) + handle_refresh(driver, device) +end + +local function device_added(driver, device) + if device:get_latest_state("main", capabilities.thermostatHeatingSetpoint.ID, + capabilities.thermostatHeatingSetpoint.heatingSetpoint.NAME) == nil then + device:emit_event(capabilities.thermostatHeatingSetpoint.heatingSetpoint( + { value = 25, unit = "C" } + )) + send_ac_code(device, { setpoint = 25 }) + end + if device:get_latest_state("main", capabilities.fanMode.ID, + capabilities.fanMode.fanMode.NAME) == nil then + device:emit_event(capabilities.fanMode.fanMode(SPEED.MEDIUM)) + end + if device:get_latest_state("main", capabilities.fanOscillationMode.ID, + capabilities.fanOscillationMode.fanOscillationMode.NAME) == nil then + device:emit_event(capabilities.fanOscillationMode.fanOscillationMode(OSC.SWING)) + end +end + +local function send_night_light(device, new) + local start_time = (tonumber(new[nightLightStartTime]) * 60) & 0xFFF + local end_time = (tonumber(new[nightLightEndTime]) * 60) & 0xFFF + local on_val = (end_time << 12) | start_time + local val = new[nightLightMode] and on_val or (on_val + 1) + device:send(cluster_base.write_manufacturer_specific_attribute( + device, aqara.CLUSTER_ID, aqara.ATTR_NIGHT_LIGHT, + aqara.MFG_CODE, data_types.Uint32, val)) +end + +local function info_changed(driver, device, event, args) + if args.old_st_store.preferences == nil then return end + + local old = args.old_st_store.preferences + local new = device.preferences + + -- Night-light: re-send when the on/off toggle flips, or when the schedule + -- changes while the feature is enabled. + local mode_changed = old[nightLightMode] ~= new[nightLightMode] + local time_changed = + old[nightLightEndTime] ~= new[nightLightEndTime] or + old[nightLightStartTime] ~= new[nightLightStartTime] + if mode_changed then + send_night_light(device, new) + elseif time_changed and new[nightLightMode] == true then + send_night_light(device, new) + end + + -- Mute beep ("do not disturb"). On first init we always push the value so + -- the device matches the preference even if it was changed before pairing. + if old[muteBeep] ~= new[muteBeep] or device:get_field("inited") == nil then + local val = new[muteBeep] and 1 or 0 + device:set_field("inited", true) + device:send(cluster_base.write_manufacturer_specific_attribute( + device, aqara.CLUSTER_ID, aqara.ATTR_DND_BEEP, + aqara.MFG_CODE, data_types.Uint8, val)) + -- When un-muted, configure the DND window to span 24h (00:18 - 00:18). + if val == 0 then + device:send(cluster_base.write_manufacturer_specific_attribute( + device, aqara.CLUSTER_ID, aqara.ATTR_DND_TIME, + aqara.MFG_CODE, data_types.Uint32, 0x00120012)) + end + end + + -- Constant-temperature thermostat control switch. + if old[thermostatCtrl] ~= new[thermostatCtrl] then + device:send(cluster_base.write_manufacturer_specific_attribute( + device, aqara.CLUSTER_ID, aqara.ATTR_THERMOSTAT_CTRL_SW, + aqara.MFG_CODE, data_types.Uint8, new[thermostatCtrl] and 1 or 0)) + end +end + +local aqara_bathroom_heater_driver_template = { + supported_capabilities = { + capabilities.switch, + capabilities.switchLevel, + capabilities.colorTemperature, + capabilities.thermostatMode, + capabilities.thermostatHeatingSetpoint, + capabilities.fanOscillationMode, + capabilities.fanMode + }, + + capability_handlers = { + [capabilities.thermostatMode.ID] = { + [capabilities.thermostatMode.commands.setThermostatMode.NAME] = handle_thermostat_mode, + }, + [capabilities.thermostatHeatingSetpoint.ID] = { + [capabilities.thermostatHeatingSetpoint.commands.setHeatingSetpoint.NAME] = handle_heating_setpoint, + }, + [capabilities.fanOscillationMode.ID] = { + [capabilities.fanOscillationMode.commands.setFanOscillationMode.NAME] = handle_fan_oscillation_mode, + }, + [capabilities.fanMode.ID] = { + [capabilities.fanMode.commands.setFanMode.NAME] = handle_fan_mode, + }, + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = handle_refresh, + }, + }, + + zigbee_handlers = { + attr = { + [aqara.CLUSTER_ID] = { + [aqara.ATTR_AC_CODE] = ac_code_attr_handler, + }, + }, + }, + health_check = false, + lifecycle_handlers = { + init = device_init, + added = device_added, + infoChanged = info_changed, + }, +} + +defaults.register_for_default_handlers( + aqara_bathroom_heater_driver_template, + aqara_bathroom_heater_driver_template.supported_capabilities, + { native_capability_cmds_enabled = true, native_capability_attrs_enabled = true } +) + +local aqara_bathroom_heater_driver = ZigbeeDriver("aqara-bathroom-heater-t1", aqara_bathroom_heater_driver_template) +aqara_bathroom_heater_driver:run() diff --git a/drivers/Aqara/aqara-bath-heater/src/test/test_aqara_bath_heater.lua b/drivers/Aqara/aqara-bath-heater/src/test/test_aqara_bath_heater.lua new file mode 100644 index 0000000000..17e85b716b --- /dev/null +++ b/drivers/Aqara/aqara-bath-heater/src/test/test_aqara_bath_heater.lua @@ -0,0 +1,852 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Consolidated test cases for the Aqara Bath Heater T1 SmartThings Edge driver. +-- +-- IMPORTANT: The test framework fires an "init" lifecycle event before every +-- test (the driver must complete its startup sequence before the test body +-- can run). Because `device_init` emits multiple capability events +-- (supported*, range) and issues three Zigbee attribute reads, `test_init` +-- pre-registers those expectations BEFORE calling `add_test_device(...)`, so +-- each individual test body can ignore the init emissions and focus only on +-- its own test-specific expectations. + +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 data_types = require "st.zigbee.data_types" +local clusters = require "st.zigbee.zcl.clusters" + +local OnOff = clusters.OnOff +local Level = clusters.Level +local ColorControl = clusters.ColorControl + +local AQARA_CLUSTER_ID = 0xFCC0 +local AQARA_MFG_CODE = 0x115F +local ATTR_AC_CODE = 0x024F +local ATTR_THERMOSTAT_CTRL_SW = 0x02BE +local ATTR_DND_BEEP = 0x0256 +local ATTR_DND_TIME = 0x0257 +local ATTR_NIGHT_LIGHT = 0x0518 + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("aqara-bath-heater.yml"), + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Aqara", + model = "lumi.bhf_light.acn001", + server_clusters = { 0x0006, 0x0008, 0x0300, 0xFCC0 } + } + } +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.supportedThermostatModes( + { "off", "heat", "dryair", "cool", "fanonly" }, + { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanOscillationMode.supportedFanOscillationModes( + { "swing", "fixed" }, + { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.supportedFanModes( + { "low", "medium", "high" }, + { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatHeatingSetpoint.heatingSetpointRange( + { value = { minimum = 16, maximum = 45, step = 1 }, unit = "C" }))) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, + Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +-- ---------------------------------------------------------------------------- +-- Helpers +-- ---------------------------------------------------------------------------- + +-- Build the 8-byte big-endian raw payload for the Aqara AC-code Uint64 attr. +local function ac_code_bytes(hi32, lo32) + return string.char( + (hi32 >> 24) & 0xFF, + (hi32 >> 16) & 0xFF, + (hi32 >> 8) & 0xFF, + hi32 & 0xFF, + (lo32 >> 24) & 0xFF, + (lo32 >> 16) & 0xFF, + (lo32 >> 8) & 0xFF, + lo32 & 0xFF + ) +end + +local function expect_ac_code_send(hi32, lo32) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, + AQARA_CLUSTER_ID, ATTR_AC_CODE, AQARA_MFG_CODE, + data_types.Uint64, ac_code_bytes(hi32, lo32)) }) +end + +local function build_ac_code_report(hi32, lo32) + return zigbee_test_utils.build_attribute_report(mock_device, AQARA_CLUSTER_ID, + { { ATTR_AC_CODE, data_types.Uint64.ID, ac_code_bytes(hi32, lo32) } }) +end + +-- ============================================================================ +-- 1. CAPABILITY COMMAND HANDLERS +-- ============================================================================ +-- +-- switch / switchLevel / colorTemperature are handled by the SmartThings +-- default zigbee handlers (registered via defaults.register_for_default_handlers), +-- so their behavior is covered by the framework's own tests and is not +-- re-tested here. + +-- thermostatMode.setThermostatMode -------------------------------------------- + +test.register_coroutine_test( + "Capability thermostatMode 'off' should send AC off code and emit event", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = { "off" } + } }) + + local hi32 = 0xFFFFFFFF + local lo32 = (0xFFFFFFFF & 0x0FFFFFFF) | (0x0 << 28) + lo32 = (lo32 & 0xF0FFFFFF) | (0xF << 24) + expect_ac_code_send(hi32, lo32) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("off"))) + end +) + +test.register_coroutine_test( + "Capability thermostatMode 'heat' should send AC code and restore defaults", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = { "heat" } + } }) + + -- No prior heatingSetpoint latest state, so the first AC code carries + -- only pwr=1 and mode=0 (no setpoint nibble set). + local hi32 = 0xFFFFFFFF + local lo32 = (0xFFFFFFFF & 0x0FFFFFFF) | (0x1 << 28) + lo32 = (lo32 & 0xF0FFFFFF) | (0x0 << 24) + expect_ac_code_send(hi32, lo32) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("heat"))) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanOscillationMode.fanOscillationMode("swing"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("medium"))) + + local r_hi = 0xFFFFFFFF + local r_lo = 0xFFFFFFFF + r_lo = (r_lo & 0xFF0FFFFF) | (0x1 << 20) + r_lo = (r_lo & 0xFFFCFFFF) | (0x0 << 16) + expect_ac_code_send(r_hi, r_lo) + end +) + +test.register_coroutine_test( + "Capability thermostatMode 'cool' should send AC code (mode=4)", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = { "cool" } + } }) + + local hi32 = 0xFFFFFFFF + local lo32 = (0xFFFFFFFF & 0x0FFFFFFF) | (0x1 << 28) + lo32 = (lo32 & 0xF0FFFFFF) | (0x4 << 24) + expect_ac_code_send(hi32, lo32) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("cool"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanOscillationMode.fanOscillationMode("swing"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("medium"))) + + local r_hi = 0xFFFFFFFF + local r_lo = 0xFFFFFFFF + r_lo = (r_lo & 0xFF0FFFFF) | (0x1 << 20) + r_lo = (r_lo & 0xFFFCFFFF) | (0x0 << 16) + expect_ac_code_send(r_hi, r_lo) + end +) + +test.register_coroutine_test( + "Capability thermostatMode 'dryair' should send AC code (mode=3)", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = { "dryair" } + } }) + + local hi32 = 0xFFFFFFFF + local lo32 = (0xFFFFFFFF & 0x0FFFFFFF) | (0x1 << 28) + lo32 = (lo32 & 0xF0FFFFFF) | (0x3 << 24) + expect_ac_code_send(hi32, lo32) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("dryair"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanOscillationMode.fanOscillationMode("swing"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("medium"))) + + local r_hi = 0xFFFFFFFF + local r_lo = 0xFFFFFFFF + r_lo = (r_lo & 0xFF0FFFFF) | (0x1 << 20) + r_lo = (r_lo & 0xFFFCFFFF) | (0x0 << 16) + expect_ac_code_send(r_hi, r_lo) + end +) + +test.register_coroutine_test( + "Capability thermostatMode 'fanonly' should send AC code (mode=5) and restore only fan", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = { "fanonly" } + } }) + + local hi32 = 0xFFFFFFFF + local lo32 = (0xFFFFFFFF & 0x0FFFFFFF) | (0x1 << 28) + lo32 = (lo32 & 0xF0FFFFFF) | (0x5 << 24) + expect_ac_code_send(hi32, lo32) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("fanonly"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("medium"))) + + local r_hi = 0xFFFFFFFF + local r_lo = (0xFFFFFFFF & 0xFF0FFFFF) | (0x1 << 20) + expect_ac_code_send(r_hi, r_lo) + end +) + +-- NOTE: setThermostatMode("unsupported_mode") would hit the driver's +-- `if not ac then return end` guard, but the capability framework validates +-- `mode` against its enum and rejects unknown values before the handler runs. + +-- thermostatHeatingSetpoint.setHeatingSetpoint -------------------------------- + +test.register_coroutine_test( + "Capability heatingSetpoint 28 in non-heat mode produces no observable output", + function() + -- Outside heat mode the handler only stashes the value in a field; the + -- capability event will fire later via the AC-code attribute report from + -- the device, not from this command path. + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "thermostatHeatingSetpoint", + component = "main", + command = "setHeatingSetpoint", + args = { 28 } + } }) + end +) + +-- NOTE: setHeatingSetpoint(10) / (60) would exercise the clamp(..., 16, 45), +-- but the profile constrains the setpoint to [16, 45] so framework validation +-- rejects those values. The same clamp is exercised via restore_mode_state +-- ("setThermostatMode heat should restore saved setpoint/swing/fan from +-- mode_state") below. + +test.register_coroutine_test( + "Capability heatingSetpoint in heat mode should send AC code with setpoint", + function() + mock_device:set_field("thermostat_mode", "heat") + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "thermostatHeatingSetpoint", + component = "main", + command = "setHeatingSetpoint", + args = { 30 } + } }) + + local hi32 = ((3000 & 0xFFFF) << 16) | (0xFFFFFFFF & 0xFFFF) + local lo32 = 0xFFFFFFFF + expect_ac_code_send(hi32, lo32) + end +) + +-- fanOscillationMode.setFanOscillationMode ------------------------------------ + +test.register_coroutine_test( + "Capability fanOscillationMode 'swing' sends AC code with swing bits=0", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "fanOscillationMode", + component = "main", + command = "setFanOscillationMode", + args = { "swing" } + } }) + + local hi32 = 0xFFFFFFFF + local lo32 = (0xFFFFFFFF & 0xFFFCFFFF) | (0x0 << 16) + expect_ac_code_send(hi32, lo32) + end +) + +test.register_coroutine_test( + "Capability fanOscillationMode 'fixed' sends AC code with swing bits=1", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "fanOscillationMode", + component = "main", + command = "setFanOscillationMode", + args = { "fixed" } + } }) + + local hi32 = 0xFFFFFFFF + local lo32 = (0xFFFFFFFF & 0xFFFCFFFF) | (0x1 << 16) + expect_ac_code_send(hi32, lo32) + end +) + +-- fanMode.setFanMode ---------------------------------------------------------- + +test.register_coroutine_test( + "Capability fanMode 'low' sends AC code with fan bits=0", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "fanMode", + component = "main", + command = "setFanMode", + args = { "low" } + } }) + expect_ac_code_send(0xFFFFFFFF, (0xFFFFFFFF & 0xFF0FFFFF) | (0x0 << 20)) + end +) + +test.register_coroutine_test( + "Capability fanMode 'medium' sends AC code with fan bits=1", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "fanMode", + component = "main", + command = "setFanMode", + args = { "medium" } + } }) + expect_ac_code_send(0xFFFFFFFF, (0xFFFFFFFF & 0xFF0FFFFF) | (0x1 << 20)) + end +) + +test.register_coroutine_test( + "Capability fanMode 'high' sends AC code with fan bits=2", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "fanMode", + component = "main", + command = "setFanMode", + args = { "high" } + } }) + expect_ac_code_send(0xFFFFFFFF, (0xFFFFFFFF & 0xFF0FFFFF) | (0x2 << 20)) + end +) + +-- NOTE: setFanMode("auto") would exercise the `MODE_TO_FAN[fan_mode] or +-- FAN_MID` fallback, but the profile's enabledValues restricts fanMode to +-- {"low","medium","high"}, so "auto" is rejected by framework validation. + +-- refresh --------------------------------------------------------------------- + +test.register_coroutine_test( + "Capability refresh should read OnOff, Level and ColorTemperature", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } }) + + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, + Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + end +) + +-- ============================================================================ +-- 2. ZIGBEE ATTRIBUTE HANDLERS +-- ============================================================================ +-- +-- OnOff / CurrentLevel / ColorTemperatureMireds attribute reports are handled +-- by the SmartThings default zigbee handlers and emit their corresponding +-- capability events automatically, so they are not re-tested here. + +-- Aqara AC code (0xFCC0 / 0x024F) --------------------------------------------- + +test.register_coroutine_test( + "AC code report: heat + medium fan + swing + setpoint 25.00 should emit all events", + function() + local hi32 = (2500 << 16) | 0xFEFF + local lo32 = 0xFFFFFFFF + lo32 = (lo32 & 0x0FFFFFFF) | (0x1 << 28) + lo32 = (lo32 & 0xF0FFFFFF) | (0x0 << 24) + lo32 = (lo32 & 0xFF0FFFFF) | (0x1 << 20) + lo32 = (lo32 & 0xFFFCFFFF) | (0x0 << 16) + + test.socket.zigbee:__queue_receive({ mock_device.id, build_ac_code_report(hi32, lo32) }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 25.0, unit = "C" }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("medium"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanOscillationMode.fanOscillationMode("swing"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("heat"))) + end +) + +test.register_coroutine_test( + "AC code report: pwr=0 (off) should emit thermostatMode 'off'", + function() + local hi32 = 0xFFFFFFFF + local lo32 = 0xFFFFFFFF + lo32 = (lo32 & 0x0FFFFFFF) | (0x0 << 28) + lo32 = (lo32 & 0xFF0FFFFF) | (0x1 << 20) + lo32 = (lo32 & 0xFFFCFFFF) | (0x1 << 16) + + test.socket.zigbee:__queue_receive({ mock_device.id, build_ac_code_report(hi32, lo32) }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("medium"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanOscillationMode.fanOscillationMode("fixed"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("off"))) + end +) + +test.register_coroutine_test( + "AC code report: pwr=0xF (invalid) should skip thermostatMode update", + function() + local hi32 = 0xFFFFFFFF + local lo32 = 0xFFFFFFFF + lo32 = (lo32 & 0xFF0FFFFF) | (0x0 << 20) + lo32 = (lo32 & 0xFFFCFFFF) | (0x0 << 16) + + test.socket.zigbee:__queue_receive({ mock_device.id, build_ac_code_report(hi32, lo32) }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("low"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanOscillationMode.fanOscillationMode("swing"))) + end +) + +test.register_coroutine_test( + "AC code report: fan=2 (high) and mode=4 (cool)", + function() + local hi32 = 0xFFFFFFFF + local lo32 = 0xFFFFFFFF + lo32 = (lo32 & 0x0FFFFFFF) | (0x1 << 28) + lo32 = (lo32 & 0xF0FFFFFF) | (0x4 << 24) + lo32 = (lo32 & 0xFF0FFFFF) | (0x2 << 20) + + test.socket.zigbee:__queue_receive({ mock_device.id, build_ac_code_report(hi32, lo32) }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("high"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("cool"))) + end +) + +test.register_coroutine_test( + "AC code report: unknown mode bits should fall back to 'heat'", + function() + local hi32 = 0xFFFFFFFF + local lo32 = 0xFFFFFFFF + lo32 = (lo32 & 0x0FFFFFFF) | (0x1 << 28) + lo32 = (lo32 & 0xF0FFFFFF) | (0x7 << 24) + lo32 = (lo32 & 0xFF0FFFFF) | (0x1 << 20) + + test.socket.zigbee:__queue_receive({ mock_device.id, build_ac_code_report(hi32, lo32) }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("medium"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("heat"))) + end +) + +test.register_coroutine_test( + "AC code report: fanonly mode (5)", + function() + local hi32 = 0xFFFFFFFF + local lo32 = 0xFFFFFFFF + lo32 = (lo32 & 0x0FFFFFFF) | (0x1 << 28) + lo32 = (lo32 & 0xF0FFFFFF) | (0x5 << 24) + lo32 = (lo32 & 0xFF0FFFFF) | (0x1 << 20) + + test.socket.zigbee:__queue_receive({ mock_device.id, build_ac_code_report(hi32, lo32) }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("medium"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("fanonly"))) + end +) + +test.register_coroutine_test( + "AC code report: pending_on_mode + incoming off should NOT overwrite mode", + function() + mock_device:set_field("pending_on_mode", "heat") + + local hi32 = 0xFFFFFFFF + local lo32 = 0xFFFFFFFF + lo32 = (lo32 & 0x0FFFFFFF) | (0x0 << 28) -- pwr=0 (off) + lo32 = (lo32 & 0xFF0FFFFF) | (0x1 << 20) -- fan=1 (medium) + lo32 = (lo32 & 0xFFFCFFFF) | (0x0 << 16) -- swing bits=00 (swing) + + test.socket.zigbee:__queue_receive({ mock_device.id, build_ac_code_report(hi32, lo32) }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("medium"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanOscillationMode.fanOscillationMode("swing"))) + -- thermostatMode NOT emitted: pwr=0 → st_mode="off", but pending_on_mode="heat" + -- causes early return from the handler. + end +) + +test.register_coroutine_test( + "AC code report: setpoint 0xFFFF (invalid marker) should skip setpoint emit", + function() + local hi32 = (0xFFFF << 16) | 0xFEFF + local lo32 = 0xFFFFFFFF + lo32 = (lo32 & 0x0FFFFFFF) | (0x1 << 28) + lo32 = (lo32 & 0xF0FFFFFF) | (0x0 << 24) + lo32 = (lo32 & 0xFF0FFFFF) | (0x1 << 20) + + test.socket.zigbee:__queue_receive({ mock_device.id, build_ac_code_report(hi32, lo32) }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("medium"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("heat"))) + end +) + +test.register_coroutine_test( + "AC code report: invalid frame (b15_8 < 0xFE) should skip setpoint emit", + function() + -- hi32 carries a valid-looking setpoint raw (2800 = 28.00°C); the "invalid" + -- frame marker lives in lo32 bits 15-8 (b15_8). Clearing them to 0x00 + -- makes hi_valid = false, so the setpoint emit MUST be suppressed even + -- though setpoint_raw != 0xFFFF. + local hi32 = (2800 << 16) | 0x0000 + local lo32 = 0xFFFFFFFF + lo32 = (lo32 & 0x0FFFFFFF) | (0x1 << 28) -- pwr=1 (on) + lo32 = (lo32 & 0xF0FFFFFF) | (0x0 << 24) -- mode=0 (heat) + lo32 = (lo32 & 0xFF0FFFFF) | (0x1 << 20) -- fan=1 (medium) + lo32 = lo32 & 0xFFFF00FF -- b15_8 = 0x00 → frame invalid + + test.socket.zigbee:__queue_receive({ mock_device.id, build_ac_code_report(hi32, lo32) }) + + -- Setpoint NOT emitted because hi_valid=false. + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("medium"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("heat"))) + end +) + +-- ============================================================================ +-- 3. LIFECYCLE HANDLERS +-- ============================================================================ + +test.register_coroutine_test( + "Lifecycle init should emit supported* / range events and read attributes", + function() + -- This test only verifies the emissions fired by the automatic init; + -- those expectations are already registered in test_init(). + end +) + +test.register_coroutine_test( + "Lifecycle added should emit defaults (setpoint=25, fan=medium, swing=swing) + AC code", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 25, unit = "C" }))) + + local hi32 = ((2500 & 0xFFFF) << 16) | (0xFFFFFFFF & 0xFFFF) + expect_ac_code_send(hi32, 0xFFFFFFFF) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("medium"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanOscillationMode.fanOscillationMode("swing"))) + end +) + +-- info_changed helpers -------------------------------------------------------- + +local function expected_night_light_value(start_h, end_h, enabled) + local start_time = (start_h * 60) & 0xFFF + local end_time = (end_h * 60) & 0xFFF + local on_val = (end_time << 12) | start_time + return enabled and on_val or (on_val + 1) +end + +test.register_coroutine_test( + "infoChanged nightLightMode on (first init) should send night-light + DND-beep + DND-time", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { + ["stse.nightLightMode"] = true, + ["stse.nightLightStartTime"] = 21, + ["stse.nightLightEndTime"] = 9, + ["stse.muteBeep"] = false + } + })) + + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, + AQARA_CLUSTER_ID, ATTR_NIGHT_LIGHT, AQARA_MFG_CODE, + data_types.Uint32, expected_night_light_value(21, 9, true)) }) + + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, + AQARA_CLUSTER_ID, ATTR_DND_BEEP, AQARA_MFG_CODE, + data_types.Uint8, 0) }) + + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, + AQARA_CLUSTER_ID, ATTR_DND_TIME, AQARA_MFG_CODE, + data_types.Uint32, 0x00120012) }) + end +) + +test.register_coroutine_test( + "infoChanged nightLightMode off should send night-light disabled value", + function() + mock_device:set_field("inited", true) + + -- The profile default is nightLightMode=false, so passing `false` again + -- would produce old==new and the handler's `mode_changed` branch would + -- not fire. First flip it ON (consuming the resulting "enabled" write), + -- then flip it OFF — which is the transition this test actually covers. + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { + ["stse.nightLightMode"] = true, + ["stse.nightLightStartTime"] = 21, + ["stse.nightLightEndTime"] = 9 + } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, + AQARA_CLUSTER_ID, ATTR_NIGHT_LIGHT, AQARA_MFG_CODE, + data_types.Uint32, expected_night_light_value(21, 9, true)) }) + + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { + ["stse.nightLightMode"] = false, + ["stse.nightLightStartTime"] = 21, + ["stse.nightLightEndTime"] = 9 + } + })) + + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, + AQARA_CLUSTER_ID, ATTR_NIGHT_LIGHT, AQARA_MFG_CODE, + data_types.Uint32, expected_night_light_value(21, 9, false)) }) + end +) + +test.register_coroutine_test( + "infoChanged night-light time changed while enabled should resend night-light", + function() + mock_device:set_field("inited", true) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { + ["stse.nightLightMode"] = true, + ["stse.nightLightStartTime"] = 22, + ["stse.nightLightEndTime"] = 8 + } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, + AQARA_CLUSTER_ID, ATTR_NIGHT_LIGHT, AQARA_MFG_CODE, + data_types.Uint32, expected_night_light_value(22, 8, true)) }) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { + ["stse.nightLightMode"] = true, + ["stse.nightLightStartTime"] = 21, + ["stse.nightLightEndTime"] = 9 + } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, + AQARA_CLUSTER_ID, ATTR_NIGHT_LIGHT, AQARA_MFG_CODE, + data_types.Uint32, expected_night_light_value(21, 9, true)) }) + end +) + +test.register_coroutine_test( + "infoChanged muteBeep toggled on should write DND-beep=1", + function() + mock_device:set_field("inited", true) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { + ["stse.muteBeep"] = true + } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, + AQARA_CLUSTER_ID, ATTR_DND_BEEP, AQARA_MFG_CODE, + data_types.Uint8, 1) }) + end +) + +test.register_coroutine_test( + "infoChanged thermostatCtrl toggled off should write 0", + function() + mock_device:set_field("inited", true) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { + ["stse.thermostatCtrl"] = false, + } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, + AQARA_CLUSTER_ID, ATTR_THERMOSTAT_CTRL_SW, AQARA_MFG_CODE, + data_types.Uint8, 0) }) + end +) + +test.register_coroutine_test( + "infoChanged when not yet initialized should trigger initialization", + function() + -- mock_device:set_field("inited", "") + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { + ["stse.muteBeep"] = true + } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, + AQARA_CLUSTER_ID, ATTR_DND_BEEP, AQARA_MFG_CODE, + data_types.Uint8, 1) }) + end +) + +-- ============================================================================ +-- 4. EDGE CASES / BRANCH COVERAGE +-- ============================================================================ + +test.register_coroutine_test( + "setHeatingSetpoint while mode is 'off' produces no observable output", + function() + mock_device:set_field("thermostat_mode", "off") + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "thermostatHeatingSetpoint", + component = "main", + command = "setHeatingSetpoint", + args = { 22 } + } }) + end +) + +test.register_coroutine_test( + "setFanOscillationMode while mode is 'fanonly' still sends AC code", + function() + mock_device:set_field("thermostat_mode", "fanonly") + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "fanOscillationMode", + component = "main", + command = "setFanOscillationMode", + args = { "fixed" } + } }) + expect_ac_code_send(0xFFFFFFFF, (0xFFFFFFFF & 0xFFFCFFFF) | (0x1 << 16)) + end +) + +test.register_coroutine_test( + "setThermostatMode heat should restore saved swing/fan from mode_state", + function() + mock_device:set_field("mode_state.heat.swing", "fixed") + mock_device:set_field("mode_state.heat.fan_mode", "high") + + test.socket.capability:__queue_receive({ mock_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = { "heat" } + } }) + + local hi32 = 0xFFFFFFFF + local lo32 = (0xFFFFFFFF & 0x0FFFFFFF) | (0x1 << 28) + lo32 = (lo32 & 0xF0FFFFFF) | (0x0 << 24) + expect_ac_code_send(hi32, lo32) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.thermostatMode("heat"))) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanOscillationMode.fanOscillationMode("fixed"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fanMode.fanMode("high"))) + + local r_hi = 0xFFFFFFFF + local r_lo = 0xFFFFFFFF + r_lo = (r_lo & 0xFF0FFFFF) | (0x2 << 20) + r_lo = (r_lo & 0xFFFCFFFF) | (0x1 << 16) + expect_ac_code_send(r_hi, r_lo) + end +) + +-- NOTE: The `if args.old_st_store.preferences == nil then return end` early +-- return in info_changed is defensive code that only fires on a brand-new +-- device. It cannot be reliably reproduced in the SmartThings integration +-- test framework because the mock_device is always built from the profile's +-- preference section (st_store.preferences is always a table), and manually +-- constructing a lifecycle event with preferences=nil is normalized away by +-- the lifecycle dispatcher before the handler sees it. + +test.run_registered_tests() From 8c953ac2d0662dd02676a850e683e2878b120619 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Fri, 22 May 2026 23:17:42 +0800 Subject: [PATCH 372/538] add laisiao_bathheate_DG60GCM-04-2904W (#2970) --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ drivers/SmartThings/zigbee-switch/src/laisiao/can_handle.lua | 1 + drivers/SmartThings/zigbee-switch/src/laisiao/init.lua | 2 -- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index db44b09caa..3219c46d65 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2404,6 +2404,11 @@ zigbeeManufacturer: manufacturer: LAISIAO model: yuba deviceProfileName: switch-smart-bath-heater-laisiao + - id: "LAISIAO/DG60GCM-04-2904W" + deviceLabel: Laisiao Bathroom Heater + manufacturer: LAISIAO + model: DG60GCM-04-2904W + deviceProfileName: switch-smart-bath-heater-laisiao # NodOn - id: "NodOn/SIN-4-1-20" deviceLabel: Zigbee Multifunction Relay Switch diff --git a/drivers/SmartThings/zigbee-switch/src/laisiao/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/laisiao/can_handle.lua index 7ed921844b..51a344c104 100644 --- a/drivers/SmartThings/zigbee-switch/src/laisiao/can_handle.lua +++ b/drivers/SmartThings/zigbee-switch/src/laisiao/can_handle.lua @@ -4,6 +4,7 @@ return function(opts, driver, device, ...) local FINGERPRINTS = { { mfr = "LAISIAO", model = "yuba" }, + { mfr = "LAISIAO", model = "DG60GCM-04-2904W" }, } for _, fingerprint in ipairs(FINGERPRINTS) do diff --git a/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua b/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua index 22e5791b0e..8fad192232 100755 --- a/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua @@ -5,8 +5,6 @@ local capabilities = require "st.capabilities" local zcl_clusters = require "st.zigbee.zcl.clusters" local configurations = require "configurations" - - local function component_to_endpoint(device, component_id) if component_id == "main" then return device.fingerprinted_endpoint_id From 1d6aae9a929a45805747b6e5d04ec6cbf5c38f5f Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Fri, 22 May 2026 14:01:26 -0500 Subject: [PATCH 373/538] WWSTCERT-11645 Govee Uplighter Floor Lamp with Nebula Effect (#2994) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index ede868ac0f..d5af3539ac 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -848,6 +848,11 @@ matterManufacturer: vendorId: 0x1387 productId: 0x1270 deviceProfileName: light-color-level + - id: "4999/24755" + deviceLabel: Govee Uplighter Floor Lamp with Nebula Effect + vendorId: 0x1387 + productId: 0x60B3 + deviceProfileName: light-color-level # Hager - id: "4741/8" deviceLabel: Hager matter 2 buttons (battery) From 1e2b9e89ee356c9ed72098ba3f9659e7b756a1c9 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Fri, 22 May 2026 14:02:47 -0500 Subject: [PATCH 374/538] WWSTCERT-11863 Linkind Light Stick T19 (#2996) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index d5af3539ac..7b298446da 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -206,6 +206,11 @@ matterManufacturer: vendorId: 0x1396 productId: 0x1045 deviceProfileName: light-color-level + - id: "5014/4578" + deviceLabel: Linkind Light Stick T19 + vendorId: 0x1396 + productId: 0x11E2 + deviceProfileName: light-color-level #Bosch Smart Home - id: "4617/12310" deviceLabel: Plug Compact [M] From dbbcc5ff1f345e9ba6cff55e3de37e5c628d19f2 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 26 May 2026 13:35:20 -0500 Subject: [PATCH 375/538] Matter Switch: Register ColorTemperatureMireds native handler (#2990) --- .../switch_handlers/attribute_handlers.lua | 3 + .../test/test_matter_on_off_parent_child.lua | 79 ++++++++++--------- .../src/test/test_matter_switch.lua | 32 ++++++++ 3 files changed, 75 insertions(+), 39 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index 838f932d86..45694bc613 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -106,6 +106,9 @@ function AttributeHandlers.current_saturation_handler(driver, device, ib, respon end function AttributeHandlers.color_temperature_mireds_handler(driver, device, ib, response) + if type(device.register_native_capability_attr_handler) == "function" then + device:register_native_capability_attr_handler("colorTemperature", "colorTemperature") + end local temp_in_mired = ib.data.value if temp_in_mired == nil then return diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua index ff8238a52c..608112d61c 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua @@ -294,44 +294,8 @@ test.register_message_test( ) test.register_message_test( - "Extended Color Child: 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, extended_color_ep_id, 15091) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, extended_color_ep_id, 21547) - } - }, - { - channel = "capability", - direction = "send", - message = mock_children[extended_color_ep_id]:generate_test_message("main", capabilities.colorControl.hue(50)) - }, - { - channel = "capability", - direction = "send", - message = mock_children[extended_color_ep_id]:generate_test_message("main", capabilities.colorControl.saturation(72)) - } - }, - { - min_api_version = 17 - } -) - -test.register_message_test( - "Extended Color Child: colorTemperatureRange, setColorTemperature, stepColorTemperatureByPercent handled appropriately", + "Extended Color Child: SetColor command should be handled correctly", { - -- setColorTemperature before a color temperature range is set { channel = "capability", direction = "receive", @@ -356,6 +320,12 @@ test.register_message_test( clusters.ColorControl.server.commands.MoveToColor:build_test_command_response(mock_device, extended_color_ep_id) } }, + } +) + +test.register_message_test( + "Extended Color Child: X and Y color values should report hue and saturation once both have been received", + { { channel = "matter", direction = "receive", @@ -381,8 +351,16 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_children[extended_color_ep_id]:generate_test_message("main", capabilities.colorControl.saturation(72)) - }, + } + }, + { + min_api_version = 17 + } +) +test.register_message_test( + "Extended Color Child: colorTemperatureRange, setColorTemperature, colorTemperature, stepColorTemperatureByPercent handled appropriately", + { -- colorTemperatureRange testing { channel = "matter", @@ -432,6 +410,29 @@ test.register_message_test( } }, -- 555 is expected since it is re-bounded by the given range + -- colorTemperature testing + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, extended_color_ep_id, 555) + } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorTemperature", capability_attr_id = "colorTemperature" } + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[extended_color_ep_id]:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) + }, + -- stepColorTemperatureByPercent testing { channel = "capability", @@ -459,7 +460,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 17 } ) 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 c3e472268f..e68f1d0101 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -268,6 +268,14 @@ test.register_message_test( clusters.ColorControl.server.commands.MoveToColorTemperature:build_test_command_response(mock_device, 1) } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorTemperature", capability_attr_id = "colorTemperature" } + } + }, { channel = "matter", direction = "receive", @@ -297,6 +305,14 @@ test.register_message_test( mock_device.id, clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 0) } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorTemperature", capability_attr_id = "colorTemperature" } + } } }, { @@ -396,6 +412,14 @@ test.register_message_test( clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 160) } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorTemperature", capability_attr_id = "colorTemperature" } + } + }, { channel = "capability", direction = "send", @@ -409,6 +433,14 @@ test.register_message_test( clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 370) } }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorTemperature", capability_attr_id = "colorTemperature" } + } + }, { channel = "capability", direction = "send", From da0a68c7171af766a483b2fcd8ca751d76d099a0 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 26 May 2026 14:31:19 -0500 Subject: [PATCH 376/538] WWSTCERT-11879 Linkind Smart Ceiling Light (#2999) * WWSTCERT-11879 Linkind Smart Ceiling Light * WWSTCERT-11879 Linkind Smart Ceiling Light --- .../SmartThings/matter-switch/fingerprints.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 7b298446da..0578d80d23 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -211,6 +211,21 @@ matterManufacturer: vendorId: 0x1396 productId: 0x11E2 deviceProfileName: light-color-level + - id: "5014/4274" + deviceLabel: Linkind Smart Ceiling Light + vendorId: 0x1396 + productId: 0x10B2 + deviceProfileName: light-color-level + - id: "5014/4642" + deviceLabel: Linkind Smart Permanent Outdoor Lights + vendorId: 0x1396 + productId: 0x1222 + deviceProfileName: light-color-level + - id: "5014/4629" + deviceLabel: Smart Outdoor String Lights + vendorId: 0x1396 + productId: 0x1215 + deviceProfileName: light-color-level #Bosch Smart Home - id: "4617/12310" deviceLabel: Plug Compact [M] From 26097e6584d03f9d39f6d2e03d2eedd6323a8cfc Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 26 May 2026 14:41:17 -0500 Subject: [PATCH 377/538] WWSTCERT-11914 Dreame NAVO Smart Lock E10 (#3001) --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 +++++ .../matter-lock/src/new-matter-lock/fingerprints.lua | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 14db5d755e..796cefd590 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -167,6 +167,11 @@ matterManufacturer: vendorId: 0x152C productId: 0x9500 deviceProfileName: lock + - id: "5420/38145" + deviceLabel: Dreame NAVO Smart Lock E10 + vendorId: 0x152C + productId: 0x9501 + deviceProfileName: lock matterGeneric: - id: "matter/door-lock" deviceLabel: Matter Door Lock diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index 4518189b64..038aa25d51 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -36,7 +36,8 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x1236, 0xa538}, -- Schlage Sense Pro {0x1236, 0x3800}, -- Schlage {0x1236, 0xA738}, -- Schlage - {0x152C, 0x9500} -- Dreame NAVO Smart Lock A10 + {0x152C, 0x9500}, -- Dreame NAVO Smart Lock A10 + {0x152C, 0x9501} -- Dreame NAVO Smart Lock E10 } return NEW_MATTER_LOCK_PRODUCTS From 50273888bc4a34961f9a7402d575f5ba79349350 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 26 May 2026 14:43:19 -0500 Subject: [PATCH 378/538] WWSTCERT-11858 Tapo smart Power strip (#3000) * WWSTCERT-11858 Tapo smart Power strip * Apply suggestion from @hcarter-775 Co-authored-by: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> --------- Co-authored-by: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> --- drivers/SmartThings/matter-switch/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 0578d80d23..ece0bcaf72 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -4138,6 +4138,12 @@ matterManufacturer: vendorId: 0x1344 productId: 0x041E deviceProfileName: matter-bridge +#Tapo + - id: "5010/269" + deviceLabel: Tapo Smart Power Strip + vendorId: 0x1392 + productId: 0x010D + deviceProfileName: plug-binary matterGeneric: From 8d9f3a70f8a908400dcdf726893cba5d2c6a5b34 Mon Sep 17 00:00:00 2001 From: Script0803 <116477970+script0803@users.noreply.github.com> Date: Wed, 27 May 2026 03:55:38 +0800 Subject: [PATCH 379/538] Add BITUO TECHNIK OEM devices for the Zemismart manufacturer (#2986) * Add BituoTechnik's OEM device --Zemismart * Add Zemismart device test file * Adjust the format --- .../zigbee-power-meter/fingerprints.yml | 25 ++ .../src/bituo/fingerprints.lua | 7 +- .../zigbee-power-meter/src/bituo/init.lua | 4 +- .../test/test_zigbee_power_meter_1p_ZM.lua | 211 +++++++++++ .../test/test_zigbee_power_meter_2p_ZM.lua | 284 ++++++++++++++ .../test/test_zigbee_power_meter_3p_ZM.lua | 358 ++++++++++++++++++ 6 files changed, 886 insertions(+), 3 deletions(-) create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p_ZM.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_2p_ZM.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_3p_ZM.lua diff --git a/drivers/SmartThings/zigbee-power-meter/fingerprints.yml b/drivers/SmartThings/zigbee-power-meter/fingerprints.yml index 15d3f199ec..2948c0a381 100644 --- a/drivers/SmartThings/zigbee-power-meter/fingerprints.yml +++ b/drivers/SmartThings/zigbee-power-meter/fingerprints.yml @@ -83,6 +83,31 @@ zigbeeManufacturer: manufacturer: BITUO TECHNIK model: "SDM01B" deviceProfileName: power-meter-1p + - id: "Zemismart/SPM01-1Z2" + deviceLabel: Energy Monitor 1PN + manufacturer: Zemismart + model: "SPM01-1Z2" + deviceProfileName: power-meter-1p + - id: "Zemismart/SDM02-2Z1" + deviceLabel: Energy Monitor 2PN + manufacturer: Zemismart + model: "SDM02-2Z1" + deviceProfileName: power-meter-2p + - id: "Zemismart/SPM02-3Z3" + deviceLabel: Energy Monitor 3PN + manufacturer: Zemismart + model: "SPM02-3Z3" + deviceProfileName: power-meter-3p + - id: "Zemismart/SDM01-3Z1" + deviceLabel: Energy Monitor 3PN + manufacturer: Zemismart + model: "SDM01-3Z1" + deviceProfileName: power-meter-3p + - id: "Zemismart/SDM01-1Z1" + deviceLabel: Energy Monitor 1PN + manufacturer: Zemismart + model: "SDM01-1Z1" + deviceProfileName: power-meter-1p zigbeeGeneric: - id: "genericMeter" deviceLabel: Zigbee Meter diff --git a/drivers/SmartThings/zigbee-power-meter/src/bituo/fingerprints.lua b/drivers/SmartThings/zigbee-power-meter/src/bituo/fingerprints.lua index 3909881854..4f4a659189 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/bituo/fingerprints.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/bituo/fingerprints.lua @@ -9,7 +9,12 @@ local ZIGBEE_POWER_METER_FINGERPRINTS = { { mfr = "BITUO TECHNIK", model = "SPM02-E0" }, { mfr = "BITUO TECHNIK", model = "SPM02X" }, { mfr = "BITUO TECHNIK", model = "SDM01W" }, - { mfr = "BITUO TECHNIK", model = "SDM01B" } + { mfr = "BITUO TECHNIK", model = "SDM01B" }, + { mfr = "Zemismart", model = "SPM01-1Z2" }, + { mfr = "Zemismart", model = "SPM02-3Z3" }, + { mfr = "Zemismart", model = "SDM01-3Z1" }, + { mfr = "Zemismart", model = "SDM01-1Z1" }, + { mfr = "Zemismart", model = "SDM02-2Z1" } } 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 index 4bf5dbd359..93788ddfb6 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/bituo/init.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/bituo/init.lua @@ -178,13 +178,13 @@ local device_init = function(self, device) 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 + if string.find(device:get_model(), "SDM02") or string.find(device:get_model(), "SPM02") or string.find(device:get_model(), "SDM01W") or string.find(device:get_model(), "SDM01-3Z1", 1, true) 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 + if string.find(device:get_model(), "SPM02") or string.find(device:get_model(), "SDM01W") or string.find(device:get_model(), "SDM01-3Z1", 1, true) then for _, attribute in ipairs(PHASE_C_CONFIGURATION) do device:add_configured_attribute(attribute) device:add_monitored_attribute(attribute) diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p_ZM.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p_ZM.lua new file mode 100644 index 0000000000..ef82322125 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p_ZM.lua @@ -0,0 +1,211 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +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 = "Zemismart", + model = "SPM01-1Z2", + 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_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() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_2p_ZM.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_2p_ZM.lua new file mode 100644 index 0000000000..5b959e7c61 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_2p_ZM.lua @@ -0,0 +1,284 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +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 = "Zemismart", + model = "SDM02-2Z1", + 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() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_3p_ZM.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_3p_ZM.lua new file mode 100644 index 0000000000..c4d24f35ba --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_3p_ZM.lua @@ -0,0 +1,358 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +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 = "Zemismart", + model = "SDM01-3Z1", + 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() From bb23ee7f2c46a771e6411f53ab93ebf1669ae93c Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 26 May 2026 14:58:51 -0500 Subject: [PATCH 380/538] min delta should be 0.0, use total if negative (#2993) --- drivers/SmartThings/matter-switch/src/switch_utils/utils.lua | 3 ++- .../matter-switch/src/test/test_electrical_sensor_set.lua | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 93936e6093..8e21c8baf8 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -402,12 +402,13 @@ function utils.report_power_consumption_to_st_energy(device, endpoint_id, total_ 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 + local delta_energy = total_imported_energy_wh - previous_imported_report.energy -- 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, + deltaEnergy = delta_energy >= 0.0 and delta_energy or total_imported_energy_wh, -- clarifying assumption: a negative delta means the meter was reset energy = total_imported_energy_wh })) end 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 index f7a0ae32bc..dae6b09a6c 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -612,7 +612,7 @@ test.register_coroutine_test( 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, + deltaEnergy = 19.0, energy = 19.0 })) ) From dd8ec73b067b1c246d5e47cc1784cdfd8c0ab673 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 28 May 2026 09:58:52 -0500 Subject: [PATCH 381/538] WWSTCERT-11891: Linkind Smart Ceiling Light (#3004) --- drivers/SmartThings/matter-switch/fingerprints.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index ece0bcaf72..c54d5492d2 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -185,7 +185,7 @@ matterManufacturer: deviceLabel: Linkind Smart Ceiling Light vendorId: 0x1396 productId: 0x10B1 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5014/4164" deviceLabel: Linkind Smart Light Bulb vendorId: 0x1396 From dd871eddd20bb9ce4a92fcdc36f429823536c53b Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 28 May 2026 10:50:28 -0500 Subject: [PATCH 382/538] WWSTCERT-11941 OSRAM PLANON SLIM (#3005) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index c54d5492d2..592d62b436 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -2070,6 +2070,11 @@ matterManufacturer: vendorId: 0x1189 productId: 0x0892 deviceProfileName: switch-binary + - id: "4489/4097" + deviceLabel: OSRAM PLANON SLIM + vendorId: 0x1189 + productId: 0x1001 + deviceProfileName: light-color-level #Shelly - id: "5264/1" deviceLabel: Shelly Plug S MTR Gen3 From 2f28b5a60fa03f35c640bd85133ebf5f6f14f078 Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Fri, 29 May 2026 11:15:15 -0500 Subject: [PATCH 383/538] Matter Camera: Prevent duplicate zones in triggeredZones report --- .../camera/camera_handlers/event_handlers.lua | 34 +++++++++++---- .../src/test/test_matter_camera.lua | 41 +++++++++++++++++++ 2 files changed, 67 insertions(+), 8 deletions(-) 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 index 02b63bb37f..8549b02d18 100644 --- 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 @@ -3,14 +3,23 @@ local camera_fields = require "sub_drivers.camera.camera_utils.fields" local capabilities = require "st.capabilities" -local switch_utils = require "switch_utils.utils" local CameraEventHandlers = {} +local function has_triggered_zone(triggered_zones, zone_id) + for _, zone in ipairs(triggered_zones or {}) do + if zone.zoneId == zone_id then + return true + end + end + return false +end + 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}) + local zone_id = ib.data.elements.zone.value + if not has_triggered_zone(triggered_zones, zone_id) then + table.insert(triggered_zones, { zoneId = zone_id }) device:set_field(camera_fields.TRIGGERED_ZONES, triggered_zones) device:emit_event_for_endpoint(ib, capabilities.zoneManagement.triggeredZones(triggered_zones)) end @@ -18,13 +27,22 @@ 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)) + local zone_id = ib.data.elements.zone.value + local updated_triggered_zones = {} + local zone_removed = false + + for _, zone in ipairs(triggered_zones) do + if zone.zoneId ~= zone_id then + table.insert(updated_triggered_zones, zone) + else + zone_removed = true end end + + if zone_removed then + device:set_field(camera_fields.TRIGGERED_ZONES, updated_triggered_zones) + device:emit_event_for_endpoint(ib, capabilities.zoneManagement.triggeredZones(updated_triggered_zones)) + end end return CameraEventHandlers diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 9198dc253f..d443bdc573 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -1471,6 +1471,47 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Duplicate ZoneTriggered events should not duplicate triggeredZones state", + 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 = 2, + reason = clusters.ZoneManagement.types.ZoneEventTriggeredReasonEnum.MOTION + }) + }) + + 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({})) + ) + end, + { + min_api_version = 17 + } +) + test.register_coroutine_test( "Button events should generate appropriate events", function() From a709fa40973a48b7c81959ff5ac270f43a2d4bd5 Mon Sep 17 00:00:00 2001 From: Fei Date: Tue, 2 Jun 2026 16:19:28 +0800 Subject: [PATCH 384/538] Update fingerprints.lua for aqara matter lock j200 and u500 --- .../matter-lock/src/new-matter-lock/fingerprints.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index 038aa25d51..375b8c6189 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -7,6 +7,10 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x115f, 0x2807}, -- AQARA, U200 Lite {0x115f, 0x2804}, -- AQARA, U400 {0x115f, 0x286A}, -- AQARA, U200 US + {0x115f, 0x2805}, -- Aqara Smart Lock J200 Set + {0x115f, 0x280e}, -- AQARA Smart Gate Lock U500 + {0x115f, 0x280f}, -- AQARA Smart Rim Lock U500 + {0x115f, 0x2810}, -- AQARA Smart Glass Door Lock U500 {0x147F, 0x0001}, -- U-tec {0x147F, 0x0007}, -- ULTRALOQ Bolt Pro Smart Matter Door Lock {0x147F, 0x0008}, -- Ultraloq, Bolt Smart Matter Door Lock From 917bdad8335bb48f661869aa48915e924185014f Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:29:12 -0500 Subject: [PATCH 385/538] Zigbee Switch: Add Stateless Step ZLL Refresh (#3006) --- .../src/stateless_handlers/init.lua | 35 ++++++++- .../zigbee-switch/src/switch_utils.lua | 5 -- .../src/test/test_zll_color_temp_bulb.lua | 76 +++++++++++++++++++ .../src/test/test_zll_dimmer_bulb.lua | 27 +++++++ 4 files changed, 136 insertions(+), 7 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua b/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua index 82be544f4a..07adfe0e84 100644 --- a/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua @@ -3,6 +3,7 @@ local capabilities = require "st.capabilities" local st_utils = require "st.utils" +local constants = require "st.zigbee.constants" local clusters = require "st.zigbee.zcl.clusters" local switch_utils = require "switch_utils" @@ -11,19 +12,47 @@ local DEFAULT_MIRED_MAX_BOUND = 370 -- 2700 Kelvin (Mireds are the inverse of Ke local DEFAULT_MIRED_MIN_BOUND = 154 -- 6500 Kelvin (Mireds are the inverse of Kelvin) -- Transition Time: The time that shall be taken to perform the step change, in units of 1/10ths of a second. +-- Specific fields can store custom transition times for stateless capabilities +local SWITCH_LEVEL_STEP_TRANSITION_TIME = "__switch_level_step_transition_time" +local COLOR_TEMP_STEP_TRANSITION_TIME = "__color_temp_step_transition_time" local DEFAULT_STEP_TRANSITION_TIME = 3 -- 0.3 seconds -- Options Mask & Override: Indicates which options are being overridden by the Level/ColorControl cluster commands local OPTIONS_MASK = 0x01 -- default: The `ExecuteIfOff` option is overriden local IGNORE_COMMAND_IF_OFF = 0x00 -- default: the command will not be executed if the device is off +-- Indicates whether a delayed refresh for ZLL devices is in progress, to prevent multiple refreshes in a quick series of step commands +local IS_REFRESH_CALLBACK_QUEUED = "__is_refresh_callback_queued" +-- Stores a timer object, which is required to cancel a timer early +local REFRESH_CALLBACK_TIMER = "__refresh_callback_timer" + +-- Note: These commands' native handlers do not match the driver's ZLL behavior 1-1. +-- Instead, they will queue a 2s timer and read refresh for each command, in all cases. +local function trigger_delayed_refresh_if_zll(device) + if device:get_profile_id() ~= constants.ZLL_PROFILE_ID then + return + end + + -- If a refresh callback is already queued, cancel it and create a new one with the updated time + if device:get_field(IS_REFRESH_CALLBACK_QUEUED) then + device.thread:cancel_timer(device:get_field(REFRESH_CALLBACK_TIMER)) + end + local delay_s = 2 + local new_timer = device.thread:call_with_delay(delay_s, function() + device:refresh() + device:set_field(IS_REFRESH_CALLBACK_QUEUED, nil) + end) + device:set_field(REFRESH_CALLBACK_TIMER, new_timer) + device:set_field(IS_REFRESH_CALLBACK_QUEUED, true) +end + local function step_color_temperature_by_percent_handler(driver, device, cmd) if type(device.register_native_capability_cmd_handler) == "function" then device:register_native_capability_cmd_handler(cmd.capability, cmd.command) end local step_percent_change = cmd.args and cmd.args.stepSize or 0 if step_percent_change == 0 then return end - local transition_time = device:get_field(switch_utils.COLOR_TEMP_STEP_TRANSITION_TIME) or DEFAULT_STEP_TRANSITION_TIME + local transition_time = device:get_field(COLOR_TEMP_STEP_TRANSITION_TIME) or DEFAULT_STEP_TRANSITION_TIME -- Reminder, stepSize > 0 == Kelvin UP == Mireds DOWN. stepSize < 0 == Kelvin DOWN == Mireds UP local step_mode = (step_percent_change > 0) and clusters.ColorControl.types.CcStepMode.DOWN or clusters.ColorControl.types.CcStepMode.UP -- note: the field containing the color temp bounds will be associated with a parent device @@ -37,6 +66,7 @@ local function step_color_temperature_by_percent_handler(driver, device, cmd) end local step_size_in_mireds = st_utils.round((max_mireds - min_mireds) * (math.abs(step_percent_change)/100.0)) device:send(clusters.ColorControl.server.commands.StepColorTemperature(device, step_mode, step_size_in_mireds, transition_time, min_mireds, max_mireds, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF)) + trigger_delayed_refresh_if_zll(device) end local function step_level_handler(driver, device, cmd) @@ -45,9 +75,10 @@ local function step_level_handler(driver, device, cmd) end local step_size = st_utils.round((cmd.args and cmd.args.stepSize or 0)/100.0 * 254) if step_size == 0 then return end - local transition_time = device:get_field(switch_utils.SWITCH_LEVEL_STEP_TRANSITION_TIME) or DEFAULT_STEP_TRANSITION_TIME + local transition_time = device:get_field(SWITCH_LEVEL_STEP_TRANSITION_TIME) or DEFAULT_STEP_TRANSITION_TIME local step_mode = (step_size > 0) and clusters.Level.types.MoveStepMode.UP or clusters.Level.types.MoveStepMode.DOWN device:send(clusters.Level.server.commands.Step(device, step_mode, math.abs(step_size), transition_time, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF)) + trigger_delayed_refresh_if_zll(device) end local stateless_handlers = { diff --git a/drivers/SmartThings/zigbee-switch/src/switch_utils.lua b/drivers/SmartThings/zigbee-switch/src/switch_utils.lua index e974d52474..d30ada0588 100644 --- a/drivers/SmartThings/zigbee-switch/src/switch_utils.lua +++ b/drivers/SmartThings/zigbee-switch/src/switch_utils.lua @@ -8,11 +8,6 @@ local switch_utils = {} switch_utils.MIRED_MAX_BOUND = "__max_mired_bound" switch_utils.MIRED_MIN_BOUND = "__min_mired_bound" --- Fields to store the transition times for the stateless capabilities, --- in case native handler implementations need to be re-configured in the future -switch_utils.SWITCH_LEVEL_STEP_TRANSITION_TIME = "__switch_level_step_transition_time" -switch_utils.COLOR_TEMP_STEP_TRANSITION_TIME = "__color_temp_step_transition_time" - switch_utils.MIREDS_CONVERSION_CONSTANT = 1000000 switch_utils.convert_mired_to_kelvin = function(mired) 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 a02e3978f5..f6842b077f 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 @@ -11,6 +11,12 @@ local OnOff = clusters.OnOff local Level = clusters.Level local ColorControl = clusters.ColorControl +local TRANSITION_TIME = 3 +local OPTIONS_MASK = 0x01 +local IGNORE_COMMAND_IF_OFF = 0x00 +local DEFAULT_MIRED_MIN = 154 +local DEFAULT_MIRED_MAX = 370 + local mock_device = test.mock_device.build_test_zigbee_device( { profile = t_utils.get_profile_definition("color-temp-bulb.yml"), fingerprinted_endpoint_id = 0x01, @@ -279,4 +285,74 @@ test.register_coroutine_test( max_api_version = 19 } ) + +test.register_coroutine_test( + "StatelessColorTemperatureStep stepColorTemperatureByPercent should trigger delayed refresh on ZLL device", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "statelessColorTemperatureStep", component = "main", command = "stepColorTemperatureByPercent", args = { 20 } } }) + mock_device:expect_native_cmd_handler_registration("statelessColorTemperatureStep", "stepColorTemperatureByPercent") + test.socket.zigbee:__expect_send({ + mock_device.id, + ColorControl.server.commands.StepColorTemperature(mock_device, ColorControl.types.CcStepMode.DOWN, 43, TRANSITION_TIME, DEFAULT_MIRED_MIN, DEFAULT_MIRED_MAX, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF) + }) + + test.wait_for_events() + test.mock_time.advance_time(2) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + end, + { + min_api_version = 20 + } +) + +test.register_coroutine_test( + "Rapid StatelessSwitchLevelStep stepLevel commands should cancel and recreate delayed refresh timer", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + + test.socket.capability:__queue_receive({ mock_device.id, { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 25 } } }) + mock_device:expect_native_cmd_handler_registration("statelessSwitchLevelStep", "stepLevel") + test.socket.zigbee:__expect_send({ + mock_device.id, + Level.server.commands.Step(mock_device, Level.types.MoveStepMode.UP, 64, TRANSITION_TIME, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF) + }) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + + test.wait_for_events() + test.mock_time.advance_time(1) + + -- Second step command: cancels timer #1, creates timer #2 + test.socket.capability:__queue_receive({ mock_device.id, { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 25 } } }) + mock_device:expect_native_cmd_handler_registration("statelessSwitchLevelStep", "stepLevel") + test.socket.zigbee:__expect_send({ + mock_device.id, + Level.server.commands.Step(mock_device, Level.types.MoveStepMode.UP, 64, TRANSITION_TIME, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF) + }) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + + test.wait_for_events() + test.mock_time.advance_time(1) + -- now, nothing should happen since the first timer was cancelled and the second timer has not yet reached its 2s delay + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMaxMireds:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTempPhysicalMinMireds:read(mock_device) }) + end, + { + min_api_version = 20 + } +) + test.run_registered_tests() 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 19d91d697a..ec10a66a62 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 @@ -10,6 +10,10 @@ local zigbee_test_utils = require "integration_test.zigbee_test_utils" local OnOff = clusters.OnOff local Level = clusters.Level +local TRANSITION_TIME = 3 +local OPTIONS_MASK = 0x01 +local IGNORE_COMMAND_IF_OFF = 0x00 + local mock_device = test.mock_device.build_test_zigbee_device( { profile = t_utils.get_profile_definition("on-off-level.yml"), fingerprinted_endpoint_id = 0x01, @@ -178,4 +182,27 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "StatelessSwitchLevelStep stepLevel should trigger delayed refresh on ZLL device", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "statelessSwitchLevelStep", component = "main", command = "stepLevel", args = { 25 } } }) + mock_device:expect_native_cmd_handler_registration("statelessSwitchLevelStep", "stepLevel") + test.socket.zigbee:__expect_send({ + mock_device.id, + Level.server.commands.Step(mock_device, Level.types.MoveStepMode.UP, 64, TRANSITION_TIME, OPTIONS_MASK, IGNORE_COMMAND_IF_OFF) + }) + + test.wait_for_events() + test.mock_time.advance_time(2) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + end, + { + min_api_version = 19 + } +) + test.run_registered_tests() From 268c9bed39bc58344b9dba030a9a093eb7eb1437 Mon Sep 17 00:00:00 2001 From: cjswedes Date: Wed, 3 Jun 2026 16:03:54 -0500 Subject: [PATCH 386/538] samsung-audio: guard missing UIC fields to avoid coroutine crashes SamsungAudio refresh and notification handlers assumed UPnP responses always included UIC/response data. Some speakers intermittently return partial XML, so command.volume/getMute/getPlayStatus returned nil or attempted to index ret.handler_res.root.UIC, causing coroutine errors at init.lua:97/101 and command.lua:98. When this happened, refresh processing crashed and users saw stale volume/mute state updates, failed refresh cycles, and unreliable audio notification unmute behavior. This change adds nil-safe response extraction for key polling commands, logs when UIC payloads are missing, and hardens refresh/audio-notification handlers to skip emitting volume/mute events when state data is unavailable instead of crashing. --- .../SmartThings/samsung-audio/src/command.lua | 33 ++++++++++++++----- .../samsung-audio/src/handlers.lua | 2 +- .../SmartThings/samsung-audio/src/init.lua | 17 +++++++--- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/drivers/SmartThings/samsung-audio/src/command.lua b/drivers/SmartThings/samsung-audio/src/command.lua index deca7c4416..d03fe07850 100644 --- a/drivers/SmartThings/samsung-audio/src/command.lua +++ b/drivers/SmartThings/samsung-audio/src/command.lua @@ -34,6 +34,19 @@ local function is_empty(t) return not t or (type(t) == "table" and #t == 0) end +local function get_uic_response(ret, command_name) + local root = ret and ret.handler_res and ret.handler_res.root + local uic = root and root.UIC + local response = uic and uic.response + + if not uic then + log.warn(string.format("Missing UIC data in %s response", tostring(command_name))) + return nil, nil + end + + return uic, response +end + local function tr(s,mappings) return string.gsub(s, "(.)", @@ -94,8 +107,9 @@ function Command.volume(ip) if ip then local url = format_url(ip, "/UIC?cmd=GetVolume") local ret = handle_http_request(ip, url) - if ret then - response_map = { volume = ret.handler_res.root.UIC.response.volume, } + local _, response = get_uic_response(ret, "GetVolume") + if response and response.volume ~= nil then + response_map = { volume = response.volume, } end end return response_map @@ -114,8 +128,9 @@ function Command.set_volume(ip, level) local encoded_str_vol = "/UIC?cmd=%3Cpwron%3Eon%3C/pwron%3E%3Cname%3ESetVolume%3C/name%3E%3Cp%20type=%22dec%22%20name=%22volume%22%20val=%22" .. level .. "%22%3E%3C/p%3E" local url = format_url(ip, encoded_str_vol) local ret = handle_http_request(ip, url) - if ret then - response_map = { volume = ret.handler_res.root.UIC.response.volume, } + local _, response = get_uic_response(ret, "SetVolume") + if response and response.volume ~= nil then + response_map = { volume = response.volume, } end end return response_map @@ -326,8 +341,9 @@ function Command.getMute(ip) if ip then local url = format_url(ip, "/UIC?cmd=GetMute") local ret = handle_http_request(ip, url) - if ret then - response_map = { muted = ret.handler_res.root.UIC.response.mute,} + local _, response = get_uic_response(ret, "GetMute") + if response and response.mute ~= nil then + response_map = { muted = response.mute,} end end return response_map @@ -342,8 +358,9 @@ function Command.getPlayStatus(ip) if ip then local url = format_url(ip, "/UIC?cmd=GetPlayStatus") local ret = handle_http_request(ip, url) - if ret then - response_map = { playstatus = ret.handler_res.root.UIC.response.playstatus,} + local _, response = get_uic_response(ret, "GetPlayStatus") + if response and response.playstatus ~= nil then + response_map = { playstatus = response.playstatus,} end end return response_map diff --git a/drivers/SmartThings/samsung-audio/src/handlers.lua b/drivers/SmartThings/samsung-audio/src/handlers.lua index 6233a86326..65a0027bf9 100644 --- a/drivers/SmartThings/samsung-audio/src/handlers.lua +++ b/drivers/SmartThings/samsung-audio/src/handlers.lua @@ -137,7 +137,7 @@ end function CapabilityHandlers.handle_audio_notification(driver, device, cmd) local ip = device:get_field("ip") local mute_status = command.getMute(ip) - if mute_status.muted ~= "off" then + if mute_status and mute_status.muted ~= nil and mute_status.muted ~= "off" then --unmute before playig notification command.unmute(ip) end diff --git a/drivers/SmartThings/samsung-audio/src/init.lua b/drivers/SmartThings/samsung-audio/src/init.lua index de1958ff25..2e51b8e0b4 100644 --- a/drivers/SmartThings/samsung-audio/src/init.lua +++ b/drivers/SmartThings/samsung-audio/src/init.lua @@ -94,14 +94,23 @@ local function emit_refresh_data_to_server(driver, device, cmd) -- get volume local vol = command.volume(device:get_field("ip")) - device:emit_event(capabilities.audioVolume.volume(tonumber(vol.volume))) + local current_volume = vol and tonumber(vol.volume) + if current_volume ~= nil then + device:emit_event(capabilities.audioVolume.volume(current_volume)) + else + log.warn("Unable to read speaker volume from refresh response") + end -- get mute status local muteStatus = command.getMute(device:get_field("ip")) - if muteStatus.muted ~= "off" then - device:emit_event(capabilities.audioMute.mute.muted()) + if muteStatus and muteStatus.muted ~= nil then + if muteStatus.muted ~= "off" then + device:emit_event(capabilities.audioMute.mute.muted()) + else + device:emit_event(capabilities.audioMute.mute.unmuted()) + end else - device:emit_event(capabilities.audioMute.mute.unmuted()) + log.warn("Unable to read speaker mute state from refresh response") end end From 90b5145085de9dff5fab5ea0afd54960b4f24a06 Mon Sep 17 00:00:00 2001 From: cjswedes Date: Wed, 3 Jun 2026 16:04:27 -0500 Subject: [PATCH 387/538] matter-lock: guard unknown lock state capability emissions Matter lock state reports can include values not present in the legacy LOCK_STATE map. When that happens, lock_state_handler passed nil to device:emit_event, which crashes with attempt to index nil capability_event in st/device.lua and interrupts lock event processing for the device. Add a nil check before emit_event and fall back to capabilities.lock.lock.unknown() for unrecognized lock state values. This prevents coroutine failures while still surfacing a safe lock state to users. Also add an integration test that injects an unknown LockState value and verifies the driver emits lock.unknown instead of crashing. --- drivers/SmartThings/matter-lock/src/init.lua | 8 ++++++- .../matter-lock/src/test/test_matter_lock.lua | 23 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-lock/src/init.lua b/drivers/SmartThings/matter-lock/src/init.lua index 4f58e960e9..09f384ee4e 100755 --- a/drivers/SmartThings/matter-lock/src/init.lua +++ b/drivers/SmartThings/matter-lock/src/init.lua @@ -83,7 +83,13 @@ local function lock_state_handler(driver, device, ib, response) } if ib.data.value ~= nil then - device:emit_event(LOCK_STATE[ib.data.value]) + local event = LOCK_STATE[ib.data.value] + if event ~= nil then + device:emit_event(event) + else + device.log.warn(string.format("Received unknown lock state value (%s), emitting unknown", ib.data.value)) + device:emit_event(attr.unknown()) + end else device:emit_event(LOCK_STATE[LockState.NOT_FULLY_LOCKED]) end diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua index 2477e2745b..9693289951 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua @@ -191,6 +191,29 @@ test.register_message_test( } ) +test.register_message_test( + "Handle unknown LockState value from Matter device.", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.DoorLock.attributes.LockState:build_test_report_data( + mock_device, 10, 0xFF + ), + }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lock.lock.unknown()), + }, + }, + { + min_api_version = 17 + } +) + test.register_message_test( "Handle received BatPercentRemaining from device.", { { From b5b9a97f017eb14344b5f7164d0914f67457c0bc Mon Sep 17 00:00:00 2001 From: cjswedes Date: Wed, 3 Jun 2026 16:05:03 -0500 Subject: [PATCH 388/538] Fix Sinope valve scheduled battery read device context --- drivers/SmartThings/zigbee-valve/src/sinope/init.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-valve/src/sinope/init.lua b/drivers/SmartThings/zigbee-valve/src/sinope/init.lua index ab3786511c..0c6994ad00 100644 --- a/drivers/SmartThings/zigbee-valve/src/sinope/init.lua +++ b/drivers/SmartThings/zigbee-valve/src/sinope/init.lua @@ -12,7 +12,9 @@ local PowerConfiguration = clusters.PowerConfiguration local function device_init(driver, device) battery_defaults.use_battery_voltage_handling(device) -- according to the DTH, this attribute cannot be configured for reporting - device.thread:call_on_schedule(900, function() device:send(PowerConfiguration.attributes.BatteryVoltage:read()) end) + device.thread:call_on_schedule(900, function() + device:send(PowerConfiguration.attributes.BatteryVoltage:read(device)) + end) end local function battery_voltage_handler(driver, device, command) From a63f5fd2ccf1d827be35912ef86ea85045e9ac09 Mon Sep 17 00:00:00 2001 From: cjswedes Date: Wed, 3 Jun 2026 16:07:20 -0500 Subject: [PATCH 389/538] Fix ZLL polling coroutine and stale device callbacks Add zll poll time maintenance to avoid stale timers when devices are removed. Add nil check in poll callback --- .../zigbee-switch/src/zll-polling/init.lua | 65 +++++++++++++------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua b/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua index b464b30acc..814e0f70ce 100644 --- a/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua @@ -5,36 +5,63 @@ local device_lib = require "st.device" local clusters = require "st.zigbee.zcl.clusters" local configurationMap = require "configurations" -local function set_up_zll_polling(driver, device) - local INFREQUENT_POLL_COUNTER = "_infrequent_poll_counter" - local function poll() - local infrequent_counter = device:get_field(INFREQUENT_POLL_COUNTER) or 1 - if infrequent_counter == 12 then - -- do a full refresh once an hour - device:refresh() - infrequent_counter = 0 - else - -- Read On/Off every poll - for _, ep in pairs(device.zigbee_endpoints) do - if device:supports_server_cluster(clusters.OnOff.ID, ep.id) then - device:send(clusters.OnOff.attributes.OnOff:read(device):to_endpoint(ep.id)) - end +local INFREQUENT_POLL_COUNTER = "_infrequent_poll_counter" +local ZLL_POLL_TIMER = "_zll_poll_timer" + +local function do_zll_poll(device) + if device == nil or type(device.get_field) ~= "function" then + return + end + + local infrequent_counter = device:get_field(INFREQUENT_POLL_COUNTER) or 1 + if infrequent_counter == 12 then + -- do a full refresh once an hour + device:refresh() + infrequent_counter = 0 + else + -- Read On/Off every poll + for _, ep in pairs(device.zigbee_endpoints) do + if device:supports_server_cluster(clusters.OnOff.ID, ep.id) then + device:send(clusters.OnOff.attributes.OnOff:read(device):to_endpoint(ep.id)) end - infrequent_counter = infrequent_counter + 1 end - device:set_field(INFREQUENT_POLL_COUNTER, infrequent_counter) + infrequent_counter = infrequent_counter + 1 end + device:set_field(INFREQUENT_POLL_COUNTER, infrequent_counter) +end +local function set_up_zll_polling(driver, device) -- only set this up for non-child devices - if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then - device.thread:call_on_schedule(5 * 60, poll, "zll_polling") + if device.network_type ~= device_lib.NETWORK_TYPE_ZIGBEE then + return + end + + -- should never happen, but defensive check + local existing_timer = device:get_field(ZLL_POLL_TIMER) + if existing_timer ~= nil then + device.thread:cancel_timer(existing_timer) + end + + local timer = device.thread:call_on_schedule(5 * 60, function() + do_zll_poll(device) + end, "zll_polling") + + device:set_field(ZLL_POLL_TIMER, timer) +end + +local function remove_zll_polling(driver, device) + local existing_timer = device:get_field(ZLL_POLL_TIMER) + if existing_timer ~= nil then + device.thread:cancel_timer(existing_timer) + device:set_field(ZLL_POLL_TIMER, nil) end end local ZLL_polling = { NAME = "ZLL Polling", lifecycle_handlers = { - init = configurationMap.reconfig_wrapper(set_up_zll_polling) + init = configurationMap.reconfig_wrapper(set_up_zll_polling), + removed = remove_zll_polling }, can_handle = require("zll-polling.can_handle"), } From 117858def1a7247601c0c9de7f9656f31fff6554 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Mon, 8 Jun 2026 11:50:51 -0500 Subject: [PATCH 390/538] WWSTCERT-12039 Cync Smart Shop Light (#3016) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 592d62b436..e506a8fb6f 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -542,6 +542,11 @@ matterManufacturer: vendorId: 0x1339 productId: 0x0016 deviceProfileName: light-color-level-2000K-7000K + - id: "4921/86" + deviceLabel: Cync Smart Shop Light + vendorId: 0x1339 + productId: 0x0056 + deviceProfileName: light-color-level #Govee - id: "4999/24740" deviceLabel: Govee Square Ceiling Light (12 inch) From efa52a659487cd476f087922506350d7e634843c Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Mon, 8 Jun 2026 13:41:28 -0500 Subject: [PATCH 391/538] Add `videoCapture2` to `detailView` in `deviceConfig` --- drivers/SmartThings/matter-switch/profiles/camera.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/SmartThings/matter-switch/profiles/camera.yml b/drivers/SmartThings/matter-switch/profiles/camera.yml index 7f62319984..1f911a9e10 100644 --- a/drivers/SmartThings/matter-switch/profiles/camera.yml +++ b/drivers/SmartThings/matter-switch/profiles/camera.yml @@ -134,6 +134,9 @@ deviceConfig: - component: main capability: motionSensor version: 1 + - component: main + capability: videoCapture2 + version: 1 automation: conditions: - component: main From 42a6a234ff90394601b667e26be00d4448a49713 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 9 Jun 2026 09:49:52 -0500 Subject: [PATCH 392/538] WWSTCERT-12050 Mini Smart Wi-Fi Plug Energy Monitoring Matter (#3017) * WWSTCERT-12050 Mini Smart Wi-Fi Plug Energy Monitoring Matter * Clean up Tapo identifier in fingerprints.yml Remove commented Tapo identifier from fingerprints.yml --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index e506a8fb6f..8f9351fcd0 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -4154,6 +4154,11 @@ matterManufacturer: vendorId: 0x1392 productId: 0x010D deviceProfileName: plug-binary + - id: "20496/610" + deviceLabel: Mini Smart Wi-Fi Plug Energy Monitoring Matter + vendorId: 0x5010 + productId: 0x0262 + deviceProfileName: plug-power-energy-powerConsumption matterGeneric: From 614a28409a5bf3e4221ede1909dbb4b010419774 Mon Sep 17 00:00:00 2001 From: JanJakubiszyn <101173721+JanJakubiszyn@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:28:57 +0200 Subject: [PATCH 393/538] WWSTCERT-11943 Added support for Bosch Motion Detector II [+M] (#2555) * Added support for Bosch matter motion sensor # Conflicts: # drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua # drivers/SmartThings/matter-switch/src/switch_utils/fields.lua * Implementing changes from the code review: edit of the device profile name and capability order, edit of the match_profile's condition # Conflicts: # drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua # Conflicts: # drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua * Code cleanup * Update fingerprints.yml * Update light-level-battery-illuminance-motion-temperature.yml --------- Co-authored-by: Konrad K <33450498+KKlimczukS@users.noreply.github.com> --- .../matter-switch/fingerprints.yml | 5 +++ ...battery-illuminance-motion-temperature.yml | 32 +++++++++++++++++++ .../src/switch_utils/device_configuration.lua | 2 +- .../matter-switch/src/switch_utils/fields.lua | 3 ++ 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/matter-switch/profiles/light-level-battery-illuminance-motion-temperature.yml diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 8f9351fcd0..745cfb6a2d 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -232,6 +232,11 @@ matterManufacturer: vendorId: 0x1209 productId: 0x3016 deviceProfileName: plug-power-energy-powerConsumption + - id: 4617/12307 + deviceLabel: "Motion Detector II [M]" + vendorId: 0x1209 + productId: 0x3013 + deviceProfileName: light-level-battery-illuminance-motion-temperature #Chengdu - id: "5218/8197" deviceLabel: Magic Cube DS001 diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-battery-illuminance-motion-temperature.yml b/drivers/SmartThings/matter-switch/profiles/light-level-battery-illuminance-motion-temperature.yml new file mode 100644 index 0000000000..fb1cd099a4 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/light-level-battery-illuminance-motion-temperature.yml @@ -0,0 +1,32 @@ +name: light-level-battery-illuminance-motion-temperature +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + config: + values: + - key: "level.value" + range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 + - id: motionSensor + version: 1 + - id: temperatureMeasurement + version: 1 + - id: illuminanceMeasurement + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: MotionSensor +preferences: + - preferenceId: tempOffset + explicit: true + diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index f757a41871..772669ebd7 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -265,7 +265,7 @@ function DeviceConfiguration.match_profile(driver, device) 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" + updated_profile = switch_utils.get_product_override_field(device, "target_profile") or "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" diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index f70a7e2160..02c864fa4f 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -120,6 +120,9 @@ SwitchFields.vendor_overrides = { [0x000C] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, [0x000D] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, }, + [0x1209] = { -- Bosch + [0x3013] = {target_profile = "light-level-battery-illuminance-motion-temperature"} + } } SwitchFields.switch_category_vendor_overrides = { From 26816e739c0e7c3cc8cfb1761bafa51750fdebe6 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Fri, 12 Jun 2026 13:47:09 -0500 Subject: [PATCH 394/538] WWSTCERT-12050 Correct Mini Smart Wi-Fi Plug Matter fingerprint (#3021) Co-authored-by: OpenCode --- drivers/SmartThings/matter-switch/fingerprints.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 745cfb6a2d..df522a4df0 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -4159,10 +4159,10 @@ matterManufacturer: vendorId: 0x1392 productId: 0x010D deviceProfileName: plug-binary - - id: "20496/610" + - id: "5010/262" deviceLabel: Mini Smart Wi-Fi Plug Energy Monitoring Matter - vendorId: 0x5010 - productId: 0x0262 + vendorId: 0x1392 + productId: 0x0106 deviceProfileName: plug-power-energy-powerConsumption From 09d82f78d8b2c320dc7c317f3215d88acc70ba74 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Fri, 12 Jun 2026 13:52:05 -0500 Subject: [PATCH 395/538] WWSTCERT-12165 Aqara Smart Lock J200 (#3022) --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 +++++ .../matter-lock/src/new-matter-lock/fingerprints.lua | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 796cefd590..01e662c421 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -25,6 +25,11 @@ matterManufacturer: vendorId: 0x115F productId: 0x2804 deviceProfileName: lock + - id: "4447/10253" + deviceLabel: Aqara Smart Lock J200 + vendorId: 0x115F + productId: 0x280D + deviceProfileName: lock-modular #Eufy - id: "5427/1" deviceLabel: eufy Smart Lock E31 diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index 375b8c6189..d3127d4b8a 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -11,6 +11,7 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x115f, 0x280e}, -- AQARA Smart Gate Lock U500 {0x115f, 0x280f}, -- AQARA Smart Rim Lock U500 {0x115f, 0x2810}, -- AQARA Smart Glass Door Lock U500 + {0x115F, 0x280D}, -- Aqara Smart Lock J200 {0x147F, 0x0001}, -- U-tec {0x147F, 0x0007}, -- ULTRALOQ Bolt Pro Smart Matter Door Lock {0x147F, 0x0008}, -- Ultraloq, Bolt Smart Matter Door Lock From 7de3ac64b83a46d688fb5bef238d31bb237b2b84 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Fri, 12 Jun 2026 14:42:17 -0500 Subject: [PATCH 396/538] WWSTCERT-12012 Linkind Smart Ceiling Fan (#3023) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index df522a4df0..47409926af 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -226,6 +226,11 @@ matterManufacturer: vendorId: 0x1396 productId: 0x1215 deviceProfileName: light-color-level + - id: "5014/4158" + deviceLabel: Linkind Smart Ceiling Fan + vendorId: 0x1396 + productId: 0x103E + deviceProfileName: fan-modular #Bosch Smart Home - id: "4617/12310" deviceLabel: Plug Compact [M] From 79b4ee0c9aa0cedc9eb75091f34fd3155c77001d Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Mon, 15 Jun 2026 09:42:19 -0500 Subject: [PATCH 397/538] Isolate the eve-energy subdriver to private cluster usage (#3019) * Isolate the eve-energy subdriver to private cluster usage * Remove redundant test, check cluster differently in can_handle * Revert 5d2a4d2, except the redundant test removal --- .../src/sub_drivers/eve_energy/can_handle.lua | 9 +- .../src/test/test_eve_energy.lua | 84 +++++++++++++++++++ 2 files changed, 89 insertions(+), 4 deletions(-) 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 index 369b93b8de..02bc9e06d2 100644 --- 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 @@ -6,11 +6,12 @@ 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 + local EVE_PRIVATE_CLUSTER_ID = 0x130AFC01 + -- this sub driver loads for devices that: + -- 1. Contain the Eve Private Cluster (0x130AFC01) + -- 2. Do NOT have the Standard Electrical Sensor device type if device.network_type == device_lib.NETWORK_TYPE_MATTER and - device.manufacturer_info.vendor_id == EVE_MANUFACTURER_ID and + #device:get_endpoints(EVE_PRIVATE_CLUSTER_ID) > 0 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 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 1be1e16980..e90f5a70da 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -15,6 +15,28 @@ local PRIVATE_ATTR_ID_WATT = 0x130A000A local PRIVATE_ATTR_ID_WATT_ACCUMULATED = 0x130A000B local PRIVATE_ATTR_ID_ACCUMULATED_CONTROL_POINT = 0x130A000E +-- Helper function to add get_endpoints method to mock devices +local function add_get_endpoints_to_mock(device) + device.get_endpoints = function(self, cluster_id, opts) + opts = opts or {} + local eps = {} + for _, ep in ipairs(self.endpoints) do + for _, cluster in ipairs(ep.clusters or {}) do + if cluster.cluster_id == cluster_id then + -- Check feature_bitmap if specified + if opts.feature_bitmap == nil or + (cluster.feature_map and (cluster.feature_map & opts.feature_bitmap) == opts.feature_bitmap) then + table.insert(eps, ep.endpoint_id) + break + end + end + end + end + return eps + end + return device +end + local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("power-energy-powerConsumption.yml"), manufacturer_info = { @@ -53,6 +75,7 @@ local mock_device = test.mock_device.build_test_matter_device({ } } }) +add_get_endpoints_to_mock(mock_device) local mock_eve_device_using_electrical_sensor = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("plug-energy-powerConsumption.yml"), @@ -112,6 +135,42 @@ local mock_eve_device_using_electrical_sensor = test.mock_device.build_test_matt } } }) +add_get_endpoints_to_mock(mock_eve_device_using_electrical_sensor) + +-- Mock device without Eve Private Cluster (should not match eve_energy sub-driver) +local mock_device_without_private_cluster = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("plug-binary.yml"), + manufacturer_info = { + vendor_id = 0x130A, + product_id = 0x0051, + }, + 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 + } + }, + device_types = { + { device_type_id = 0x010A, device_type_revision = 1 } -- On/Off Plug + } + } + } +}) +add_get_endpoints_to_mock(mock_device_without_private_cluster) local function test_init() local cluster_subscribe_list = { @@ -129,6 +188,31 @@ local function test_init() end test.set_test_init_function(test_init) +test.register_coroutine_test( + "Eve Energy sub-driver can_handle should return true for devices with Eve Private Cluster and no Electrical Sensor", + function() + local eve_energy_can_handle = require("sub_drivers.eve_energy.can_handle") + local result, sub_driver = eve_energy_can_handle(nil, nil, mock_device) + assert(result == true, "can_handle should return true for Eve device with private cluster and no electrical sensor") + assert(sub_driver ~= nil, "sub_driver should be returned") + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Eve Energy sub-driver can_handle should return false for devices without Eve Private Cluster", + function() + local eve_energy_can_handle = require("sub_drivers.eve_energy.can_handle") + local result = eve_energy_can_handle(nil, nil, mock_device_without_private_cluster) + assert(result == false, "can_handle should return false for device without Eve Private Cluster") + end, + { + min_api_version = 17 + } +) + test.register_message_test( "On command should send the appropriate commands", { From f92f81d64d6f7d6292fd171105af003793bcb3d1 Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Tue, 16 Jun 2026 19:46:51 +0900 Subject: [PATCH 398/538] Fix error when updating pin code (#3007) Fix error when updating pin code Signed-off-by: Hunsup Jung --- .../matter-lock/src/new-matter-lock/init.lua | 3 +- .../src/test/test_new_matter_lock.lua | 98 ++++++++++++++++++- 2 files changed, 97 insertions(+), 4 deletions(-) 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 670b9dc63b..3850d71380 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -1671,6 +1671,7 @@ local function handle_update_credential(driver, device, command) device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) device:set_field(lock_utils.CRED_INDEX, credIdx, {persist = true}) + device:set_field(lock_utils.USER_TYPE, nil, {persist = true}) -- Send command local ep = device:component_to_endpoint(command.component) @@ -1728,7 +1729,7 @@ local function set_pin_response_handler(driver, device, ib, response) -- If User Type is Guest and device support schedule, add default schedule local week_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.WEEK_DAY_ACCESS_SCHEDULES}) local year_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.YEAR_DAY_ACCESS_SCHEDULES}) - if userType == "guest" and (#week_schedule_eps > 0 or #year_schedule_eps > 0) then + if userType == "guest" and (#week_schedule_eps > 0 or #year_schedule_eps > 0) and cmdName ~= "updateCredential" then local cmdName = "defaultSchedule" local scheduleIdx = 1 diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 0b7535223b..492a3c2805 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -1666,15 +1666,106 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "Handle Update Credential command received from SmartThings.", + "Update Credential for Guest user should not add default schedule again", function() + -- Add Guest user with credential. This sets the USER_TYPE field to "guest". + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockCredentials.ID, + command = "addCredential", + args = {0, "guest", "pin", "654123"} + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + {credential_type = DoorLock.types.CredentialTypeEnum.PIN, credential_index = 1} + ), -- credential + "654123", -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users( + {{userIndex = 1, userType = "guest"}}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials( + {{credentialIndex=1, credentialType="pin", userIndex=1}}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetYearDaySchedule( + mock_device, 1, -- endpoint + 1, -- year_day_index + 1, -- user_index + 0, -- local_start_time + 0xffffffff -- local_end_time + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.SetYearDaySchedule:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.SUCCESS -- status + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.commandResult( + {commandName="addCredential", credentialIndex=1, statusCode="success", userIndex=1}, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + test.wait_for_events() + -- Update the Guest user's credential. The stale "guest" USER_TYPE field must + -- be cleared so the default schedule is not added again on the response. test.socket.capability:__queue_receive( { mock_device.id, { capability = capabilities.lockCredentials.ID, command = "updateCredential", - args = {1, 1, "pin", "654123"} + args = {1, 1, "pin", "111213"} }, } ) @@ -1687,7 +1778,7 @@ test.register_coroutine_test( DoorLock.types.CredentialStruct( {credential_type = DoorLock.types.CredentialTypeEnum.PIN, credential_index = 1} ), -- credential - "654123", -- credential_data + "111213", -- credential_data 1, -- user_index nil, -- user_status nil -- user_type @@ -1706,6 +1797,7 @@ test.register_coroutine_test( ), } ) + -- The commandResult must be emitted without sending SetYearDaySchedule test.socket.capability:__expect_send( mock_device:generate_test_message( "main", From f04eec5be979fb89a717ced3c5938e579012f0d8 Mon Sep 17 00:00:00 2001 From: LQ107 Date: Wed, 17 Jun 2026 02:17:02 +0800 Subject: [PATCH 399/538] Ledvance zigbee EM EU plug (#3009) * ledvance zigbee meter plug driver pull * add the handle file * add driver fingerprints * modify the ledvance zigbee meter plug device Label * add driver for PLUG EU EM T * delete the PLUG EU EM T driver * Update the PLUG COMPACT EU EM T device driver * set the multiplier and divisor fields in the initial handler * disable the rely on the defaults to read the multiplier/divisor and Add driver registration processing * fixing the init.lua multiplier/divisor handle ,fixing the commit conflict * update sub driver init.lua files * Remove unnecessary files * Remove redundant code * 1.add the test unit 2. add sub driver register 3.fixing the energy_meter_handler issue * revert the gitgnore file to the default * 1.fixing the energy meter value redundant processing 2.change the location of the test file * 1.Use the default SmartThings simple metering handlers * fixing the following issue: 1.Device-reported multiplier/divisor values are kept when present. 2.Defaults are only applied when the device did not provide usable values. 3.The test covers that fallback behavior. * Fix the issues of missing trailing newline and remove trailing white space * 1.rename the driver file name to ledvance-metering-plug 2.keep the gitignore file don't change * keep gitignore same to the main branch * add LEDVANCE/PLUG EU EM T meter plug driver * add the min_api_version option value for PLUG EU EM T * add Min api version * PLUG EU EM T only add fingerprints * move sub driver path to zigbee-switch-power * remove the old citations of the PLUG COMPACT EU EM T * PLUG EU EM T add sub driver model * remove old sub driver files * remove old sub driver load * 1.add the ledvance PLUG fingerprints at the zigbee-switch-power/fingerprints.lua 2.add the test.disable_startup_messages() --- .../zigbee-switch/fingerprints.yml | 5 ++ .../ledvance-metering-plug/fingerprints.lua | 6 --- .../zigbee-switch/src/sub_drivers.lua | 1 - .../src/test/test_ledvance_metering_plug.lua | 49 ++++++++++++++++++- .../src/zigbee-switch-power/fingerprints.lua | 4 +- .../ledvance-metering-plug/can_handle.lua | 4 +- .../ledvance-metering-plug/fingerprints.lua | 7 +++ .../ledvance-metering-plug/init.lua | 2 +- .../src/zigbee-switch-power/sub_drivers.lua | 3 +- 9 files changed, 67 insertions(+), 14 deletions(-) delete mode 100644 drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/fingerprints.lua rename drivers/SmartThings/zigbee-switch/src/{ => zigbee-switch-power}/ledvance-metering-plug/can_handle.lua (66%) create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/ledvance-metering-plug/fingerprints.lua rename drivers/SmartThings/zigbee-switch/src/{ => zigbee-switch-power}/ledvance-metering-plug/init.lua (89%) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 3219c46d65..90862aa61f 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -1735,6 +1735,11 @@ zigbeeManufacturer: manufacturer: LEDVANCE model: PLUG COMPACT EU EM T deviceProfileName: switch-power-energy + - id: "LEDVANCE/PLUG EU EM T" + deviceLabel: SMART ZIGBEE PLUG EU EM T + manufacturer: LEDVANCE + model: PLUG EU EM T + deviceProfileName: switch-power-energy - id: "OSRAM/LIGHTIFY Edge-lit flushmount" deviceLabel: SYLVANIA Light manufacturer: OSRAM diff --git a/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/fingerprints.lua deleted file mode 100644 index e514269f82..0000000000 --- a/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/fingerprints.lua +++ /dev/null @@ -1,6 +0,0 @@ --- Copyright 2026 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -return { - { mfr = "LEDVANCE", model = "PLUG COMPACT EU EM T" } -} diff --git a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua index 736c2a0464..69be094da4 100644 --- a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua @@ -14,7 +14,6 @@ return { lazy_load_if_possible("sinope"), lazy_load_if_possible("sinope-dimmer"), lazy_load_if_possible("zigbee-dimmer-power-energy"), - lazy_load_if_possible("ledvance-metering-plug"), lazy_load_if_possible("zigbee-metering-plug-power-consumption-report"), lazy_load_if_possible("jasco"), lazy_load_if_possible("multi-switch-no-master"), diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua b/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua index 6e63bba1a9..ebdcae468e 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua @@ -20,11 +20,26 @@ local mock_device = test.mock_device.build_test_zigbee_device( } ) +local mock_device_eu_em_t = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("switch-power-energy.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LEDVANCE", + model = "PLUG EU EM T", + server_clusters = { 0x0006, 0x0702 } + } + } + } +) + zigbee_test_utils.prepare_zigbee_env_info() local function test_init() test.disable_startup_messages() test.mock_device.add_test_device(mock_device) + test.mock_device.add_test_device(mock_device_eu_em_t) end test.set_test_init_function(test_init) @@ -40,7 +55,7 @@ test.register_coroutine_test( assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 100) end, { - min_api_version = 15 + min_api_version = 17 } ) @@ -55,7 +70,37 @@ test.register_coroutine_test( assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 1000) end, { - min_api_version = 15 + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Device init should set default multiplier and divisor only when not already set - PLUG EU EM T", + function() + assert(mock_device_eu_em_t:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == nil) + assert(mock_device_eu_em_t:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == nil) + test.socket.device_lifecycle:__queue_receive({ mock_device_eu_em_t.id, "init" }) + test.wait_for_events() + assert(mock_device_eu_em_t:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == 1) + assert(mock_device_eu_em_t:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 100) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Device init should preserve device-reported multiplier and divisor - PLUG EU EM T", + function() + mock_device_eu_em_t:set_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY, 5, {persist = true}) + mock_device_eu_em_t:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, 1000, {persist = true}) + test.socket.device_lifecycle:__queue_receive({ mock_device_eu_em_t.id, "init" }) + test.wait_for_events() + assert(mock_device_eu_em_t:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) == 5) + assert(mock_device_eu_em_t:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 1000) + end, + { + min_api_version = 17 } ) diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/fingerprints.lua index d277d36967..f393f61eea 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/fingerprints.lua @@ -14,5 +14,7 @@ return { { mfr = "SALUS", model = "SX885ZB" }, { mfr = "AduroSmart Eria", model = "AD-SmartPlug3001" }, { mfr = "AduroSmart Eria", model = "BPU3" }, - { mfr = "AduroSmart Eria", model = "BDP3001" } + { mfr = "AduroSmart Eria", model = "BDP3001" }, + { mfr = "LEDVANCE", model = "PLUG COMPACT EU EM T" }, + { mfr = "LEDVANCE", model = "PLUG EU EM T" } } diff --git a/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/ledvance-metering-plug/can_handle.lua similarity index 66% rename from drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/can_handle.lua rename to drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/ledvance-metering-plug/can_handle.lua index 2d7f42bac8..4ecb5ab271 100644 --- a/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/can_handle.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/ledvance-metering-plug/can_handle.lua @@ -2,10 +2,10 @@ -- Licensed under the Apache License, Version 2.0 return function(opts, driver, device, ...) - local FINGERPRINTS = require("ledvance-metering-plug.fingerprints") + local FINGERPRINTS = require("zigbee-switch-power.ledvance-metering-plug.fingerprints") for _, fingerprint in ipairs(FINGERPRINTS) do if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("ledvance-metering-plug") + local subdriver = require("zigbee-switch-power.ledvance-metering-plug") return true, subdriver end end diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/ledvance-metering-plug/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/ledvance-metering-plug/fingerprints.lua new file mode 100644 index 0000000000..50542e69ea --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/ledvance-metering-plug/fingerprints.lua @@ -0,0 +1,7 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "LEDVANCE", model = "PLUG COMPACT EU EM T" }, + { mfr = "LEDVANCE", model = "PLUG EU EM T" } +} diff --git a/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/init.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/ledvance-metering-plug/init.lua similarity index 89% rename from drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/init.lua rename to drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/ledvance-metering-plug/init.lua index e907f25a28..55e703a6d6 100644 --- a/drivers/SmartThings/zigbee-switch/src/ledvance-metering-plug/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/ledvance-metering-plug/init.lua @@ -17,7 +17,7 @@ local ledvance_metering_plug = { lifecycle_handlers = { init = device_init }, - can_handle = require("ledvance-metering-plug.can_handle") + can_handle = require("zigbee-switch-power.ledvance-metering-plug.can_handle") } return ledvance_metering_plug 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 index 340e1f27c6..0c1ba5eeef 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/sub_drivers.lua @@ -5,5 +5,6 @@ local lazy_load = require "lazy_load_subdriver" return { lazy_load("zigbee-switch-power.aurora-relay"), - lazy_load("zigbee-switch-power.vimar") + lazy_load("zigbee-switch-power.vimar"), + lazy_load("zigbee-switch-power.ledvance-metering-plug") } From 2df9e6222d00f33dab8b886486b124625c2228ac Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Wed, 17 Jun 2026 10:54:01 -0500 Subject: [PATCH 400/538] Revert "WWSTCERT-12165 Aqara Smart Lock J200" (#3030) --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 ----- .../matter-lock/src/new-matter-lock/fingerprints.lua | 1 - 2 files changed, 6 deletions(-) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 01e662c421..796cefd590 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -25,11 +25,6 @@ matterManufacturer: vendorId: 0x115F productId: 0x2804 deviceProfileName: lock - - id: "4447/10253" - deviceLabel: Aqara Smart Lock J200 - vendorId: 0x115F - productId: 0x280D - deviceProfileName: lock-modular #Eufy - id: "5427/1" deviceLabel: eufy Smart Lock E31 diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index d3127d4b8a..375b8c6189 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -11,7 +11,6 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x115f, 0x280e}, -- AQARA Smart Gate Lock U500 {0x115f, 0x280f}, -- AQARA Smart Rim Lock U500 {0x115f, 0x2810}, -- AQARA Smart Glass Door Lock U500 - {0x115F, 0x280D}, -- Aqara Smart Lock J200 {0x147F, 0x0001}, -- U-tec {0x147F, 0x0007}, -- ULTRALOQ Bolt Pro Smart Matter Door Lock {0x147F, 0x0008}, -- Ultraloq, Bolt Smart Matter Door Lock From d740b1e21e5558d8c9d1de281a315c4ba262abb0 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 17 Jun 2026 11:27:48 -0500 Subject: [PATCH 401/538] Matter Ikea Scroll: Aggregate multiple responses into one event, remove InitialPress handling during scroll (#3014) --- .../scroll_handlers/event_handlers.lua | 52 +++++++++----- .../ikea_scroll/scroll_utils/event_utils.lua | 50 +++++++++++++ .../ikea_scroll/scroll_utils/fields.lua | 11 ++- .../src/test/test_ikea_scroll.lua | 72 +++++-------------- 4 files changed, 104 insertions(+), 81 deletions(-) create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua index da9b1c0392..a6a7ce9168 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua @@ -6,28 +6,41 @@ local capabilities = require "st.capabilities" local switch_utils = require "switch_utils.utils" local generic_event_handlers = require "switch_handlers.event_handlers" local scroll_fields = require "sub_drivers.ikea_scroll.scroll_utils.fields" +local event_utils = require "sub_drivers.ikea_scroll.scroll_utils.event_utils" local IkeaScrollEventHandlers = {} local function rotate_amount_event_helper(device, endpoint_id, num_presses_to_handle) + if num_presses_to_handle <= 0 then return end + -- to cut down on checks, we can assume that if the endpoint is not in ENDPOINTS_UP_SCROLL, it is in ENDPOINTS_DOWN_SCROLL local scroll_direction = switch_utils.tbl_contains(scroll_fields.ENDPOINTS_UP_SCROLL, endpoint_id) and 1 or -1 local scroll_amount = st_utils.clamp_value(scroll_direction * scroll_fields.PER_SCROLL_EVENT_ROTATION * num_presses_to_handle, -100, 100) + device:emit_event_for_endpoint(endpoint_id, capabilities.knob.rotateAmount(scroll_amount, {state_change = true})) end -- Used by ENDPOINTS_UP_SCROLL and ENDPOINTS_DOWN_SCROLL, not ENDPOINTS_PUSH function IkeaScrollEventHandlers.multi_press_ongoing_handler(driver, device, ib, response) if switch_utils.tbl_contains(scroll_fields.ENDPOINTS_PUSH, ib.endpoint_id) then - -- Ignore MultiPressOngoing events from push endpoints. device.log.debug("Received MultiPressOngoing event from push endpoint, ignoring.") else - local cur_num_presses_counted = ib.data and ib.data.elements and ib.data.elements.current_number_of_presses_counted.value or 0 - local num_presses_to_handle = cur_num_presses_counted - (device:get_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED) or 0) - if num_presses_to_handle > 0 then - device:set_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED, cur_num_presses_counted) - rotate_amount_event_helper(device, ib.endpoint_id, num_presses_to_handle) + local cur_num_presses_counted = ib.data.elements and ib.data.elements.current_number_of_presses_counted.value or 0 + local cur_multi_press_count = cur_num_presses_counted + if #response.info_blocks > 1 then + -- note: keep in mind that response blocks with mutliple info blocks are not supported by unit tests today. + if event_utils.is_last_valid_info_block(ib.event_id, cur_num_presses_counted, response.info_blocks) then + local aggregated_presses = event_utils.aggregate_scroll_amount_for_info_blocks(device, response.info_blocks) or {} + cur_num_presses_counted = aggregated_presses.total_presses or 0 + cur_multi_press_count = aggregated_presses.presses_in_current_chain or 0 + else + device.log.debug("Received MultiPressOngoing event that is not the last valid info block, ignoring.") + return + end end + local num_presses_to_handle = cur_num_presses_counted - (device:get_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_HANDLED) or 0) + device:set_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_HANDLED, cur_multi_press_count) + rotate_amount_event_helper(device, ib.endpoint_id, num_presses_to_handle) end end @@ -35,13 +48,21 @@ function IkeaScrollEventHandlers.multi_press_complete_handler(driver, device, ib if switch_utils.tbl_contains(scroll_fields.ENDPOINTS_PUSH, ib.endpoint_id) then generic_event_handlers.multi_press_complete_handler(driver, device, ib, response) else - local total_num_presses_counted = ib.data and ib.data.elements and ib.data.elements.total_number_of_presses_counted.value or 0 - local num_presses_to_handle = total_num_presses_counted - (device:get_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED) or 0) - if num_presses_to_handle > 0 then - rotate_amount_event_helper(device, ib.endpoint_id, num_presses_to_handle) + local total_num_presses_counted = ib.data.elements and ib.data.elements.total_number_of_presses_counted.value or 0 + if #response.info_blocks > 1 then + -- note: keep in mind that response blocks with mutliple info blocks are not supported by unit tests today. + if event_utils.is_last_valid_info_block(ib.event_id, total_num_presses_counted, response.info_blocks) then + local aggregated_presses = event_utils.aggregate_scroll_amount_for_info_blocks(device, response.info_blocks) or {} + total_num_presses_counted = aggregated_presses.total_presses or 0 + else + device.log.debug("Received MultiPressComplete event that is not the last valid info block, ignoring.") + return + end end - -- reset the LATEST_NUMBER_OF_PRESSES_COUNTED to nil at the end of a MultiPress chain. - device:set_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED, nil) + local num_presses_to_handle = total_num_presses_counted - (device:get_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_HANDLED) or 0) + rotate_amount_event_helper(device, ib.endpoint_id, num_presses_to_handle) + -- always reset the LATEST_NUMBER_OF_PRESSES_HANDLED to nil at the end of a handled MultiPress chain. + device:set_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_HANDLED, nil) end end @@ -49,12 +70,7 @@ function IkeaScrollEventHandlers.initial_press_handler(driver, device, ib, respo if switch_utils.tbl_contains(scroll_fields.ENDPOINTS_PUSH, ib.endpoint_id) then generic_event_handlers.initial_press_handler(driver, device, ib, response) else - -- the magic number "1" occurs in this handler since the InitialPress event represents the first press. - local latest_presses_counted = device:get_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED) or 0 - if latest_presses_counted == 0 then - device:set_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED, 1) - rotate_amount_event_helper(device, ib.endpoint_id, 1) - end + device.log.debug("Received InitialPress event from scroll endpoint, ignoring.") end end diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua new file mode 100644 index 0000000000..c838ad5c67 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua @@ -0,0 +1,50 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local clusters = require "st.matter.clusters" + +local IkeaScrollEventUtils = {} + +-- inspect all info blocks to find the last one that is not an InitialPress event. We will +-- only try to emit a rotateAmount event if the current info block being handled is that last one. +function IkeaScrollEventUtils.is_last_valid_info_block(cur_info_block_event_id, cur_info_block_value, info_blocks) + local last_valid_emission_idx = #info_blocks + -- Ignore all InitialPress events in a multi-response block + while (last_valid_emission_idx > 1) and (info_blocks[last_valid_emission_idx].info_block.event_id == clusters.Switch.events.InitialPress.ID) do + last_valid_emission_idx = last_valid_emission_idx - 1 + end + + -- Because the info block does not include the unique_key Matter defined event number, this + -- logic is a best guess at matching the current info block to the last valid info block. + local emission_ib = info_blocks[last_valid_emission_idx].info_block + if emission_ib.event_id ~= cur_info_block_event_id then + return false + elseif emission_ib.event_id == clusters.Switch.events.MultiPressComplete.ID then + local last_valid_ib_value = emission_ib.data.elements and emission_ib.data.elements.total_number_of_presses_counted.value or 0 + return last_valid_ib_value == cur_info_block_value + elseif emission_ib.event_id == clusters.Switch.events.MultiPressOngoing.ID then + local last_valid_ib_value = emission_ib.data.elements and emission_ib.data.elements.current_number_of_presses_counted.value or 0 + return last_valid_ib_value == cur_info_block_value + elseif last_valid_emission_idx == 1 then -- aka, all ib's are InitialPress + return true + end + + return false +end + +function IkeaScrollEventUtils.aggregate_scroll_amount_for_info_blocks(device, info_blocks) + local total_presses = 0 + local presses_in_current_chain = 0 + for _, ib in ipairs(info_blocks) do + if ib.info_block.event_id == clusters.Switch.events.MultiPressOngoing.ID then + presses_in_current_chain = ib.info_block.data.elements and ib.info_block.data.elements.current_number_of_presses_counted.value or 0 + elseif ib.info_block.event_id == clusters.Switch.events.MultiPressComplete.ID then + total_presses = total_presses + (ib.info_block.data.elements and ib.info_block.data.elements.total_number_of_presses_counted.value or 0) + presses_in_current_chain = 0 + end + end + total_presses = total_presses + presses_in_current_chain -- aggregate any presses to the total from the current chain + return { total_presses = total_presses, presses_in_current_chain = presses_in_current_chain } +end + +return IkeaScrollEventUtils 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 index 5e98a829c0..451111f0fb 100644 --- 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 @@ -1,7 +1,6 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 -local st_utils = require "st.utils" local clusters = require "st.matter.clusters" local IkeaScrollFields = {} @@ -18,14 +17,12 @@ IkeaScrollFields.ENDPOINTS_UP_SCROLL = {1, 4, 7} -- Generic Switch Endpoints used for Down Scroll functionality IkeaScrollFields.ENDPOINTS_DOWN_SCROLL = {2, 5, 8} --- Maximum number of presses at a time -IkeaScrollFields.MAX_SCROLL_PRESSES = 18 - -- Amount to rotate per scroll event -IkeaScrollFields.PER_SCROLL_EVENT_ROTATION = st_utils.round(1 / IkeaScrollFields.MAX_SCROLL_PRESSES * 100) +-- 6 == st_utils.round(1/18 * 100), where 18 is the maximum number of presses that can be pressed at a time +IkeaScrollFields.PER_SCROLL_EVENT_ROTATION = 6 --- Field to track the latest number of presses counted during a single scroll event sequence -IkeaScrollFields.LATEST_NUMBER_OF_PRESSES_COUNTED = "__latest_number_of_presses_counted" +-- Field to track the latest number of presses handled during a single scroll event sequence +IkeaScrollFields.LATEST_NUMBER_OF_PRESSES_HANDLED = "__latest_number_of_presses_handled" -- Required Events for the ENDPOINTS_PUSH. IkeaScrollFields.switch_press_subscribed_events = { diff --git a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua index 806b25f33f..f421b17f79 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua @@ -249,12 +249,7 @@ test.register_message_test( ) }, }, - { - channel = "capability", - direction = "send", - message = mock_ikea_scroll:generate_test_message("main", - capabilities.knob.rotateAmount(6, {state_change = true})) - }, + -- ignore InitialPress events during scroll { channel = "matter", direction = "receive", @@ -269,7 +264,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("main", - capabilities.knob.rotateAmount(6, {state_change = true})) + capabilities.knob.rotateAmount(12, {state_change = true})) }, { channel = "matter", @@ -307,12 +302,7 @@ test.register_message_test( ) }, }, - { - channel = "capability", - direction = "send", - message = mock_ikea_scroll:generate_test_message("main", - capabilities.knob.rotateAmount(6, {state_change = true})) - }, + -- ignore InitialPress events during scroll { channel = "matter", direction = "receive", @@ -327,7 +317,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("main", - capabilities.knob.rotateAmount(6, {state_change = true})) + capabilities.knob.rotateAmount(12, {state_change = true})) }, { channel = "matter", @@ -373,12 +363,7 @@ test.register_message_test( ) }, }, - { - channel = "capability", - direction = "send", - message = mock_ikea_scroll:generate_test_message("main", - capabilities.knob.rotateAmount(-6, {state_change = true})) - }, + -- ignore InitialPress events during scroll { channel = "matter", direction = "receive", @@ -393,7 +378,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("main", - capabilities.knob.rotateAmount(-6, {state_change = true})) + capabilities.knob.rotateAmount(-12, {state_change = true})) }, { channel = "matter", @@ -431,12 +416,7 @@ test.register_message_test( ) }, }, - { - channel = "capability", - direction = "send", - message = mock_ikea_scroll:generate_test_message("main", - capabilities.knob.rotateAmount(-6, {state_change = true})) - }, + -- ignore InitialPress events during scroll { channel = "matter", direction = "receive", @@ -451,7 +431,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("main", - capabilities.knob.rotateAmount(-6, {state_change = true})) + capabilities.knob.rotateAmount(-12, {state_change = true})) }, { channel = "matter", @@ -497,12 +477,7 @@ test.register_message_test( ) }, }, - { - channel = "capability", - direction = "send", - message = mock_ikea_scroll:generate_test_message("group2", - capabilities.knob.rotateAmount(6, {state_change = true})) - }, + -- ignore InitialPress events during scroll { channel = "matter", direction = "receive", @@ -517,7 +492,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("group2", - capabilities.knob.rotateAmount(6, {state_change = true})) + capabilities.knob.rotateAmount(12, {state_change = true})) }, { channel = "matter", @@ -553,12 +528,7 @@ test.register_message_test( ) }, }, - { - channel = "capability", - direction = "send", - message = mock_ikea_scroll:generate_test_message("group2", - capabilities.knob.rotateAmount(-6, {state_change = true})) - }, + -- ignore InitialPress events during scroll { channel = "matter", direction = "receive", @@ -573,7 +543,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("group2", - capabilities.knob.rotateAmount(-6, {state_change = true})) + capabilities.knob.rotateAmount(-12, {state_change = true})) }, { channel = "matter", @@ -609,12 +579,7 @@ test.register_message_test( ) }, }, - { - channel = "capability", - direction = "send", - message = mock_ikea_scroll:generate_test_message("group3", - capabilities.knob.rotateAmount(6, {state_change = true})) - }, + -- ignore InitialPress events during scroll { channel = "matter", direction = "receive", @@ -629,7 +594,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("group3", - capabilities.knob.rotateAmount(6, {state_change = true})) + capabilities.knob.rotateAmount(12, {state_change = true})) }, { channel = "matter", @@ -665,12 +630,7 @@ test.register_message_test( ) }, }, - { - channel = "capability", - direction = "send", - message = mock_ikea_scroll:generate_test_message("group3", - capabilities.knob.rotateAmount(-6, {state_change = true})) - }, + -- ignore InitialPress events during scroll { channel = "matter", direction = "receive", @@ -685,7 +645,7 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_ikea_scroll:generate_test_message("group3", - capabilities.knob.rotateAmount(-6, {state_change = true})) + capabilities.knob.rotateAmount(-12, {state_change = true})) }, { channel = "matter", From 8c73ff2a4434c29ee0e873f06e6e8beeac6d9568 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:31:53 -0500 Subject: [PATCH 402/538] update new lifx fingerprints to include colorControl (#3034) --- .../matter-switch/fingerprints.yml | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 47409926af..e4602b4178 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1351,172 +1351,172 @@ matterManufacturer: deviceLabel: LIFX Supercolor (A19) vendorId: 0x1423 productId: 0x00A3 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/118" deviceLabel: LIFX Lightstrip vendorId: 0x1423 productId: 0x0076 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/221" deviceLabel: LIFX Spot vendorId: 0x1423 productId: 0x00DD - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/144" deviceLabel: LIFX String vendorId: 0x1423 productId: 0x0090 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/216" deviceLabel: LIFX Candle Color (B10) vendorId: 0x1423 productId: 0x00D8 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/225" deviceLabel: LIFX PAR38 vendorId: 0x1423 productId: 0x00E1 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/186" deviceLabel: LIFX Candle Color vendorId: 0x1423 productId: 0x00BA - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/202" deviceLabel: LIFX Ceiling 13x26 vendorId: 0x1423 productId: 0x00CA - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/143" deviceLabel: LIFX String vendorId: 0x1423 productId: 0x008F - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/166" deviceLabel: LIFX Supercolour (BR30) vendorId: 0x1423 productId: 0x00A6 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/167" deviceLabel: LIFX Downlight vendorId: 0x1423 productId: 0x00A7 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/207" deviceLabel: LIFX Everyday Lightstrip vendorId: 0x1423 productId: 0x00CF - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/222" deviceLabel: LIFX Path (Round) vendorId: 0x1423 productId: 0x00DE - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/203" deviceLabel: LIFX String vendorId: 0x1423 productId: 0x00CB - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/218" deviceLabel: LIFX Tube vendorId: 0x1423 productId: 0x00DA - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/214" deviceLabel: LIFX Permanent Outdoor vendorId: 0x1423 productId: 0x00D6 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/117" deviceLabel: LIFX Lightstrip vendorId: 0x1423 productId: 0x0075 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/223" deviceLabel: LIFX Downlight (6 Retro Downlight) vendorId: 0x1423 productId: 0x00DF - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/224" deviceLabel: LIFX Downlight (90mm Downlight) vendorId: 0x1423 productId: 0x00E0 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/204" deviceLabel: LIFX String vendorId: 0x1423 productId: 0x00CC - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/206" deviceLabel: LIFX Neon vendorId: 0x1423 productId: 0x00CE - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/164" deviceLabel: LIFX Supercolor (BR30) vendorId: 0x1423 productId: 0x00A4 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/120" deviceLabel: LIFX Beam vendorId: 0x1423 productId: 0x0078 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/208" deviceLabel: LIFX Everyday Lightstrip vendorId: 0x1423 productId: 0x00D0 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/165" deviceLabel: LIFX Supercolour (A19) vendorId: 0x1423 productId: 0x00A5 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/142" deviceLabel: LIFX Neon vendorId: 0x1423 productId: 0x008E - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/141" deviceLabel: LIFX Neon vendorId: 0x1423 productId: 0x008D - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/177" deviceLabel: LIFX Ceiling vendorId: 0x1423 productId: 0x00B1 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/170" deviceLabel: LIFX Supercolour (A21) vendorId: 0x1423 productId: 0x00AA - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/205" deviceLabel: LIFX Neon vendorId: 0x1423 productId: 0x00CD - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/265" deviceLabel: Ceiling 13 vendorId: 0x1423 productId: 0x0109 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/266" deviceLabel: LIFX Ceiling 13 vendorId: 0x1423 productId: 0x010A - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/267" deviceLabel: LIFX Mirror vendorId: 0x1423 productId: 0x010B - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/268" deviceLabel: LIFX Mirror vendorId: 0x1423 productId: 0x010C - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level #LG - id: "4142/8784" From b4d10a664874c1c93cd2c73e752e18f3dbda1d99 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:39:40 -0500 Subject: [PATCH 403/538] persist electrical tags that are used for profiling (#3038) --- drivers/SmartThings/matter-switch/src/switch_utils/utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 8e21c8baf8..d2457bfb4d 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -431,7 +431,7 @@ function utils.set_fields_for_electrical_sensor_endpoint(device, electrical_sens 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.ELECTRICAL_TAGS, primary_associated_ep_id, tags, {persist = true}) 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 From fa41aa63b5f45cb43c3c8627e2571c1025ed0fc5 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:39:53 -0500 Subject: [PATCH 404/538] Add Initial FP400 profile and subdriver (#3024) --- .../matter-sensor/fingerprints.yml | 5 + .../matter-sensor/profiles/aqara-fp400.yml | 14 ++ .../matter-sensor/src/sensor_utils/fields.lua | 6 + .../matter-sensor/src/sensor_utils/utils.lua | 11 ++ .../matter-sensor/src/sub_drivers.lua | 1 + .../sub_drivers/aqara_fp400/can_handle.lua | 12 ++ .../src/sub_drivers/aqara_fp400/init.lua | 23 +++ .../src/test/test_matter_aqara_fp400.lua | 141 ++++++++++++++++++ 8 files changed, 213 insertions(+) create mode 100644 drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/can_handle.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index 483b92fc21..45b21dd386 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -15,6 +15,11 @@ matterManufacturer: vendorId: 0x115F productId: 0x2005 deviceProfileName: presence-illuminance-temperature-humidity-battery + - id: "4447/8201" + deviceLabel: Spatial Multi-Sensor FP400 + vendorId: 0x115F + productId: 0x2009 + deviceProfileName: aqara-fp400 #Bosch - id: 4617/12309 deviceLabel: "Door/window contact II [M]" diff --git a/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml b/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml new file mode 100644 index 0000000000..c2d5b7b037 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml @@ -0,0 +1,14 @@ +name: aqara-fp400 +components: +- id: main + capabilities: + - id: presenceSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: PresenceSensor diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua index b31b1b5c5b..232e431277 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua @@ -49,4 +49,10 @@ SensorFields.BOOLEAN_CAP_EVENT_MAP = { } } +SensorFields.vendor_overrides = { + [0x115F] = { -- AQARA_MANUFACTURER_ID + [0x2009] = { is_aqara_fp400 = true } + } +} + return SensorFields diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua index d5437410a5..513c9f4602 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua @@ -1,6 +1,8 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local fields = require "sensor_utils.fields" + local utils = {} -- Sanity check bounds for soil moisture measurement limits (percent) @@ -15,6 +17,15 @@ function utils.set_field_for_endpoint(device, field, endpoint, value, additional device:set_field(string.format("%s_%d", field, endpoint), value, additional_params) 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.tbl_contains(array, value) if value == nil then return false end for _, element in pairs(array or {}) do diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers.lua index 3bf4c46f73..aa8b046927 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers.lua @@ -6,5 +6,6 @@ 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"), + lazy_load_if_possible("sub_drivers.aqara_fp400"), } return sub_drivers diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/can_handle.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/can_handle.lua new file mode 100644 index 0000000000..d28a66f147 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_aqara_fp400(opts, driver, device) + local sensor_utils = require "sensor_utils.utils" + if sensor_utils.get_product_override_field(device, "is_aqara_fp400") then + return true, require("sub_drivers.aqara_fp400") + end + return false +end + +return is_aqara_fp400 diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua new file mode 100644 index 0000000000..5f47c86047 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua @@ -0,0 +1,23 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local Fp400LifecycleHandlers = {} + +-- overwrite to avoid unnecessary metadata update calls +function Fp400LifecycleHandlers.do_configure() end + +-- overwrite to avoid unnecessary metadata update calls +function Fp400LifecycleHandlers.driver_switched(driver, device) + device:try_update_metadata({provisioning_state = "PROVISIONED"}) +end + +local aqara_fp400_handler = { + NAME = "aqara-fp400", + lifecycle_handlers = { + doConfigure = Fp400LifecycleHandlers.do_configure, + driverSwitched = Fp400LifecycleHandlers.driver_switched, + }, + can_handle = require("sub_drivers.aqara_fp400.can_handle"), +} + +return aqara_fp400_handler diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua new file mode 100644 index 0000000000..7f384c744e --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua @@ -0,0 +1,141 @@ +-- Copyright 2026 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 matter_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.OccupancySensing.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.IlluminanceMeasurement.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- Occupancy Sensor + } + } +} + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("aqara-fp400.yml"), + manufacturer_info = { + vendor_id = 0x115F, + product_id = 0x2009, + }, + endpoints = matter_endpoints +}) + +local function subscribe_on_init(dev) + local subscribe_request = clusters.OccupancySensing.attributes.Occupancy:subscribe(dev) + subscribe_request:merge(clusters.IlluminanceMeasurement.attributes.MeasuredValue:subscribe(dev)) + return subscribe_request +end + +local function test_init() + test.socket.matter:__set_channel_ordering("relaxed") + local subscribe_request = subscribe_on_init(mock_device) + 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) + +test.register_coroutine_test( + "Test no profile change on doConfigure for FP400", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + -- The FP400 sub-driver overrides doConfigure to be a no-op + -- When doConfigure completes successfully, the framework automatically provisions the device + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Test no profile change on driverSwitched for FP400", + function() + local current_profile = mock_device.profile.id + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "driverSwitched" }) + -- The FP400 sub-driver overrides driverSwitched to only update provisioning state + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + -- Ensure profile has not changed + test.wait_for_events() + assert(mock_device.profile.id == current_profile, "Profile should not change on driverSwitched") + end, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Occupancy reports should generate correct presence messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 1, 1) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.presenceSensor.presence("present")) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 1, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.presenceSensor.presence("not present")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Illuminance reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.IlluminanceMeasurement.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 21370) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 137 })) + } + }, + { + min_api_version = 17 + } +) + +test.run_registered_tests() From c7fc03175413aebe8a351623f9c0807598b10770 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 24 Jun 2026 01:28:30 +0800 Subject: [PATCH 405/538] add firstled-io_M4S4BAC (#2971) --- .../zigbee-switch/fingerprints.yml | 6 + .../switch-button-light-restore-wireless.yml | 39 ++ .../profiles/switch-button-wireless.yml | 17 + .../switch-light-restore-wireless.yml | 37 ++ .../profiles/switch-wireless.yml | 15 + .../src/firstled-io/can_handle.lua | 12 + .../src/firstled-io/fingerprints.lua | 9 + .../zigbee-switch/src/firstled-io/init.lua | 209 ++++++++ .../zigbee-switch/src/sub_drivers.lua | 3 +- .../src/test/test_firstled_switch.lua | 481 ++++++++++++++++++ tools/localizations/cn.csv | 1 + 11 files changed, 828 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-switch/profiles/switch-button-light-restore-wireless.yml create mode 100644 drivers/SmartThings/zigbee-switch/profiles/switch-button-wireless.yml create mode 100644 drivers/SmartThings/zigbee-switch/profiles/switch-light-restore-wireless.yml create mode 100644 drivers/SmartThings/zigbee-switch/profiles/switch-wireless.yml create mode 100644 drivers/SmartThings/zigbee-switch/src/firstled-io/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 90862aa61f..b44a13d3c1 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2451,6 +2451,12 @@ zigbeeManufacturer: manufacturer: JNL model: Y-K002-001 deviceProfileName: basic-switch + #FIRSTLED + - id: "FIRSTLED/M4S4BAC" + deviceLabel: Mirror Series 4x4 1 + manufacturer: FIRSTLED + model: M4S4BAC + deviceProfileName: switch-light-restore-wireless zigbeeGeneric: - id: "genericSwitch" deviceLabel: Zigbee Switch diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-button-light-restore-wireless.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-button-light-restore-wireless.yml new file mode 100644 index 0000000000..f98b552b87 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-button-light-restore-wireless.yml @@ -0,0 +1,39 @@ +name: switch-button-light-restore-wireless +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: button + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController +preferences: + - title: "背光灯(backlight/백라이트)" + name: backlight + description: "背光灯(backlight/백라이트)" + required: false + preferenceType: enumeration + definition: + options: + 0: "关闭(off/닫다)" + 1: "打开(on/열다)" + 2: "人体接近感应(proximity detection/근접 감지)" + default: 2 + - title: "开关上电状态(relay powerOn state)" + name: powerOnStatus + description: "开关上电状态(relay powerOn state/릴레이 초기 동작 상태)" + required: false + preferenceType: enumeration + definition: + options: + 0: "关闭(off/닫다)" + 1: "打开(on/열다)" + 2: "恢复记忆状态(restore/복원)" + default: 2 + - preferenceId: stse.changeToWirelessSwitch + explicit: true diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-button-wireless.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-button-wireless.yml new file mode 100644 index 0000000000..c48290e1a8 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-button-wireless.yml @@ -0,0 +1,17 @@ +name: switch-button-wireless +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: button + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController +preferences: + - preferenceId: stse.changeToWirelessSwitch + explicit: true diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-light-restore-wireless.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-light-restore-wireless.yml new file mode 100644 index 0000000000..b2e002d690 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-light-restore-wireless.yml @@ -0,0 +1,37 @@ +name: switch-light-restore-wireless +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch +preferences: + - title: "背光灯(backlight/백라이트)" + name: backlight + description: "背光灯(backlight/백라이트)" + required: false + preferenceType: enumeration + definition: + options: + 0: "关闭(off/닫다)" + 1: "打开(on/열다)" + 2: "人体接近感应(proximity detection/근접 감지)" + default: 2 + - title: "开关上电状态(relay powerOn state)" + name: powerOnStatus + description: "开关上电状态(relay powerOn state/릴레이 초기 동작 상태)" + required: false + preferenceType: enumeration + definition: + options: + 0: "关闭(off/닫다)" + 1: "打开(on/열다)" + 2: "恢复记忆状态(restore/복원)" + default: 2 + - preferenceId: stse.changeToWirelessSwitch + explicit: true diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-wireless.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-wireless.yml new file mode 100644 index 0000000000..cb4be470f9 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-wireless.yml @@ -0,0 +1,15 @@ +name: switch-wireless +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch +preferences: + - preferenceId: stse.changeToWirelessSwitch + explicit: true diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/can_handle.lua new file mode 100644 index 0000000000..5ad2d32a4b --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local FINGERPRINTS = require("firstled-io.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("firstled-io") + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua new file mode 100644 index 0000000000..2586782642 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +--The number of children determines the number of sub-devices to be created. Each sub-device has the capability to switch between a switch and a button. +--The number of buttons determines how many buttons devices will be created. +--The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. +return { + { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" } +} diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua new file mode 100644 index 0000000000..6348f254b2 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua @@ -0,0 +1,209 @@ +-- Copyright 2026 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" +local data_types = require "st.zigbee.data_types" +local zcl_clusters = require "st.zigbee.zcl.clusters" + +local Scenes = zcl_clusters.Scenes +local PRIVATE_CLUSTER_ID = 0xFCCA +local MFG_CODE = 0x1235 +local FINGERPRINTS = require("firstled-io.fingerprints") + +--stse.changeToWirelessSwitch +--switch mode:The local switch and the app can control the relay.The button capability is not working. +--wirelessSwitch mode:The local switch does not control the relay. Once triggered, it will report to the system as "RecallScene",emit_event button.pushed. The relay can be controlled via the app. +local preference_map = { + ["backlight"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0000, + mfg_code = MFG_CODE, + data_type = data_types.Uint8, + }, + ["powerOnStatus"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0001, + mfg_code = MFG_CODE, + data_type = data_types.Uint8, + }, + ["stse.changeToWirelessSwitch"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0002, + mfg_code = MFG_CODE, + data_type = data_types.Boolean + } +} + +local function is_parent_device(device) + local parent = device:get_parent_device() + return parent == nil +end + +--If it is in the switch mode, only the switch will be displayed. If it is in the wirelessSwitch mode, both the switch and the button will be displayed. +local function toggle_button_visibility(device, show) + local is_parent = is_parent_device(device) + if is_parent then + if show then + device:try_update_metadata({profile = "switch-button-light-restore-wireless"}) + else + device:try_update_metadata({profile = "switch-light-restore-wireless"}) + end + else + if show then + device:try_update_metadata({profile = "switch-button-wireless"}) + else + device:try_update_metadata({profile = "switch-wireless"}) + end + end +end + +--When stse.changeToWirelessSwitch switching to the wirelessSwitch mode to listen for profile changes +local function listen_profile_button_transition(device, args) + local current_has = device:supports_capability(capabilities.button, "main") + + local old_has = false + if args and args.old_st_store and args.old_st_store.profile then + local old_main = args.old_st_store.profile.components.main + if old_main and old_main.capabilities then + old_has = old_main.capabilities["button"] ~= nil + end + end + --Capabilities button from non-existent to existing + if not old_has and current_has then + device:emit_event(capabilities.button.numberOfButtons({value = 1}, {visibility = {displayed = false}})) + device:emit_event(capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}})) + end +end + +local function device_info_changed(driver, device, event, args) + listen_profile_button_transition(device, args) + + local preferences = device.preferences + local old_preferences = args.old_st_store.preferences + if preferences ~= nil then + for id, attr in pairs(preference_map) do + local old_value = old_preferences[id] + local value = preferences[id] + if value ~= nil and value ~= old_value then + if attr.data_type == data_types.Uint8 then + value = tonumber(value) + end + device:send(cluster_base.write_manufacturer_specific_attribute(device, attr.cluster_id, attr.attribute_id, + attr.mfg_code, attr.data_type, value)) + --Switch to the corresponding profile based on stse.changeToWirelessSwitch + if id == "stse.changeToWirelessSwitch" then + toggle_button_visibility(device, value) + end + end + end + end +end + +local function get_children_amount(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.children + end + end +end + +local function get_button_amount(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.buttons + end + end +end + +local function get_child_profile_name(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.child_profile + end + end +end + +local function find_child(parent, ep_id) + return parent:get_child_by_parent_assigned_key(string.format("%02X", ep_id)) +end + +--Create composite switches such as 4+4, 1+1, 4+0, 3+0 children+button +local function device_added(driver, device) + -- Create the corresponding number of child devices based on the value of fingerprint.children + if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + local children_amount = get_children_amount(device) + if children_amount >= 2 then + for i = 2, children_amount, 1 do + if find_child(device, i) == nil then + local name = string.format("%s%d", string.sub(device.label, 0, -2), i) + local child_profile = get_child_profile_name(device) + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = child_profile, + parent_device_id = device.id, + parent_assigned_child_key = string.format("%02X", i), + vendor_provided_label = name + } + driver:try_create_device(metadata) + end + end + end + -- Create the corresponding number of button devices based on the value of fingerprint.buttons + local button_amount = get_button_amount(device) + if button_amount >= 1 then + for i = children_amount + 1, children_amount + button_amount, 1 do + if find_child(device, i) == nil then + local name = string.format("%s%d", string.sub(device.label, 0, -2), i) + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = "button", + parent_device_id = device.id, + parent_assigned_child_key = string.format("%02X", i), + vendor_provided_label = name, + } + driver:try_create_device(metadata) + end + end + end + elseif device.network_type == "DEVICE_EDGE_CHILD" then + device:emit_event(capabilities.button.numberOfButtons({ value = 1 }, + { visibility = { displayed = false } })) + device:emit_event(capabilities.button.supportedButtonValues({ "pushed" }, + { visibility = { displayed = false } })) + end +end + +local function scenes_cluster_handler(driver, device, zb_rx) + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, + capabilities.button.button.pushed({ state_change = true })) +end + +local function device_init(self, device) + -- for multiple switch + if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + device:set_find_child(find_child) + end +end + +local firstled_switch_handler = { + NAME = "FIRSTLED Switch Handler", + lifecycle_handlers = { + init = device_init, + added = device_added, + infoChanged = device_info_changed + }, + zigbee_handlers = { + cluster = { + [Scenes.ID] = { + [Scenes.server.commands.RecallScene.ID] = scenes_cluster_handler, + } + } + }, + can_handle = require("firstled-io.can_handle"), +} + +return firstled_switch_handler diff --git a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua index 69be094da4..b29c746d71 100644 --- a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua @@ -36,5 +36,6 @@ return { lazy_load_if_possible("frient"), lazy_load_if_possible("frient-IO"), lazy_load_if_possible("color_temp_range_handlers"), - lazy_load_if_possible("stateless_handlers") + lazy_load_if_possible("stateless_handlers"), + lazy_load_if_possible("firstled-io") } diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua new file mode 100644 index 0000000000..3a3c4782ea --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua @@ -0,0 +1,481 @@ +-- Copyright 2026 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.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +local frameCtrl = require "st.zigbee.zcl.frame_ctrl" +local device_lib = require "st.device" + +local OnOff = clusters.OnOff +local Scenes = clusters.Scenes + +local PRIVATE_CLUSTER_ID = 0xFCCA +local MFG_CODE = 0x1235 +local FINGERPRINTS = require("firstled-io.fingerprints") + +local parent_profile = t_utils.get_profile_definition("switch-button-light-restore-wireless.yml") +local child_switch_profile = t_utils.get_profile_definition("switch-button-wireless.yml") + +local function get_children_amount(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.children + end + end +end + +local function get_button_amount(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.buttons + end + end +end + +local function get_child_profile_name(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.child_profile + end + end +end + +local function find_child(parent, ep_id) + return parent:get_child_by_parent_assigned_key(string.format("%02X", ep_id)) +end +-- ====================== Mock Devices ====================== +local mock_parent = test.mock_device.build_test_zigbee_device({ + profile = parent_profile, + manufacturer = "FIRSTLED", + model = "M4S4BAC", + label = "Mirror Series 4x4 1", + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { id = 1, manufacturer = "FIRSTLED", model = "M4S4BAC", server_clusters = { 0x0004, 0x0006 } } + } +}) + +local mock_children = {} + +for i = 2, 4 do + local name = string.format("%s%d", string.sub("Mirror Series 4x4 1", 0, -2), i) + table.insert(mock_children, test.mock_device.build_test_child_device({ + type = "EDGE_CHILD", + profile = child_switch_profile, + label = name, + device_network_id = string.format("%04X:%02X", mock_parent:get_short_address(), i), + parent_device_id = mock_parent.id, + parent_assigned_child_key = string.format("%02X", i), + vendor_provided_label = name + })) +end + +local function test_init() + test.mock_device.add_test_device(mock_parent) + for _, child in ipairs(mock_children) do + test.mock_device.add_test_device(child) + end +end + +test.set_test_init_function(test_init) + +-- ====================== can_handle ====================== +test.register_coroutine_test("can_handle should return true and handler for matching device", function() + local can_handle = require("firstled-io.can_handle") + local result, handler = can_handle({}, nil, mock_parent) + assert(result == true) + assert(handler ~= nil) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("can_handle should return false for non-matching device", function() + local can_handle = require("firstled-io.can_handle") + local non_match = test.mock_device.build_test_zigbee_device({ + manufacturer = "OTHER", model = "OTHER", profile = parent_profile + }) + local result = can_handle({}, nil, non_match) + assert(result == false) + end, + { + min_api_version = 19 + } +) + +-- ====================== Lifecycle ====================== +test.register_coroutine_test("device_init should set find_child for parent", function() + test.socket.device_lifecycle:__queue_receive({mock_parent.id, "init"}) + end, + { + min_api_version = 19 + } +) + +-- ====================== device_added ====================== +test.register_coroutine_test("device_added - Zigbee Parent should create children and emit capabilities", function() + test.socket.device_lifecycle:__queue_receive({mock_parent.id, "added"}) + if mock_parent.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + local children_amount = get_children_amount(mock_parent) + if children_amount >= 2 then + for i = 2, children_amount, 1 do + if find_child(mock_parent, i) == nil then + local name = string.format("%s%d", string.sub(mock_parent.label, 0, -2), i) + local expected_metadata = { + type = "EDGE_CHILD", + label = name, + profile = get_child_profile_name(mock_parent), + parent_device_id = mock_parent.id, + parent_assigned_child_key = string.format("%02X", i), + } + mock_parent:expect_device_create(expected_metadata) + end + end + end + local button_amount = get_button_amount(mock_parent) + if button_amount >= 1 then + for i = children_amount + 1, children_amount + button_amount, 1 do + if find_child(mock_parent, i) == nil then + local name = string.format("%s%d", string.sub(mock_parent.label, 0, -2), i) + local expected_metadata = { + type = "EDGE_CHILD", + label = name, + profile = "button", + parent_device_id = mock_parent.id, + parent_assigned_child_key = string.format("%02X", i), + } + mock_parent:expect_device_create(expected_metadata) + end + end + end + + elseif mock_parent.network_type == "DEVICE_EDGE_CHILD" then + test.socket.capability:__expect_send(mock_parent:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_parent:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) + end + end, + { + min_api_version = 19 + } +) + +local function test_device_added_child(ep, name) + test.register_coroutine_test(name, function() + local child = mock_children[ep-1] + test.socket.device_lifecycle:__queue_receive({child.id, "added"}) + + test.socket.capability:__expect_send(child:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }))) + + test.socket.capability:__expect_send(child:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) + end, + { + min_api_version = 19 + } +) +end + +for ep = 2, 4 do + test_device_added_child(ep, "test_device_added_child endpoint " .. ep) +end + +-- ====================== Preferences ====================== +test.register_coroutine_test("infoChanged - backlight 0", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { backlight = "0" }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE, data_types.Uint8, 0) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - backlight 1", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { backlight = "1" }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE, data_types.Uint8, 1) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - backlight 2", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { backlight = "2" }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE, data_types.Uint8, 2) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - powerOnStatus 0", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { powerOnStatus = "0" }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE, data_types.Uint8, 0) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - powerOnStatus", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { powerOnStatus = "1" }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE, data_types.Uint8, 1) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - powerOnStatus 2", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { powerOnStatus = "2" }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE, data_types.Uint8, 2) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - stse.changeToWirelessSwitch true", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { ["stse.changeToWirelessSwitch"] = true }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE, data_types.Boolean, true) + }) + mock_parent:expect_metadata_update({ profile = "switch-button-light-restore-wireless" }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - stse.changeToWirelessSwitch false", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { ["stse.changeToWirelessSwitch"] = false }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE, data_types.Boolean, false) + }) + mock_parent:expect_metadata_update({ profile = "switch-light-restore-wireless" }) + end, + { + min_api_version = 19 + } +) + +local function test_child_changeToWirelessSwitch_true(ep, name) + test.register_coroutine_test(name, function() + test.socket.device_lifecycle:__queue_receive(mock_children[ep]:generate_info_changed({ preferences = { ["stse.changeToWirelessSwitch"] = true }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE, data_types.Boolean, true):to_endpoint(ep+1) + }) + mock_children[ep]:expect_metadata_update({ profile = "switch-button-wireless" }) + end, + { + min_api_version = 19 + } + ) +end + +local function test_child_changeToWirelessSwitch_false(ep, name) + test.register_coroutine_test(name, function() + test.socket.device_lifecycle:__queue_receive(mock_children[ep]:generate_info_changed({ preferences = { ["stse.changeToWirelessSwitch"] = false }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE, data_types.Boolean, false):to_endpoint(ep+1) + }) + mock_children[ep]:expect_metadata_update({ profile = "switch-wireless" }) + end, + { + min_api_version = 19 + } + ) +end + +for ep = 1, 3 do + test_child_changeToWirelessSwitch_true(ep, "children infoChanged - stse.changeToWirelessSwitch true " .. ep + 1) +end + +for ep = 1, 3 do + test_child_changeToWirelessSwitch_false(ep, "children infoChanged - stse.changeToWirelessSwitch false " .. ep + 1) +end + +-- ====================== Commands ====================== +test.register_message_test("Parent device - On command", { + { channel = "device_lifecycle", direction = "receive", message = { mock_parent.id, "init" }}, + { channel = "capability", direction = "receive", message = { mock_parent.id, { capability = "switch", component = "main", command = "on", args = {} }}}, + { channel = "devices", direction = "send", message = { "register_native_capability_cmd_handler", { device_uuid = mock_parent.id, capability_id = "switch", capability_cmd_id = "on" }}}, + { channel = "zigbee", direction = "send", message = { mock_parent.id, OnOff.server.commands.On(mock_parent):to_endpoint(0x01) }} + }, + { + min_api_version = 19 + } +) + +test.register_message_test("Parent device - Off command", { + { channel = "capability", direction = "receive", message = { mock_parent.id, { capability = "switch", component = "main", command = "off", args = {} }}}, + { channel = "devices", direction = "send", message = { "register_native_capability_cmd_handler", { device_uuid = mock_parent.id, capability_id = "switch", capability_cmd_id = "off" }}}, + { channel = "zigbee", direction = "send", message = { mock_parent.id, OnOff.server.commands.Off(mock_parent):to_endpoint(0x01) }} + }, + { + min_api_version = 19 + } +) + +-- ====================== Attribute Reports ====================== +test.register_coroutine_test( + "OnOff report on parent endpoint", + function() + test.socket.device_lifecycle:__queue_receive({mock_parent.id, "init"}) + + local report = OnOff.attributes.OnOff:build_test_attr_report(mock_parent, true):from_endpoint(0x01) + test.socket.zigbee:__queue_receive({mock_parent.id, report}) + + test.socket.capability:__expect_send(mock_parent:generate_test_message("main", capabilities.switch.switch.on())) + mock_parent:expect_native_attr_handler_registration("switch", "switch") + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "OnOff report off parent endpoint", + function() + test.socket.device_lifecycle:__queue_receive({mock_parent.id, "init"}) + + local report = OnOff.attributes.OnOff:build_test_attr_report(mock_parent, false):from_endpoint(0x01) + test.socket.zigbee:__queue_receive({mock_parent.id, report}) + + test.socket.capability:__expect_send(mock_parent:generate_test_message("main", capabilities.switch.switch.off())) + mock_parent:expect_native_attr_handler_registration("switch", "switch") + end, + { + min_api_version = 19 + } +) + +local function test_on_cmd(ep, name) + test.register_message_test(name, { + { channel = "capability", direction = "receive", message = { mock_children[ep].id, { capability = "switch", component = "main", command = "on", args = {} }}}, + { channel = "devices", direction = "send", message = { "register_native_capability_cmd_handler", { device_uuid = mock_children[ep].id, capability_id = "switch", capability_cmd_id = "on" }}}, + { channel = "zigbee", direction = "send", message = { mock_parent.id, OnOff.server.commands.On(mock_parent):to_endpoint(ep+1) }} + }, + { + min_api_version = 19 + }) +end + +local function test_off_cmd(ep, name) + test.register_message_test(name, { + { channel = "capability", direction = "receive", message = { mock_children[ep].id, { capability = "switch", component = "main", command = "off", args = {} }}}, + { channel = "devices", direction = "send", message = { "register_native_capability_cmd_handler", { device_uuid = mock_children[ep].id, capability_id = "switch", capability_cmd_id = "off" }}}, + { channel = "zigbee", direction = "send", message = { mock_parent.id, OnOff.server.commands.Off(mock_parent):to_endpoint(ep+1) }} + }, + { + min_api_version = 19 + }) +end + +local function test_onoff_report_on_cmd(ep, name) + test.register_coroutine_test( + name, + function() + test.socket.device_lifecycle:__queue_receive({mock_parent.id, "init"}) + + local report = OnOff.attributes.OnOff:build_test_attr_report(mock_parent, true):from_endpoint(ep+1) + test.socket.zigbee:__queue_receive({mock_children[ep].id, report}) + + test.socket.capability:__expect_send(mock_children[ep]:generate_test_message("main", capabilities.switch.switch.on())) + mock_children[ep]:expect_native_attr_handler_registration("switch", "switch") + end, + { + min_api_version = 19 + } + ) +end + +local function test_onoff_report_off_cmd(ep, name) + test.register_coroutine_test( + name, + function() + test.socket.device_lifecycle:__queue_receive({mock_parent.id, "init"}) + + local report = OnOff.attributes.OnOff:build_test_attr_report(mock_parent, false):from_endpoint(ep+1) + test.socket.zigbee:__queue_receive({mock_children[ep].id, report}) + + test.socket.capability:__expect_send(mock_children[ep]:generate_test_message("main", capabilities.switch.switch.off())) + mock_children[ep]:expect_native_attr_handler_registration("switch", "switch") + end, + { + min_api_version = 19 + } + ) +end + +-- ====================== RecallScene ====================== +local function test_recall_scene(ep, name) + test.register_coroutine_test(name, function() + local cmd = Scenes.server.commands.RecallScene.build_test_rx(mock_parent, 0xF0F0, ep) + cmd.body.zcl_header.frame_ctrl = frameCtrl(0x11) + test.socket.zigbee:__queue_receive({ mock_parent.id, cmd }) + test.socket.capability:__expect_send(mock_parent:generate_test_message("main", + capabilities.button.button.pushed({ state_change = true }))) + end, + { + min_api_version = 19 + }) +end + +local function test_child_recall_scene(ep, name) + test.register_coroutine_test(name, function() + local cmd = Scenes.server.commands.RecallScene.build_test_rx(mock_parent, 0xF0F0, ep + 1) + cmd.body.zcl_header.frame_ctrl = frameCtrl(0x11) + test.socket.zigbee:__queue_receive({ mock_children[ep].id, cmd }) + test.socket.capability:__expect_send(mock_children[ep]:generate_test_message("main", + capabilities.button.button.pushed({ state_change = true }))) + end, + { + min_api_version = 19 + }) +end + +for ep = 1, 1 do + test_recall_scene(ep, "RecallScene on parent endpoint " .. ep) +end + +for ep = 1, 3 do + test_child_recall_scene(ep, "test_child_recall_scene on endpoint " .. ep + 1) +end + +for ep = 1, 3 do + test_on_cmd(ep, "children test_on_cmd on endpoint " .. ep + 1) +end + +for ep = 1, 3 do + test_off_cmd(ep, "children test_off_cmd on endpoint " .. ep + 1) +end + +for ep = 1, 3 do + test_onoff_report_on_cmd(ep, "children test_onoff_report_on_cmd endpoint " .. ep + 1) +end + +for ep = 1, 3 do + test_onoff_report_off_cmd(ep, "children test_onoff_report_off_cmd endpoint " .. ep + 1) +end + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index a67fdfeddf..d1a3ed3369 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -140,3 +140,4 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 +"Mirror Series 4x4 1",镜系列4x4 1 From 5f1eb231178c89c60692569c5f1db2450859ece5 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:31:53 -0500 Subject: [PATCH 406/538] update new lifx fingerprints to include colorControl (#3034) --- .../matter-switch/fingerprints.yml | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 47409926af..e4602b4178 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1351,172 +1351,172 @@ matterManufacturer: deviceLabel: LIFX Supercolor (A19) vendorId: 0x1423 productId: 0x00A3 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/118" deviceLabel: LIFX Lightstrip vendorId: 0x1423 productId: 0x0076 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/221" deviceLabel: LIFX Spot vendorId: 0x1423 productId: 0x00DD - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/144" deviceLabel: LIFX String vendorId: 0x1423 productId: 0x0090 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/216" deviceLabel: LIFX Candle Color (B10) vendorId: 0x1423 productId: 0x00D8 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/225" deviceLabel: LIFX PAR38 vendorId: 0x1423 productId: 0x00E1 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/186" deviceLabel: LIFX Candle Color vendorId: 0x1423 productId: 0x00BA - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/202" deviceLabel: LIFX Ceiling 13x26 vendorId: 0x1423 productId: 0x00CA - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/143" deviceLabel: LIFX String vendorId: 0x1423 productId: 0x008F - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/166" deviceLabel: LIFX Supercolour (BR30) vendorId: 0x1423 productId: 0x00A6 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/167" deviceLabel: LIFX Downlight vendorId: 0x1423 productId: 0x00A7 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/207" deviceLabel: LIFX Everyday Lightstrip vendorId: 0x1423 productId: 0x00CF - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/222" deviceLabel: LIFX Path (Round) vendorId: 0x1423 productId: 0x00DE - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/203" deviceLabel: LIFX String vendorId: 0x1423 productId: 0x00CB - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/218" deviceLabel: LIFX Tube vendorId: 0x1423 productId: 0x00DA - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/214" deviceLabel: LIFX Permanent Outdoor vendorId: 0x1423 productId: 0x00D6 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/117" deviceLabel: LIFX Lightstrip vendorId: 0x1423 productId: 0x0075 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/223" deviceLabel: LIFX Downlight (6 Retro Downlight) vendorId: 0x1423 productId: 0x00DF - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/224" deviceLabel: LIFX Downlight (90mm Downlight) vendorId: 0x1423 productId: 0x00E0 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/204" deviceLabel: LIFX String vendorId: 0x1423 productId: 0x00CC - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/206" deviceLabel: LIFX Neon vendorId: 0x1423 productId: 0x00CE - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/164" deviceLabel: LIFX Supercolor (BR30) vendorId: 0x1423 productId: 0x00A4 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/120" deviceLabel: LIFX Beam vendorId: 0x1423 productId: 0x0078 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/208" deviceLabel: LIFX Everyday Lightstrip vendorId: 0x1423 productId: 0x00D0 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/165" deviceLabel: LIFX Supercolour (A19) vendorId: 0x1423 productId: 0x00A5 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/142" deviceLabel: LIFX Neon vendorId: 0x1423 productId: 0x008E - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/141" deviceLabel: LIFX Neon vendorId: 0x1423 productId: 0x008D - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/177" deviceLabel: LIFX Ceiling vendorId: 0x1423 productId: 0x00B1 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/170" deviceLabel: LIFX Supercolour (A21) vendorId: 0x1423 productId: 0x00AA - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/205" deviceLabel: LIFX Neon vendorId: 0x1423 productId: 0x00CD - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/265" deviceLabel: Ceiling 13 vendorId: 0x1423 productId: 0x0109 - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/266" deviceLabel: LIFX Ceiling 13 vendorId: 0x1423 productId: 0x010A - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/267" deviceLabel: LIFX Mirror vendorId: 0x1423 productId: 0x010B - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level - id: "5155/268" deviceLabel: LIFX Mirror vendorId: 0x1423 productId: 0x010C - deviceProfileName: light-level-colorTemperature + deviceProfileName: light-color-level #LG - id: "4142/8784" From 922a001a2cd96e91e5477a7e1216c90dfb492ea3 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:39:40 -0500 Subject: [PATCH 407/538] persist electrical tags that are used for profiling (#3038) --- drivers/SmartThings/matter-switch/src/switch_utils/utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 8e21c8baf8..d2457bfb4d 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -431,7 +431,7 @@ function utils.set_fields_for_electrical_sensor_endpoint(device, electrical_sens 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.ELECTRICAL_TAGS, primary_associated_ep_id, tags, {persist = true}) 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 From 2067aa5a91a6b3241c7cee3d1df8b1398b60aadc Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:39:53 -0500 Subject: [PATCH 408/538] Add Initial FP400 profile and subdriver (#3024) --- .../matter-sensor/fingerprints.yml | 5 + .../matter-sensor/profiles/aqara-fp400.yml | 14 ++ .../matter-sensor/src/sensor_utils/fields.lua | 6 + .../matter-sensor/src/sensor_utils/utils.lua | 11 ++ .../matter-sensor/src/sub_drivers.lua | 1 + .../sub_drivers/aqara_fp400/can_handle.lua | 12 ++ .../src/sub_drivers/aqara_fp400/init.lua | 23 +++ .../src/test/test_matter_aqara_fp400.lua | 141 ++++++++++++++++++ 8 files changed, 213 insertions(+) create mode 100644 drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/can_handle.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index 483b92fc21..45b21dd386 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -15,6 +15,11 @@ matterManufacturer: vendorId: 0x115F productId: 0x2005 deviceProfileName: presence-illuminance-temperature-humidity-battery + - id: "4447/8201" + deviceLabel: Spatial Multi-Sensor FP400 + vendorId: 0x115F + productId: 0x2009 + deviceProfileName: aqara-fp400 #Bosch - id: 4617/12309 deviceLabel: "Door/window contact II [M]" diff --git a/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml b/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml new file mode 100644 index 0000000000..c2d5b7b037 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml @@ -0,0 +1,14 @@ +name: aqara-fp400 +components: +- id: main + capabilities: + - id: presenceSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: PresenceSensor diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua index b31b1b5c5b..232e431277 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua @@ -49,4 +49,10 @@ SensorFields.BOOLEAN_CAP_EVENT_MAP = { } } +SensorFields.vendor_overrides = { + [0x115F] = { -- AQARA_MANUFACTURER_ID + [0x2009] = { is_aqara_fp400 = true } + } +} + return SensorFields diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua index d5437410a5..513c9f4602 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua @@ -1,6 +1,8 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local fields = require "sensor_utils.fields" + local utils = {} -- Sanity check bounds for soil moisture measurement limits (percent) @@ -15,6 +17,15 @@ function utils.set_field_for_endpoint(device, field, endpoint, value, additional device:set_field(string.format("%s_%d", field, endpoint), value, additional_params) 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.tbl_contains(array, value) if value == nil then return false end for _, element in pairs(array or {}) do diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers.lua index 3bf4c46f73..aa8b046927 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers.lua @@ -6,5 +6,6 @@ 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"), + lazy_load_if_possible("sub_drivers.aqara_fp400"), } return sub_drivers diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/can_handle.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/can_handle.lua new file mode 100644 index 0000000000..d28a66f147 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_aqara_fp400(opts, driver, device) + local sensor_utils = require "sensor_utils.utils" + if sensor_utils.get_product_override_field(device, "is_aqara_fp400") then + return true, require("sub_drivers.aqara_fp400") + end + return false +end + +return is_aqara_fp400 diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua new file mode 100644 index 0000000000..5f47c86047 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua @@ -0,0 +1,23 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local Fp400LifecycleHandlers = {} + +-- overwrite to avoid unnecessary metadata update calls +function Fp400LifecycleHandlers.do_configure() end + +-- overwrite to avoid unnecessary metadata update calls +function Fp400LifecycleHandlers.driver_switched(driver, device) + device:try_update_metadata({provisioning_state = "PROVISIONED"}) +end + +local aqara_fp400_handler = { + NAME = "aqara-fp400", + lifecycle_handlers = { + doConfigure = Fp400LifecycleHandlers.do_configure, + driverSwitched = Fp400LifecycleHandlers.driver_switched, + }, + can_handle = require("sub_drivers.aqara_fp400.can_handle"), +} + +return aqara_fp400_handler diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua new file mode 100644 index 0000000000..7f384c744e --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua @@ -0,0 +1,141 @@ +-- Copyright 2026 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 matter_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.OccupancySensing.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.IlluminanceMeasurement.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- Occupancy Sensor + } + } +} + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("aqara-fp400.yml"), + manufacturer_info = { + vendor_id = 0x115F, + product_id = 0x2009, + }, + endpoints = matter_endpoints +}) + +local function subscribe_on_init(dev) + local subscribe_request = clusters.OccupancySensing.attributes.Occupancy:subscribe(dev) + subscribe_request:merge(clusters.IlluminanceMeasurement.attributes.MeasuredValue:subscribe(dev)) + return subscribe_request +end + +local function test_init() + test.socket.matter:__set_channel_ordering("relaxed") + local subscribe_request = subscribe_on_init(mock_device) + 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) + +test.register_coroutine_test( + "Test no profile change on doConfigure for FP400", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + -- The FP400 sub-driver overrides doConfigure to be a no-op + -- When doConfigure completes successfully, the framework automatically provisions the device + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Test no profile change on driverSwitched for FP400", + function() + local current_profile = mock_device.profile.id + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "driverSwitched" }) + -- The FP400 sub-driver overrides driverSwitched to only update provisioning state + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + -- Ensure profile has not changed + test.wait_for_events() + assert(mock_device.profile.id == current_profile, "Profile should not change on driverSwitched") + end, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Occupancy reports should generate correct presence messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 1, 1) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.presenceSensor.presence("present")) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 1, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.presenceSensor.presence("not present")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Illuminance reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.IlluminanceMeasurement.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 21370) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 137 })) + } + }, + { + min_api_version = 17 + } +) + +test.run_registered_tests() From 219c1d34efa5d86f724225a58723d42b16d2ccca Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 24 Jun 2026 01:28:30 +0800 Subject: [PATCH 409/538] add firstled-io_M4S4BAC (#2971) --- .../zigbee-switch/fingerprints.yml | 6 + .../switch-button-light-restore-wireless.yml | 39 ++ .../profiles/switch-button-wireless.yml | 17 + .../switch-light-restore-wireless.yml | 37 ++ .../profiles/switch-wireless.yml | 15 + .../src/firstled-io/can_handle.lua | 12 + .../src/firstled-io/fingerprints.lua | 9 + .../zigbee-switch/src/firstled-io/init.lua | 209 ++++++++ .../zigbee-switch/src/sub_drivers.lua | 3 +- .../src/test/test_firstled_switch.lua | 481 ++++++++++++++++++ tools/localizations/cn.csv | 1 + 11 files changed, 828 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-switch/profiles/switch-button-light-restore-wireless.yml create mode 100644 drivers/SmartThings/zigbee-switch/profiles/switch-button-wireless.yml create mode 100644 drivers/SmartThings/zigbee-switch/profiles/switch-light-restore-wireless.yml create mode 100644 drivers/SmartThings/zigbee-switch/profiles/switch-wireless.yml create mode 100644 drivers/SmartThings/zigbee-switch/src/firstled-io/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 90862aa61f..b44a13d3c1 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2451,6 +2451,12 @@ zigbeeManufacturer: manufacturer: JNL model: Y-K002-001 deviceProfileName: basic-switch + #FIRSTLED + - id: "FIRSTLED/M4S4BAC" + deviceLabel: Mirror Series 4x4 1 + manufacturer: FIRSTLED + model: M4S4BAC + deviceProfileName: switch-light-restore-wireless zigbeeGeneric: - id: "genericSwitch" deviceLabel: Zigbee Switch diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-button-light-restore-wireless.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-button-light-restore-wireless.yml new file mode 100644 index 0000000000..f98b552b87 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-button-light-restore-wireless.yml @@ -0,0 +1,39 @@ +name: switch-button-light-restore-wireless +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: button + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController +preferences: + - title: "背光灯(backlight/백라이트)" + name: backlight + description: "背光灯(backlight/백라이트)" + required: false + preferenceType: enumeration + definition: + options: + 0: "关闭(off/닫다)" + 1: "打开(on/열다)" + 2: "人体接近感应(proximity detection/근접 감지)" + default: 2 + - title: "开关上电状态(relay powerOn state)" + name: powerOnStatus + description: "开关上电状态(relay powerOn state/릴레이 초기 동작 상태)" + required: false + preferenceType: enumeration + definition: + options: + 0: "关闭(off/닫다)" + 1: "打开(on/열다)" + 2: "恢复记忆状态(restore/복원)" + default: 2 + - preferenceId: stse.changeToWirelessSwitch + explicit: true diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-button-wireless.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-button-wireless.yml new file mode 100644 index 0000000000..c48290e1a8 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-button-wireless.yml @@ -0,0 +1,17 @@ +name: switch-button-wireless +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: button + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController +preferences: + - preferenceId: stse.changeToWirelessSwitch + explicit: true diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-light-restore-wireless.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-light-restore-wireless.yml new file mode 100644 index 0000000000..b2e002d690 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-light-restore-wireless.yml @@ -0,0 +1,37 @@ +name: switch-light-restore-wireless +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch +preferences: + - title: "背光灯(backlight/백라이트)" + name: backlight + description: "背光灯(backlight/백라이트)" + required: false + preferenceType: enumeration + definition: + options: + 0: "关闭(off/닫다)" + 1: "打开(on/열다)" + 2: "人体接近感应(proximity detection/근접 감지)" + default: 2 + - title: "开关上电状态(relay powerOn state)" + name: powerOnStatus + description: "开关上电状态(relay powerOn state/릴레이 초기 동작 상태)" + required: false + preferenceType: enumeration + definition: + options: + 0: "关闭(off/닫다)" + 1: "打开(on/열다)" + 2: "恢复记忆状态(restore/복원)" + default: 2 + - preferenceId: stse.changeToWirelessSwitch + explicit: true diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-wireless.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-wireless.yml new file mode 100644 index 0000000000..cb4be470f9 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-wireless.yml @@ -0,0 +1,15 @@ +name: switch-wireless +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch +preferences: + - preferenceId: stse.changeToWirelessSwitch + explicit: true diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/can_handle.lua new file mode 100644 index 0000000000..5ad2d32a4b --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local FINGERPRINTS = require("firstled-io.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("firstled-io") + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua new file mode 100644 index 0000000000..2586782642 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +--The number of children determines the number of sub-devices to be created. Each sub-device has the capability to switch between a switch and a button. +--The number of buttons determines how many buttons devices will be created. +--The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. +return { + { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" } +} diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua new file mode 100644 index 0000000000..6348f254b2 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua @@ -0,0 +1,209 @@ +-- Copyright 2026 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" +local data_types = require "st.zigbee.data_types" +local zcl_clusters = require "st.zigbee.zcl.clusters" + +local Scenes = zcl_clusters.Scenes +local PRIVATE_CLUSTER_ID = 0xFCCA +local MFG_CODE = 0x1235 +local FINGERPRINTS = require("firstled-io.fingerprints") + +--stse.changeToWirelessSwitch +--switch mode:The local switch and the app can control the relay.The button capability is not working. +--wirelessSwitch mode:The local switch does not control the relay. Once triggered, it will report to the system as "RecallScene",emit_event button.pushed. The relay can be controlled via the app. +local preference_map = { + ["backlight"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0000, + mfg_code = MFG_CODE, + data_type = data_types.Uint8, + }, + ["powerOnStatus"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0001, + mfg_code = MFG_CODE, + data_type = data_types.Uint8, + }, + ["stse.changeToWirelessSwitch"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0002, + mfg_code = MFG_CODE, + data_type = data_types.Boolean + } +} + +local function is_parent_device(device) + local parent = device:get_parent_device() + return parent == nil +end + +--If it is in the switch mode, only the switch will be displayed. If it is in the wirelessSwitch mode, both the switch and the button will be displayed. +local function toggle_button_visibility(device, show) + local is_parent = is_parent_device(device) + if is_parent then + if show then + device:try_update_metadata({profile = "switch-button-light-restore-wireless"}) + else + device:try_update_metadata({profile = "switch-light-restore-wireless"}) + end + else + if show then + device:try_update_metadata({profile = "switch-button-wireless"}) + else + device:try_update_metadata({profile = "switch-wireless"}) + end + end +end + +--When stse.changeToWirelessSwitch switching to the wirelessSwitch mode to listen for profile changes +local function listen_profile_button_transition(device, args) + local current_has = device:supports_capability(capabilities.button, "main") + + local old_has = false + if args and args.old_st_store and args.old_st_store.profile then + local old_main = args.old_st_store.profile.components.main + if old_main and old_main.capabilities then + old_has = old_main.capabilities["button"] ~= nil + end + end + --Capabilities button from non-existent to existing + if not old_has and current_has then + device:emit_event(capabilities.button.numberOfButtons({value = 1}, {visibility = {displayed = false}})) + device:emit_event(capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}})) + end +end + +local function device_info_changed(driver, device, event, args) + listen_profile_button_transition(device, args) + + local preferences = device.preferences + local old_preferences = args.old_st_store.preferences + if preferences ~= nil then + for id, attr in pairs(preference_map) do + local old_value = old_preferences[id] + local value = preferences[id] + if value ~= nil and value ~= old_value then + if attr.data_type == data_types.Uint8 then + value = tonumber(value) + end + device:send(cluster_base.write_manufacturer_specific_attribute(device, attr.cluster_id, attr.attribute_id, + attr.mfg_code, attr.data_type, value)) + --Switch to the corresponding profile based on stse.changeToWirelessSwitch + if id == "stse.changeToWirelessSwitch" then + toggle_button_visibility(device, value) + end + end + end + end +end + +local function get_children_amount(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.children + end + end +end + +local function get_button_amount(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.buttons + end + end +end + +local function get_child_profile_name(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.child_profile + end + end +end + +local function find_child(parent, ep_id) + return parent:get_child_by_parent_assigned_key(string.format("%02X", ep_id)) +end + +--Create composite switches such as 4+4, 1+1, 4+0, 3+0 children+button +local function device_added(driver, device) + -- Create the corresponding number of child devices based on the value of fingerprint.children + if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + local children_amount = get_children_amount(device) + if children_amount >= 2 then + for i = 2, children_amount, 1 do + if find_child(device, i) == nil then + local name = string.format("%s%d", string.sub(device.label, 0, -2), i) + local child_profile = get_child_profile_name(device) + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = child_profile, + parent_device_id = device.id, + parent_assigned_child_key = string.format("%02X", i), + vendor_provided_label = name + } + driver:try_create_device(metadata) + end + end + end + -- Create the corresponding number of button devices based on the value of fingerprint.buttons + local button_amount = get_button_amount(device) + if button_amount >= 1 then + for i = children_amount + 1, children_amount + button_amount, 1 do + if find_child(device, i) == nil then + local name = string.format("%s%d", string.sub(device.label, 0, -2), i) + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = "button", + parent_device_id = device.id, + parent_assigned_child_key = string.format("%02X", i), + vendor_provided_label = name, + } + driver:try_create_device(metadata) + end + end + end + elseif device.network_type == "DEVICE_EDGE_CHILD" then + device:emit_event(capabilities.button.numberOfButtons({ value = 1 }, + { visibility = { displayed = false } })) + device:emit_event(capabilities.button.supportedButtonValues({ "pushed" }, + { visibility = { displayed = false } })) + end +end + +local function scenes_cluster_handler(driver, device, zb_rx) + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, + capabilities.button.button.pushed({ state_change = true })) +end + +local function device_init(self, device) + -- for multiple switch + if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + device:set_find_child(find_child) + end +end + +local firstled_switch_handler = { + NAME = "FIRSTLED Switch Handler", + lifecycle_handlers = { + init = device_init, + added = device_added, + infoChanged = device_info_changed + }, + zigbee_handlers = { + cluster = { + [Scenes.ID] = { + [Scenes.server.commands.RecallScene.ID] = scenes_cluster_handler, + } + } + }, + can_handle = require("firstled-io.can_handle"), +} + +return firstled_switch_handler diff --git a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua index 69be094da4..b29c746d71 100644 --- a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua @@ -36,5 +36,6 @@ return { lazy_load_if_possible("frient"), lazy_load_if_possible("frient-IO"), lazy_load_if_possible("color_temp_range_handlers"), - lazy_load_if_possible("stateless_handlers") + lazy_load_if_possible("stateless_handlers"), + lazy_load_if_possible("firstled-io") } diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua new file mode 100644 index 0000000000..3a3c4782ea --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua @@ -0,0 +1,481 @@ +-- Copyright 2026 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.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +local frameCtrl = require "st.zigbee.zcl.frame_ctrl" +local device_lib = require "st.device" + +local OnOff = clusters.OnOff +local Scenes = clusters.Scenes + +local PRIVATE_CLUSTER_ID = 0xFCCA +local MFG_CODE = 0x1235 +local FINGERPRINTS = require("firstled-io.fingerprints") + +local parent_profile = t_utils.get_profile_definition("switch-button-light-restore-wireless.yml") +local child_switch_profile = t_utils.get_profile_definition("switch-button-wireless.yml") + +local function get_children_amount(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.children + end + end +end + +local function get_button_amount(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.buttons + end + end +end + +local function get_child_profile_name(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.child_profile + end + end +end + +local function find_child(parent, ep_id) + return parent:get_child_by_parent_assigned_key(string.format("%02X", ep_id)) +end +-- ====================== Mock Devices ====================== +local mock_parent = test.mock_device.build_test_zigbee_device({ + profile = parent_profile, + manufacturer = "FIRSTLED", + model = "M4S4BAC", + label = "Mirror Series 4x4 1", + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { id = 1, manufacturer = "FIRSTLED", model = "M4S4BAC", server_clusters = { 0x0004, 0x0006 } } + } +}) + +local mock_children = {} + +for i = 2, 4 do + local name = string.format("%s%d", string.sub("Mirror Series 4x4 1", 0, -2), i) + table.insert(mock_children, test.mock_device.build_test_child_device({ + type = "EDGE_CHILD", + profile = child_switch_profile, + label = name, + device_network_id = string.format("%04X:%02X", mock_parent:get_short_address(), i), + parent_device_id = mock_parent.id, + parent_assigned_child_key = string.format("%02X", i), + vendor_provided_label = name + })) +end + +local function test_init() + test.mock_device.add_test_device(mock_parent) + for _, child in ipairs(mock_children) do + test.mock_device.add_test_device(child) + end +end + +test.set_test_init_function(test_init) + +-- ====================== can_handle ====================== +test.register_coroutine_test("can_handle should return true and handler for matching device", function() + local can_handle = require("firstled-io.can_handle") + local result, handler = can_handle({}, nil, mock_parent) + assert(result == true) + assert(handler ~= nil) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("can_handle should return false for non-matching device", function() + local can_handle = require("firstled-io.can_handle") + local non_match = test.mock_device.build_test_zigbee_device({ + manufacturer = "OTHER", model = "OTHER", profile = parent_profile + }) + local result = can_handle({}, nil, non_match) + assert(result == false) + end, + { + min_api_version = 19 + } +) + +-- ====================== Lifecycle ====================== +test.register_coroutine_test("device_init should set find_child for parent", function() + test.socket.device_lifecycle:__queue_receive({mock_parent.id, "init"}) + end, + { + min_api_version = 19 + } +) + +-- ====================== device_added ====================== +test.register_coroutine_test("device_added - Zigbee Parent should create children and emit capabilities", function() + test.socket.device_lifecycle:__queue_receive({mock_parent.id, "added"}) + if mock_parent.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + local children_amount = get_children_amount(mock_parent) + if children_amount >= 2 then + for i = 2, children_amount, 1 do + if find_child(mock_parent, i) == nil then + local name = string.format("%s%d", string.sub(mock_parent.label, 0, -2), i) + local expected_metadata = { + type = "EDGE_CHILD", + label = name, + profile = get_child_profile_name(mock_parent), + parent_device_id = mock_parent.id, + parent_assigned_child_key = string.format("%02X", i), + } + mock_parent:expect_device_create(expected_metadata) + end + end + end + local button_amount = get_button_amount(mock_parent) + if button_amount >= 1 then + for i = children_amount + 1, children_amount + button_amount, 1 do + if find_child(mock_parent, i) == nil then + local name = string.format("%s%d", string.sub(mock_parent.label, 0, -2), i) + local expected_metadata = { + type = "EDGE_CHILD", + label = name, + profile = "button", + parent_device_id = mock_parent.id, + parent_assigned_child_key = string.format("%02X", i), + } + mock_parent:expect_device_create(expected_metadata) + end + end + end + + elseif mock_parent.network_type == "DEVICE_EDGE_CHILD" then + test.socket.capability:__expect_send(mock_parent:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_parent:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) + end + end, + { + min_api_version = 19 + } +) + +local function test_device_added_child(ep, name) + test.register_coroutine_test(name, function() + local child = mock_children[ep-1] + test.socket.device_lifecycle:__queue_receive({child.id, "added"}) + + test.socket.capability:__expect_send(child:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }))) + + test.socket.capability:__expect_send(child:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) + end, + { + min_api_version = 19 + } +) +end + +for ep = 2, 4 do + test_device_added_child(ep, "test_device_added_child endpoint " .. ep) +end + +-- ====================== Preferences ====================== +test.register_coroutine_test("infoChanged - backlight 0", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { backlight = "0" }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE, data_types.Uint8, 0) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - backlight 1", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { backlight = "1" }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE, data_types.Uint8, 1) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - backlight 2", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { backlight = "2" }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE, data_types.Uint8, 2) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - powerOnStatus 0", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { powerOnStatus = "0" }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE, data_types.Uint8, 0) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - powerOnStatus", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { powerOnStatus = "1" }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE, data_types.Uint8, 1) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - powerOnStatus 2", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { powerOnStatus = "2" }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE, data_types.Uint8, 2) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - stse.changeToWirelessSwitch true", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { ["stse.changeToWirelessSwitch"] = true }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE, data_types.Boolean, true) + }) + mock_parent:expect_metadata_update({ profile = "switch-button-light-restore-wireless" }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - stse.changeToWirelessSwitch false", function() + test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { ["stse.changeToWirelessSwitch"] = false }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE, data_types.Boolean, false) + }) + mock_parent:expect_metadata_update({ profile = "switch-light-restore-wireless" }) + end, + { + min_api_version = 19 + } +) + +local function test_child_changeToWirelessSwitch_true(ep, name) + test.register_coroutine_test(name, function() + test.socket.device_lifecycle:__queue_receive(mock_children[ep]:generate_info_changed({ preferences = { ["stse.changeToWirelessSwitch"] = true }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE, data_types.Boolean, true):to_endpoint(ep+1) + }) + mock_children[ep]:expect_metadata_update({ profile = "switch-button-wireless" }) + end, + { + min_api_version = 19 + } + ) +end + +local function test_child_changeToWirelessSwitch_false(ep, name) + test.register_coroutine_test(name, function() + test.socket.device_lifecycle:__queue_receive(mock_children[ep]:generate_info_changed({ preferences = { ["stse.changeToWirelessSwitch"] = false }})) + test.socket.zigbee:__expect_send({ mock_parent.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE, data_types.Boolean, false):to_endpoint(ep+1) + }) + mock_children[ep]:expect_metadata_update({ profile = "switch-wireless" }) + end, + { + min_api_version = 19 + } + ) +end + +for ep = 1, 3 do + test_child_changeToWirelessSwitch_true(ep, "children infoChanged - stse.changeToWirelessSwitch true " .. ep + 1) +end + +for ep = 1, 3 do + test_child_changeToWirelessSwitch_false(ep, "children infoChanged - stse.changeToWirelessSwitch false " .. ep + 1) +end + +-- ====================== Commands ====================== +test.register_message_test("Parent device - On command", { + { channel = "device_lifecycle", direction = "receive", message = { mock_parent.id, "init" }}, + { channel = "capability", direction = "receive", message = { mock_parent.id, { capability = "switch", component = "main", command = "on", args = {} }}}, + { channel = "devices", direction = "send", message = { "register_native_capability_cmd_handler", { device_uuid = mock_parent.id, capability_id = "switch", capability_cmd_id = "on" }}}, + { channel = "zigbee", direction = "send", message = { mock_parent.id, OnOff.server.commands.On(mock_parent):to_endpoint(0x01) }} + }, + { + min_api_version = 19 + } +) + +test.register_message_test("Parent device - Off command", { + { channel = "capability", direction = "receive", message = { mock_parent.id, { capability = "switch", component = "main", command = "off", args = {} }}}, + { channel = "devices", direction = "send", message = { "register_native_capability_cmd_handler", { device_uuid = mock_parent.id, capability_id = "switch", capability_cmd_id = "off" }}}, + { channel = "zigbee", direction = "send", message = { mock_parent.id, OnOff.server.commands.Off(mock_parent):to_endpoint(0x01) }} + }, + { + min_api_version = 19 + } +) + +-- ====================== Attribute Reports ====================== +test.register_coroutine_test( + "OnOff report on parent endpoint", + function() + test.socket.device_lifecycle:__queue_receive({mock_parent.id, "init"}) + + local report = OnOff.attributes.OnOff:build_test_attr_report(mock_parent, true):from_endpoint(0x01) + test.socket.zigbee:__queue_receive({mock_parent.id, report}) + + test.socket.capability:__expect_send(mock_parent:generate_test_message("main", capabilities.switch.switch.on())) + mock_parent:expect_native_attr_handler_registration("switch", "switch") + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "OnOff report off parent endpoint", + function() + test.socket.device_lifecycle:__queue_receive({mock_parent.id, "init"}) + + local report = OnOff.attributes.OnOff:build_test_attr_report(mock_parent, false):from_endpoint(0x01) + test.socket.zigbee:__queue_receive({mock_parent.id, report}) + + test.socket.capability:__expect_send(mock_parent:generate_test_message("main", capabilities.switch.switch.off())) + mock_parent:expect_native_attr_handler_registration("switch", "switch") + end, + { + min_api_version = 19 + } +) + +local function test_on_cmd(ep, name) + test.register_message_test(name, { + { channel = "capability", direction = "receive", message = { mock_children[ep].id, { capability = "switch", component = "main", command = "on", args = {} }}}, + { channel = "devices", direction = "send", message = { "register_native_capability_cmd_handler", { device_uuid = mock_children[ep].id, capability_id = "switch", capability_cmd_id = "on" }}}, + { channel = "zigbee", direction = "send", message = { mock_parent.id, OnOff.server.commands.On(mock_parent):to_endpoint(ep+1) }} + }, + { + min_api_version = 19 + }) +end + +local function test_off_cmd(ep, name) + test.register_message_test(name, { + { channel = "capability", direction = "receive", message = { mock_children[ep].id, { capability = "switch", component = "main", command = "off", args = {} }}}, + { channel = "devices", direction = "send", message = { "register_native_capability_cmd_handler", { device_uuid = mock_children[ep].id, capability_id = "switch", capability_cmd_id = "off" }}}, + { channel = "zigbee", direction = "send", message = { mock_parent.id, OnOff.server.commands.Off(mock_parent):to_endpoint(ep+1) }} + }, + { + min_api_version = 19 + }) +end + +local function test_onoff_report_on_cmd(ep, name) + test.register_coroutine_test( + name, + function() + test.socket.device_lifecycle:__queue_receive({mock_parent.id, "init"}) + + local report = OnOff.attributes.OnOff:build_test_attr_report(mock_parent, true):from_endpoint(ep+1) + test.socket.zigbee:__queue_receive({mock_children[ep].id, report}) + + test.socket.capability:__expect_send(mock_children[ep]:generate_test_message("main", capabilities.switch.switch.on())) + mock_children[ep]:expect_native_attr_handler_registration("switch", "switch") + end, + { + min_api_version = 19 + } + ) +end + +local function test_onoff_report_off_cmd(ep, name) + test.register_coroutine_test( + name, + function() + test.socket.device_lifecycle:__queue_receive({mock_parent.id, "init"}) + + local report = OnOff.attributes.OnOff:build_test_attr_report(mock_parent, false):from_endpoint(ep+1) + test.socket.zigbee:__queue_receive({mock_children[ep].id, report}) + + test.socket.capability:__expect_send(mock_children[ep]:generate_test_message("main", capabilities.switch.switch.off())) + mock_children[ep]:expect_native_attr_handler_registration("switch", "switch") + end, + { + min_api_version = 19 + } + ) +end + +-- ====================== RecallScene ====================== +local function test_recall_scene(ep, name) + test.register_coroutine_test(name, function() + local cmd = Scenes.server.commands.RecallScene.build_test_rx(mock_parent, 0xF0F0, ep) + cmd.body.zcl_header.frame_ctrl = frameCtrl(0x11) + test.socket.zigbee:__queue_receive({ mock_parent.id, cmd }) + test.socket.capability:__expect_send(mock_parent:generate_test_message("main", + capabilities.button.button.pushed({ state_change = true }))) + end, + { + min_api_version = 19 + }) +end + +local function test_child_recall_scene(ep, name) + test.register_coroutine_test(name, function() + local cmd = Scenes.server.commands.RecallScene.build_test_rx(mock_parent, 0xF0F0, ep + 1) + cmd.body.zcl_header.frame_ctrl = frameCtrl(0x11) + test.socket.zigbee:__queue_receive({ mock_children[ep].id, cmd }) + test.socket.capability:__expect_send(mock_children[ep]:generate_test_message("main", + capabilities.button.button.pushed({ state_change = true }))) + end, + { + min_api_version = 19 + }) +end + +for ep = 1, 1 do + test_recall_scene(ep, "RecallScene on parent endpoint " .. ep) +end + +for ep = 1, 3 do + test_child_recall_scene(ep, "test_child_recall_scene on endpoint " .. ep + 1) +end + +for ep = 1, 3 do + test_on_cmd(ep, "children test_on_cmd on endpoint " .. ep + 1) +end + +for ep = 1, 3 do + test_off_cmd(ep, "children test_off_cmd on endpoint " .. ep + 1) +end + +for ep = 1, 3 do + test_onoff_report_on_cmd(ep, "children test_onoff_report_on_cmd endpoint " .. ep + 1) +end + +for ep = 1, 3 do + test_onoff_report_off_cmd(ep, "children test_onoff_report_off_cmd endpoint " .. ep + 1) +end + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index a67fdfeddf..d1a3ed3369 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -140,3 +140,4 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 +"Mirror Series 4x4 1",镜系列4x4 1 From ba0ed5a9824b6d05609395f60ec379a3470bc225 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue, 23 Jun 2026 16:48:22 -0500 Subject: [PATCH 410/538] add global scroll state for ikea scroll (#3039) --- .../scroll_handlers/event_handlers.lua | 4 ++- .../ikea_scroll/scroll_utils/event_utils.lua | 26 +++++++++++++++++++ .../ikea_scroll/scroll_utils/fields.lua | 9 +++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua index a6a7ce9168..dbad323784 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua @@ -17,7 +17,9 @@ local function rotate_amount_event_helper(device, endpoint_id, num_presses_to_ha local scroll_direction = switch_utils.tbl_contains(scroll_fields.ENDPOINTS_UP_SCROLL, endpoint_id) and 1 or -1 local scroll_amount = st_utils.clamp_value(scroll_direction * scroll_fields.PER_SCROLL_EVENT_ROTATION * num_presses_to_handle, -100, 100) - device:emit_event_for_endpoint(endpoint_id, capabilities.knob.rotateAmount(scroll_amount, {state_change = true})) + if event_utils.is_valid_scroll_amount(device, scroll_amount) then + device:emit_event_for_endpoint(endpoint_id, capabilities.knob.rotateAmount(scroll_amount, {state_change = true})) + end end -- Used by ENDPOINTS_UP_SCROLL and ENDPOINTS_DOWN_SCROLL, not ENDPOINTS_PUSH diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua index c838ad5c67..8a7a684cf6 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua @@ -1,10 +1,36 @@ -- Copyright © 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local st_utils = require "st.utils" local clusters = require "st.matter.clusters" +local scroll_fields = require "sub_drivers.ikea_scroll.scroll_utils.fields" local IkeaScrollEventUtils = {} + +function IkeaScrollEventUtils.requeue_clear_scroll_state(device) + -- cancel any previously queued clear state actions to prevent unintended clears + if device:get_field(scroll_fields.CLEAR_STATE_TIMER) then + device.thread:cancel_timer(device:get_field(scroll_fields.CLEAR_STATE_TIMER)) + end + local new_timer = device.thread:call_with_delay(scroll_fields.CLEAR_STATE_DELAY_S, function() + device:set_field(scroll_fields.GLOBAL_ROTATE_AMOUNT_STATE, 0) + end) + device:set_field(scroll_fields.CLEAR_STATE_TIMER, new_timer) +end + +function IkeaScrollEventUtils.is_valid_scroll_amount(device, scroll_amount) + local global_rotate_amount_state = device:get_field(scroll_fields.GLOBAL_ROTATE_AMOUNT_STATE) or 0 + local is_rotate_amount_state_at_bounds = (scroll_amount < 0 and global_rotate_amount_state <= -100) or (scroll_amount > 0 and global_rotate_amount_state >= 100) + if is_rotate_amount_state_at_bounds then + return false + end + + device:set_field(scroll_fields.GLOBAL_ROTATE_AMOUNT_STATE, st_utils.clamp_value(global_rotate_amount_state + scroll_amount, -100, 100)) + IkeaScrollEventUtils.requeue_clear_scroll_state(device) + return true +end + -- inspect all info blocks to find the last one that is not an InitialPress event. We will -- only try to emit a rotateAmount event if the current info block being handled is that last one. function IkeaScrollEventUtils.is_last_valid_info_block(cur_info_block_event_id, cur_info_block_value, info_blocks) 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 index 451111f0fb..27e0dbcd6d 100644 --- 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 @@ -24,6 +24,15 @@ IkeaScrollFields.PER_SCROLL_EVENT_ROTATION = 6 -- Field to track the latest number of presses handled during a single scroll event sequence IkeaScrollFields.LATEST_NUMBER_OF_PRESSES_HANDLED = "__latest_number_of_presses_handled" +-- Field to track the global rotate amount state for the device to ensure no scroll events mapped outside of state bounds are emitted +IkeaScrollFields.GLOBAL_ROTATE_AMOUNT_STATE = "__global_rotate_amount_state" + +-- Stores a timer object, which is required to cancel a timer early +IkeaScrollFields.CLEAR_STATE_TIMER = "__clear_state_timer" + +-- Delay in seconds to wait before clearing the global rotate amount state after the last scroll event +IkeaScrollFields.CLEAR_STATE_DELAY_S = 8 + -- Required Events for the ENDPOINTS_PUSH. IkeaScrollFields.switch_press_subscribed_events = { clusters.Switch.events.InitialPress.ID, From ca15822ed89e44216814f5ed499b9720e6d17b9e Mon Sep 17 00:00:00 2001 From: seojune79 <119141897+seojune79@users.noreply.github.com> Date: Wed, 24 Jun 2026 08:01:03 +0900 Subject: [PATCH 411/538] Aqara Bath Heater: localize device profiles (#3029) --- .../profiles/aqara-bath-heater.yml | 150 +----------------- drivers/Aqara/aqara-bath-heater/src/init.lua | 1 + .../src/test/test_aqara_bath_heater.lua | 2 + 3 files changed, 6 insertions(+), 147 deletions(-) diff --git a/drivers/Aqara/aqara-bath-heater/profiles/aqara-bath-heater.yml b/drivers/Aqara/aqara-bath-heater/profiles/aqara-bath-heater.yml index d130be5947..2acda35491 100644 --- a/drivers/Aqara/aqara-bath-heater/profiles/aqara-bath-heater.yml +++ b/drivers/Aqara/aqara-bath-heater/profiles/aqara-bath-heater.yml @@ -8,165 +8,18 @@ components: version: 1 - id: colorTemperature version: 1 - config: - values: - - key: "colorTemperature.value" - range: [2700, 6500] - id: thermostatMode version: 1 - config: - values: - - key: "thermostatMode.value" - enabledValues: - - "off" - - "heat" - - "dryair" - - "cool" - - "fanonly" - id: thermostatHeatingSetpoint version: 1 - config: - values: - - key: "thermostatHeatingSetpoint.value" - range: [16, 45] - unit: "C" - id: fanOscillationMode version: 1 - config: - values: - - key: "fanOscillationMode.value" - enabledValues: - - "swing" - - "fixed" - id: fanMode version: 1 - config: - values: - - key: "fanMode.value" - enabledValues: - - "low" - - "medium" - - "high" - id: refresh version: 1 categories: - name: Thermostat -deviceConfig: - dashboard: - states: - - component: main - capability: switch - version: 1 - - component: main - capability: fanMode - version: 1 - actions: - - component: main - capability: switch - version: 1 - detailView: - - component: main - capability: switch - version: 1 - - component: main - capability: switchLevel - version: 1 - - component: main - capability: colorTemperature - version: 1 - - component: main - capability: thermostatMode - version: 1 - - component: main - capability: thermostatHeatingSetpoint - version: 1 - visibleCondition: - component: main - capability: thermostatMode - version: 1 - value: thermostatMode.value - operator: EQUALS - operand: "heat" - - component: main - capability: fanOscillationMode - version: 1 - visibleCondition: - component: main - capability: thermostatMode - version: 1 - value: thermostatMode.value - operator: ONE_OF - operand: '["heat", "dryair", "cool"]' - - component: main - capability: fanMode - version: 1 - visibleCondition: - component: main - capability: thermostatMode - version: 1 - value: thermostatMode.value - operator: ONE_OF - operand: '["heat", "dryair", "cool", "fanonly"]' - - component: main - capability: refresh - version: 1 - automation: - conditions: - - component: main - capability: switch - version: 1 - - component: main - capability: switchLevel - version: 1 - - component: main - capability: colorTemperature - version: 1 - - component: main - capability: thermostatMode - version: 1 - - component: main - capability: thermostatHeatingSetpoint - version: 1 - - component: main - capability: fanOscillationMode - version: 1 - - component: main - capability: fanMode - version: 1 - values: - - key: "fanMode.value" - enabledValues: - - "low" - - "medium" - - "high" - actions: - - component: main - capability: switch - version: 1 - - component: main - capability: switchLevel - version: 1 - - component: main - capability: colorTemperature - version: 1 - - component: main - capability: thermostatMode - version: 1 - - component: main - capability: thermostatHeatingSetpoint - version: 1 - - component: main - capability: fanOscillationMode - version: 1 - - component: main - capability: fanMode - version: 1 - values: - - key: "setFanMode.fanMode" - enabledValues: - - "low" - - "medium" - - "high" preferences: - preferenceId: stse.nightLightMode explicit: true @@ -178,3 +31,6 @@ preferences: explicit: true - preferenceId: stse.thermostatCtrl explicit: true +metadata: + mnmn: SolutionsEngineering + vid: SmartThings-smartthings-Aqara_Bath_Heater diff --git a/drivers/Aqara/aqara-bath-heater/src/init.lua b/drivers/Aqara/aqara-bath-heater/src/init.lua index f543a704a8..62007b537c 100644 --- a/drivers/Aqara/aqara-bath-heater/src/init.lua +++ b/drivers/Aqara/aqara-bath-heater/src/init.lua @@ -429,6 +429,7 @@ local function device_added(driver, device) capabilities.fanOscillationMode.fanOscillationMode.NAME) == nil then device:emit_event(capabilities.fanOscillationMode.fanOscillationMode(OSC.SWING)) end + device:emit_event(capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} })) end local function send_night_light(device, new) diff --git a/drivers/Aqara/aqara-bath-heater/src/test/test_aqara_bath_heater.lua b/drivers/Aqara/aqara-bath-heater/src/test/test_aqara_bath_heater.lua index 17e85b716b..8dfaa66974 100644 --- a/drivers/Aqara/aqara-bath-heater/src/test/test_aqara_bath_heater.lua +++ b/drivers/Aqara/aqara-bath-heater/src/test/test_aqara_bath_heater.lua @@ -619,6 +619,8 @@ test.register_coroutine_test( capabilities.fanMode.fanMode("medium"))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fanOscillationMode.fanOscillationMode("swing"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} }))) end ) From d973a4eb74395ac295065f9665ae7d9a4c3409e7 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 24 Jun 2026 13:58:38 -0500 Subject: [PATCH 412/538] Adding HCv0.61 to Jenkins driver test matrix --- .github/workflows/jenkins-driver-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jenkins-driver-tests.yml b/.github/workflows/jenkins-driver-tests.yml index b9fc07f091..6494eadd15 100644 --- a/.github/workflows/jenkins-driver-tests.yml +++ b/.github/workflows/jenkins-driver-tests.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: version: - [ 59, 60, dev] + [ 59, 60, 61, dev] runs-on: ubuntu-latest steps: From 1a1bca77f2700110b963ff95d591cb15cbb2cd21 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 25 Jun 2026 11:22:07 -0500 Subject: [PATCH 413/538] WWSTCERT-12419 Dual Socket Smart Plug (#3046) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index e4602b4178..b17f2578cb 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -557,6 +557,11 @@ matterManufacturer: vendorId: 0x1339 productId: 0x0056 deviceProfileName: light-color-level + - id: "4921/85" + deviceLabel: Dual Socket Smart Plug + vendorId: 0x1339 + productId: 0x0055 + deviceProfileName: plug-binary #Govee - id: "4999/24740" deviceLabel: Govee Square Ceiling Light (12 inch) From 47443d1cf4ab5bf1073e61d3d5463b85b3513bc6 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 25 Jun 2026 11:44:25 -0500 Subject: [PATCH 414/538] WWSTCERT-12246 Floor Lamp (#3047) --- drivers/SmartThings/matter-switch/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index b17f2578cb..8637b37713 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -4174,6 +4174,12 @@ matterManufacturer: vendorId: 0x1392 productId: 0x0106 deviceProfileName: plug-power-energy-powerConsumption +#GIANT LIGHTING + - id: "5707/1" + deviceLabel: Floor Lamp + vendorId: 0x164B + productId: 0x0001 + deviceProfileName: light-color-level matterGeneric: From 2ecb5a9526afb2deda800b204987028d905c08b5 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 25 Jun 2026 12:02:21 -0500 Subject: [PATCH 415/538] WWSTCERT-12397 GELUBU Door Lock R60 (#3041) --- .../SmartThings/zigbee-lock/fingerprints.yml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/SmartThings/zigbee-lock/fingerprints.yml b/drivers/SmartThings/zigbee-lock/fingerprints.yml index 083dd3a11e..309a0f2883 100644 --- a/drivers/SmartThings/zigbee-lock/fingerprints.yml +++ b/drivers/SmartThings/zigbee-lock/fingerprints.yml @@ -10,6 +10,26 @@ zigbeeManufacturer: manufacturer: GELUBU model: G30 deviceProfileName: lock-battery + - id: "GELUBU/R60" + deviceLabel: GELUBU Door Lock R60 + manufacturer: GELUBU + model: R60 + deviceProfileName: lock-battery + - id: "GELUBU/R50" + deviceLabel: GELUBU Door Lock R50 + manufacturer: GELUBU + model: R50 + deviceProfileName: lock-battery + - id: "GELUBU/P90" + deviceLabel: GELUBU Door Lock P90 + manufacturer: GELUBU + model: P90 + deviceProfileName: lock-battery + - id: "GELUBU/R71" + deviceLabel: GELUBU Door Lock R71 + manufacturer: GELUBU + model: R71 + deviceProfileName: lock-battery # YALE - id: "Yale YRD220/240" deviceLabel: "Yale Door Lock" From dae7b10c336e69ea87ac848edc893d9d2fbf64d3 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Thu, 25 Jun 2026 11:36:39 -0500 Subject: [PATCH 416/538] firstled_switch: Updated min_api_version to 20 for out of date tests --- .../src/test/test_firstled_switch.lua | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua index 3a3c4782ea..a9bc2ac8fe 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua @@ -195,7 +195,7 @@ test.register_coroutine_test("infoChanged - backlight 0", function() }) end, { - min_api_version = 19 + min_api_version = 20 } ) @@ -206,7 +206,7 @@ test.register_coroutine_test("infoChanged - backlight 1", function() }) end, { - min_api_version = 19 + min_api_version = 20 } ) @@ -217,7 +217,7 @@ test.register_coroutine_test("infoChanged - backlight 2", function() }) end, { - min_api_version = 19 + min_api_version = 20 } ) @@ -228,18 +228,18 @@ test.register_coroutine_test("infoChanged - powerOnStatus 0", function() }) end, { - min_api_version = 19 + min_api_version = 20 } ) -test.register_coroutine_test("infoChanged - powerOnStatus", function() +test.register_coroutine_test("infoChanged - powerOnStatus 1", function() test.socket.device_lifecycle:__queue_receive(mock_parent:generate_info_changed({ preferences = { powerOnStatus = "1" }})) test.socket.zigbee:__expect_send({ mock_parent.id, cluster_base.write_manufacturer_specific_attribute(mock_parent, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE, data_types.Uint8, 1) }) end, { - min_api_version = 19 + min_api_version = 20 } ) @@ -250,7 +250,7 @@ test.register_coroutine_test("infoChanged - powerOnStatus 2", function() }) end, { - min_api_version = 19 + min_api_version = 20 } ) @@ -262,7 +262,7 @@ test.register_coroutine_test("infoChanged - stse.changeToWirelessSwitch true", f mock_parent:expect_metadata_update({ profile = "switch-button-light-restore-wireless" }) end, { - min_api_version = 19 + min_api_version = 20 } ) @@ -274,7 +274,7 @@ test.register_coroutine_test("infoChanged - stse.changeToWirelessSwitch false", mock_parent:expect_metadata_update({ profile = "switch-light-restore-wireless" }) end, { - min_api_version = 19 + min_api_version = 20 } ) @@ -287,7 +287,7 @@ local function test_child_changeToWirelessSwitch_true(ep, name) mock_children[ep]:expect_metadata_update({ profile = "switch-button-wireless" }) end, { - min_api_version = 19 + min_api_version = 20 } ) end @@ -301,7 +301,7 @@ local function test_child_changeToWirelessSwitch_false(ep, name) mock_children[ep]:expect_metadata_update({ profile = "switch-wireless" }) end, { - min_api_version = 19 + min_api_version = 20 } ) end From 4d9d3999ed551dc1b851c19ffcea1de585dae8b1 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:31:06 -0500 Subject: [PATCH 417/538] Matter Sensor: Fix nil value logging and handling for soil sensor attribute (#3073) --- .../matter-sensor/src/sensor_handlers/attribute_handlers.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua index a81a03b4a6..eaa8730fc4 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua @@ -89,7 +89,8 @@ function AttributeHandlers.soil_moisture_measurement_limits_handler(driver, devi local min_val = ib.data.elements and ib.data.elements.min_measured_value and ib.data.elements.min_measured_value.value local max_val = ib.data.elements and ib.data.elements.max_measured_value and ib.data.elements.max_measured_value.value if not (min_val and max_val) or (min_val >= max_val) or (min_val < sensor_utils.SOIL_MOISTURE_MIN) or (max_val > sensor_utils.SOIL_MOISTURE_MAX) then - device.log.warn_with({hub_logs = true}, string.format("Device reported invalid soil moisture limits: min=%d, max=%d", min_val, max_val)) + device.log.warn_with({hub_logs = true}, string.format("Device reported invalid soil moisture limits: min=%s, max=%s", min_val, max_val)) + return end sensor_utils.set_field_for_endpoint(device, fields.SOIL_LIMIT_MIN, ib.endpoint_id, min_val) sensor_utils.set_field_for_endpoint(device, fields.SOIL_LIMIT_MAX, ib.endpoint_id, max_val) From 0d148565304be56b2488f89c2a12910c5057dedd Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 29 Jun 2026 08:53:09 -0500 Subject: [PATCH 418/538] Hue: fix conditional checking in hue command error logging (#3072) --- .../philips-hue/src/handlers/commands.lua | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/SmartThings/philips-hue/src/handlers/commands.lua b/drivers/SmartThings/philips-hue/src/handlers/commands.lua index 6bee932239..2c55f39452 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/commands.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/commands.lua @@ -54,13 +54,14 @@ end ---@param err string? Error message returned from the Hue API call, if any ---@param action_desc string Description of the action being performed, for logging purposes local function log_command_response_errors(response, err, action_desc) - if not response or (response.errors and #response.errors == 0) then - if err ~= nil then - log.error_with({ hub_logs = true }, "Error performing " .. action_desc .. ": " .. err) - elseif response and #response.errors > 0 then - for _, error in ipairs(response.errors) do - log.error_with({ hub_logs = true }, "Error returned in Hue response for " .. action_desc .. ": " .. error.description) - end + if err ~= nil then + log.error_with({ hub_logs = true }, "Error performing " .. action_desc .. ": " .. err) + end + + if response and response.errors and #response.errors > 0 then + for _, error in ipairs(response.errors) do + log.error_with({ hub_logs = true }, + "Error returned in Hue response for " .. action_desc .. ": " .. error.description) end end end From 42520391710afb86f348d873dfdf5ef5fab9037a Mon Sep 17 00:00:00 2001 From: "Jan Jakubiszyn/SmartThings Integrations (BJ.jakubiszynS) /SRPOL/Engineer/Samsung Electronics" Date: Mon, 30 Mar 2026 18:30:39 +0200 Subject: [PATCH 419/538] Adding support for Hager WAASYS devices --- .../matter-switch/fingerprints.yml | 20 + .../profiles/motion-illuminance.yml | 14 + .../profiles/window-covering.yml | 21 + .../SmartThings/matter-switch/src/init.lua | 3 +- .../src/sub_drivers/hager/can_handle.lua | 13 + .../src/sub_drivers/hager/init.lua | 475 +++++ .../switch_handlers/attribute_handlers.lua | 2 +- .../src/switch_utils/device_configuration.lua | 1 - .../matter-switch/src/switch_utils/fields.lua | 10 + .../src/test/test_hager_waasys.lua | 1776 +++++++++++++++++ 10 files changed, 2332 insertions(+), 3 deletions(-) create mode 100644 drivers/SmartThings/matter-switch/profiles/motion-illuminance.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/window-covering.yml create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/hager/can_handle.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 8637b37713..222950789b 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -904,6 +904,26 @@ matterManufacturer: vendorId: 0x1285 productId: 0x0009 deviceProfileName: 4-button-batteryLevel + - id: "4741/5" + deviceLabel: Hager Switch 1G + vendorId: 0x1285 + productId: 0x0005 + deviceProfileName: matter-bridge + - id: "4741/6" + deviceLabel: Hager Switch 2G + vendorId: 0x1285 + productId: 0x0006 + deviceProfileName: matter-bridge + - id: "4741/7" + deviceLabel: Hager PIR 1.1M + vendorId: 0x1285 + productId: 0x0007 + deviceProfileName: matter-bridge + - id: "4741/10" + deviceLabel: Hager PIR 2.2M + vendorId: 0x1285 + productId: 0x000A + deviceProfileName: matter-bridge # HAOJAI - id: "5530/4113" deviceLabel: HAOJAI Smart Switch 3-key diff --git a/drivers/SmartThings/matter-switch/profiles/motion-illuminance.yml b/drivers/SmartThings/matter-switch/profiles/motion-illuminance.yml new file mode 100644 index 0000000000..dfe052043c --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/motion-illuminance.yml @@ -0,0 +1,14 @@ +name: motion-illuminance +components: +- id: main + capabilities: + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: MotionSensor diff --git a/drivers/SmartThings/matter-switch/profiles/window-covering.yml b/drivers/SmartThings/matter-switch/profiles/window-covering.yml new file mode 100644 index 0000000000..b588b41ec9 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/window-covering.yml @@ -0,0 +1,21 @@ +name: window-covering +components: +- id: main + capabilities: + - id: windowShade + version: 1 + - id: windowShadePreset + version: 1 + - id: windowShadeLevel + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Blind +preferences: + - preferenceId: presetPosition + explicit: true + - preferenceId: reverse + explicit: true diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index e2a04e103a..3a8f32f345 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -1,4 +1,4 @@ --- Copyright © 2025 SmartThings, Inc. +-- Copyright © 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 local MatterDriver = require "st.matter.driver" @@ -374,6 +374,7 @@ local matter_driver_template = { 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.hager"), 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/hager/can_handle.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/can_handle.lua new file mode 100644 index 0000000000..3ac75a5805 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local device_lib = require "st.device" + local get_product_override_field = require "switch_utils.utils".get_product_override_field + + local checked_device = device.network_type == device_lib.NETWORK_TYPE_MATTER and device or device:get_parent_device() + if get_product_override_field(checked_device, "needs_hager_subdriver") then + return true, require("sub_drivers.hager") + end + return false +end diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua new file mode 100644 index 0000000000..448295247d --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua @@ -0,0 +1,475 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" +local device_lib = require "st.device" +local buttonCfg = require "switch_utils.device_configuration".ButtonCfg +local create_child = require "switch_utils.device_configuration".ChildCfg.create_or_update_child_devices +local switch_utils = require "switch_utils.utils" +local fields = require "switch_utils.fields" + +local ACTIVE_EPS = "__active_EPS" +local MATTER_DEVICE_ID = "MATTER_DEVICE_ID" +local PARENT_ID = "PARENT_ID" +local CURRENT_LIFT = "__current_lift" +local BUTTON_EPS = "__button_eps" +local MAIN_WC_EP = "__main_wc_ep" + +local PRODUCT_ID = { + SWITCH_1G = 0x0005, + SWITCH_2G = 0x0006, + PIR_1_1M = 0x0007, + PIR_2_2M = 0x000A, +} + +local function subscribe (device, endpoint_id, cluster_id, attr_id, event_id) + device:send(cluster_base.subscribe(device, endpoint_id, cluster_id, attr_id, event_id)) +end + +local function get_parent (driver, device) + return driver:get_device_info(device:get_field(PARENT_ID) or nil) +end + +local function get_matter_device(device) + local matter_device_id = device:get_field(MATTER_DEVICE_ID) + if matter_device_id then + local driver = device.driver + if driver then + local matter_device = driver:get_device_info(matter_device_id) + if matter_device then + return matter_device + end + end + end + return nil +end + +local function extract_reported_endpoints(ib) + local eps = {} + if ib.data and ib.data.elements then + for _, el in ipairs(ib.data.elements) do + local ep_id = el.value + if type(ep_id) == "number" and ep_id ~= 1 and ep_id ~= 2 then + table.insert(eps, ep_id) + end + end + end + return eps +end + +local function detect_endpoint_changes (device, ib_elements) + local stored_eps = device:get_field(ACTIVE_EPS) or {} + ib_elements = ib_elements or {} + + local old_set, new_set = {}, {} + for _, ep_id in ipairs(stored_eps) do + old_set[ep_id] = true + end + for _, ep_id in ipairs(ib_elements) do + new_set[ep_id] = true + end + + local removed_eps, added_eps = {}, {} + + for ep_id in pairs(old_set) do + if not new_set[ep_id] then + table.insert(removed_eps, ep_id) + end + end + for ep_id in pairs(new_set) do + if not old_set[ep_id] then + table.insert(added_eps, ep_id) + end + end + return removed_eps, added_eps +end + +local function assign_profile_for_endpoint(device_type_id) + local profile = fields.device_type_profile_map[device_type_id] or "switch-binary" + return function(device, ep_id, is_child_device) + return profile, nil + end +end + +local function link_matter_device_and_parent(matter_device) + local parent = matter_device:get_parent_device() + matter_device:set_field(PARENT_ID, parent.id, { persist = true }) + matter_device:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) + parent:set_field(PARENT_ID, parent.id, { persist = true }) + parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) +end + +local function handle_set_preset(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local lift_value = device.preferences.presetPosition + local hundredths_lift_percent = (100 - tonumber(lift_value)) * 100 + device:send(clusters.WindowCovering.server.commands.GoToLiftPercentage(device, endpoint_id, hundredths_lift_percent)) +end + +local function handle_close(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local req = clusters.WindowCovering.server.commands.DownOrClose(device, endpoint_id) + if device.preferences.reverse then + req = clusters.WindowCovering.server.commands.UpOrOpen(device, endpoint_id) + end + device:send(req) +end + +local function handle_open(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local req = clusters.WindowCovering.server.commands.UpOrOpen(device, endpoint_id) + if device.preferences.reverse then + req = clusters.WindowCovering.server.commands.DownOrClose(device, endpoint_id) + end + device:send(req) +end + +local function handle_pause(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local req = clusters.WindowCovering.server.commands.StopMotion(device, endpoint_id) + device:send(req) +end + +local function handle_shade_level(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local lift_percentage_value = 100 - cmd.args.shadeLevel + local hundredths_lift_percentage = lift_percentage_value * 100 + local req = clusters.WindowCovering.server.commands.GoToLiftPercentage( + device, endpoint_id, hundredths_lift_percentage + ) + device:send(req) +end + +local current_pos_handler = function(attribute) + return function(driver, device, ib, response) + if ib.data.value == nil then + return + end + local windowShade = capabilities.windowShade.windowShade + local position = 100 - math.floor(ib.data.value / 100) + local reverse = device.preferences.reverse + device:emit_event_for_endpoint(ib.endpoint_id, attribute(position)) + if attribute == capabilities.windowShadeLevel.shadeLevel then + device:set_field(CURRENT_LIFT, position) + end + local lift_position = device:get_field(CURRENT_LIFT) + if lift_position == 100 then + device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closed() or windowShade.open()) + elseif lift_position > 0 then + device:emit_event_for_endpoint(ib.endpoint_id, windowShade.partially_open()) + elseif lift_position == 0 then + device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.open() or windowShade.closed()) + end + end +end + +local function current_status_handler(driver, device, ib, response) + local windowShade = capabilities.windowShade.windowShade + local reverse = device.preferences.reverse + local state = ib.data.value & clusters.WindowCovering.types.OperationalStatus.GLOBAL + if state == 1 then + device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closing() or windowShade.opening()) + elseif state == 2 then + device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.opening() or windowShade.closing()) + elseif state ~= 0 then + device:emit_event_for_endpoint(ib.endpoint_id, windowShade.unknown()) + end +end + +local function info_changed(driver, device, event, args) + if not device or not device.id or not device.profile then + return + end + + if device.profile.id ~= args.old_st_store.profile.id or device.network_type == device_lib.NETWORK_TYPE_CHILD then + local parent = device:get_parent_device() + local matter_device = get_matter_device(parent) + local map = {} + device.thread:call_with_delay(2, function() + if device:supports_capability(capabilities.button) then + local button_eps = parent:get_field(BUTTON_EPS) + local clean_eps = {} + table.sort(button_eps) + + for _, v in ipairs(button_eps or {}) do + table.insert(clean_eps, v) + end + + buttonCfg.update_button_component_map(matter_device, clean_eps[1], clean_eps) + for _, ep_id in ipairs(clean_eps) do + subscribe(device, ep_id, clusters.Switch.ID, nil, clusters.Switch.events.MultiPressComplete.ID) + subscribe(device, ep_id, clusters.Switch.ID, nil, clusters.Switch.events.LongPress.ID) + device:emit_event_for_endpoint(ep_id, capabilities.button.supportedButtonValues({ "pushed", "double", "held" })) + end + return + elseif device:supports_capability(capabilities.switch) then + map = { main = 3 } + subscribe(device, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) + if device:supports_capability(capabilities.switchLevel) then + map = { main = 4 } + subscribe(device, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.CurrentLevel.ID, nil) + subscribe(device, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MaxLevel.ID) + subscribe(device, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID) + end + elseif device:supports_capability(capabilities.motionSensor) then + subscribe(device, nil, clusters.OccupancySensing.ID, clusters.OccupancySensing.attributes.Occupancy.ID, nil) + subscribe(device, nil, clusters.IlluminanceMeasurement.ID, clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID, nil) + elseif device:supports_capability(capabilities.windowShadeLevel) then + map = { main = 5 } + subscribe(device, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.OperationalStatus.ID) + subscribe(device, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) + end + if device.id == matter_device.id then + parent:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, map, { persist = true }) + matter_device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, map, { persist = true }) + end + end) + end +end + +local function device_init (driver, device) + if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then + device.thread:call_with_delay(4, function() + info_changed(driver, device, nil, { + old_st_store = { profile = { } } + }) + end) + return + end + + if device:get_parent_device() ~= nil then + link_matter_device_and_parent(device) + local parent = device:get_parent_device() + device:extend_device("send", function(self, message) + return parent:send(message) + end) + else + subscribe(device, 2, clusters.Descriptor.ID, clusters.Descriptor.attributes.PartsList.ID) + device.thread:call_with_delay(3, function() + local matter_device = get_matter_device(device) + device:extend_device("emit_event_for_endpoint", function(self, ep_info, event) + local endpoint_id = type(ep_info) == "number" and ep_info or ep_info.endpoint_id + local child = self:get_child_by_parent_assigned_key(string.format("%d", endpoint_id)) + if child then + return child:emit_event(event) + end + if matter_device then + return matter_device:emit_event_for_endpoint(ep_info, event) + end + end) + end) + end + + device:set_component_to_endpoint_fn(switch_utils.component_to_endpoint) + device:set_endpoint_to_component_fn(switch_utils.endpoint_to_component) +end + +local function handle_descriptor_report(driver, device, ib, response) + local product_id = device.manufacturer_info and device.manufacturer_info.product_id + local parent = get_parent(driver, device) + + if not parent or ib.endpoint_id ~= 2 then + return + end + + local matter_device = get_matter_device(device) + + local new_eps = extract_reported_endpoints(ib) or {} + table.sort(new_eps) + local removed_eps, added_eps = detect_endpoint_changes(device, new_eps) + + device:set_field(ACTIVE_EPS, new_eps, { persist = true }) + + for _, ep_id in ipairs(added_eps or {}) do + subscribe(parent, ep_id, clusters.Descriptor.ID, clusters.Descriptor.attributes.DeviceTypeList.ID, nil) + + if product_id == PRODUCT_ID.SWITCH_1G and ep_id == 3 then + matter_device:try_update_metadata({ profile = "light-binary" }) + elseif product_id == PRODUCT_ID.SWITCH_2G then + if ep_id == 3 then + local profile = switch_utils.tbl_contains(new_eps, 4) and "light-binary" or "2-button" + matter_device:try_update_metadata({ profile = profile }) + elseif ep_id == 4 then + local profile = switch_utils.tbl_contains(new_eps, 3) and "light-binary" or "2-button" + matter_device:try_update_metadata({ profile = profile }) + end + end + end + + for _, ep_id in ipairs(removed_eps) do + local button_eps = parent:get_field(BUTTON_EPS) or {} + local clean_eps = {} + + for _, value in ipairs(button_eps) do + if value ~= ep_id then + table.insert(clean_eps, value) + end + end + table.sort(clean_eps) + parent:set_field(BUTTON_EPS, clean_eps, { persist = true }) + matter_device:set_field(BUTTON_EPS, clean_eps, { persist = true }) + + local child = parent:get_child_by_parent_assigned_key(tostring(ep_id)) + if child then + driver:try_delete_device(child.id) + end + + if ep_id == 3 then + if device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_1G then + matter_device:try_update_metadata({ profile = "2-button" }) + elseif device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_2G then + local has_ep4 = switch_utils.tbl_contains(new_eps, 4) + matter_device:try_update_metadata({ profile = has_ep4 and "2-button" or "4-button" }) + end + elseif ep_id == 4 then + if device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_2G then + local button_comb = switch_utils.tbl_contains(new_eps, 3) + if button_comb then + matter_device:try_update_metadata({ profile = "2-button" }) + create_child(driver, parent, { 3 }, 1, assign_profile_for_endpoint(fields.DEVICE_TYPE_ID.LIGHT.ON_OFF)) + else + matter_device:try_update_metadata({ profile = "4-button" }) + end + end + end + end +end + +local function device_type_handler (driver, device, ib) + local parent = get_parent(driver, device) + if not parent then + return + end + + local matter_device = get_matter_device(device) + local stored_btn_eps = parent:get_field(BUTTON_EPS) or {} + local new_btn_eps = {} + local ep_id = ib.endpoint_id + local value = ib.data.elements + + for _, v in ipairs(stored_btn_eps) do + table.insert(new_btn_eps, v) + end + for _, element in ipairs(value) do + local device_type_field = element.elements.device_type + local device_type_id = device_type_field and device_type_field.value + + if device_type_id == fields.DEVICE_TYPE_ID.GENERIC_SWITCH then + if not switch_utils.tbl_contains(new_btn_eps, ep_id) then + switch_utils.set_field_for_endpoint(parent, fields.SUPPORTS_MULTI_PRESS, ep_id) + switch_utils.set_field_for_endpoint(parent, fields.IGNORE_NEXT_MPC, ep_id) + table.insert(new_btn_eps, ep_id) + table.sort(new_btn_eps) + parent:set_field(BUTTON_EPS, new_btn_eps, { persist = true }) + end + end + + if device_type_id == fields.DEVICE_TYPE_ID.LIGHT.ON_OFF then + local active_eps = device:get_field(ACTIVE_EPS) + + if ep_id == 3 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_1G then + return + elseif ep_id == 4 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_2G then + if switch_utils.tbl_contains(active_eps, 3) then + local ep3 = parent:get_child_by_parent_assigned_key("3") + if ep3 then + driver:try_delete_device(ep3.id) + end + else + create_child(driver, parent, { 4 }, 1, assign_profile_for_endpoint(device_type_id)) + return + end + elseif ep_id == 3 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_2G then + if not switch_utils.tbl_contains(active_eps, 4) then + create_child(driver, parent, { 3 }, 1, assign_profile_for_endpoint(device_type_id)) + else + driver:try_delete_device(ep_id) + end + return + end + create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id)) + elseif device_type_id == fields.DEVICE_TYPE_ID.LIGHT.DIMMABLE then + if ep_id == 4 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_1G then + return + end + create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id)) + elseif device_type_id == fields.DEVICE_TYPE_ID.WINDOW_COVERING then + if matter_device:get_field(MAIN_WC_EP) == nil then + create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id)) + end + end + end +end + +local function do_configure (driver, device) + if device.network_type == device_lib.NETWORK_TYPE_MATTER then + + local wc_eps = device:get_endpoints(clusters.WindowCovering.ID) + local oc_eps = device:get_endpoints(clusters.OccupancySensing.ID) + local bt_eps = device:get_endpoints(clusters.Switch.ID) + local lvl_eps = device:get_endpoints(clusters.LevelControl.ID) + local product_id = device.manufacturer_info.product_id + + table.sort(wc_eps) + table.sort(oc_eps) + table.sort(lvl_eps) + + if #oc_eps > 0 then + device:try_update_metadata({ profile = "motion-illuminance" }) + elseif #wc_eps > 0 and product_id == PRODUCT_ID.SWITCH_1G then + device:try_update_metadata({ profile = "window-covering" }) + device:set_field(MAIN_WC_EP, wc_eps[1], { persist = true }) + elseif #bt_eps == 4 then + device:try_update_metadata({ profile = "4-button" }) + elseif #bt_eps == 2 then + device:try_update_metadata({ profile = "2-button" }) + elseif #lvl_eps > 0 and product_id == PRODUCT_ID.SWITCH_1G then + device:try_update_metadata({ profile = "light-level" }) + end + device:set_field(BUTTON_EPS, bt_eps, { persist = true }) + end +end + +local function added (driver, device) end +local function driver_switched(driver, device) end + +local hager_switch = { + NAME = "Hager Subdriver", + lifecycle_handlers = { + added = added, + init = device_init, + infoChanged = info_changed, + doConfigure = do_configure, + driverSwitched = driver_switched + }, + matter_handlers = { + attr = { + [clusters.Descriptor.ID] = { + [clusters.Descriptor.attributes.PartsList.ID] = handle_descriptor_report, + [clusters.Descriptor.attributes.DeviceTypeList.ID] = device_type_handler, + }, + [clusters.WindowCovering.ID] = { + [clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID] = current_pos_handler(capabilities.windowShadeLevel.shadeLevel), + [clusters.WindowCovering.attributes.OperationalStatus.ID] = current_status_handler, + }, + }, + }, + capability_handlers = { + [capabilities.windowShadePreset.ID] = { + [capabilities.windowShadePreset.commands.presetPosition.NAME] = handle_set_preset, + }, + [capabilities.windowShade.ID] = { + [capabilities.windowShade.commands.close.NAME] = handle_close, + [capabilities.windowShade.commands.open.NAME] = handle_open, + [capabilities.windowShade.commands.pause.NAME] = handle_pause, + }, + [capabilities.windowShadeLevel.ID] = { + [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = handle_shade_level, + }, + }, + can_handle = require("sub_drivers.hager.can_handle") +} + +return hager_switch diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index 45694bc613..b7feed6344 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -246,7 +246,7 @@ end -- [[ OCCUPANCY CLUSTER ATTRIBUTES ]] -- function AttributeHandlers.occupancy_handler(driver, device, ib, response) - device:emit_event(ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) + device:emit_event_for_endpoint(ib.endpoint_id, ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 772669ebd7..9c5235d1e2 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -156,7 +156,6 @@ function ButtonDeviceConfiguration.update_button_component_map(device, default_e 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}) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 02c864fa4f..b0aca9d102 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -34,7 +34,9 @@ SwitchFields.DEVICE_TYPE_ID = { ELECTRICAL_SENSOR = 0x0510, FAN = 0x002B, GENERIC_SWITCH = 0x000F, + LIGHT_SENSOR = 0x0106, IRRIGATION_SYSTEM = 0x0040, + OCCUPANCY_SENSOR = 0x0107, MOUNTED_ON_OFF_CONTROL = 0x010F, MOUNTED_DIMMABLE_LOAD_CONTROL = 0x0110, ON_OFF_PLUG_IN_UNIT = 0x010A, @@ -50,6 +52,7 @@ SwitchFields.DEVICE_TYPE_ID = { COLOR_DIMMER = 0x0105, }, WATER_VALVE = 0x0042, + WINDOW_COVERING = 0x0202 } SwitchFields.device_type_profile_map = { @@ -64,6 +67,7 @@ SwitchFields.device_type_profile_map = { [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", + [SwitchFields.DEVICE_TYPE_ID.WINDOW_COVERING] = "window-covering" } -- COMPONENT_TO_ENDPOINT_MAP is here to preserve the endpoint mapping for @@ -116,6 +120,12 @@ SwitchFields.vendor_overrides = { [0x1189] = { -- LEDVANCE_MANUFACTURER_ID [0x0891] = { target_profile = "switch-binary", initial_profile = "light-binary" }, }, + [0x1285] = { -- HAGER_MANUFACTURER_ID + [0x0005] = { needs_hager_subdriver = true }, -- Hager HBnet 1g switch + [0x0006] = { needs_hager_subdriver = true }, -- Hager HBnet 2g switch + [0x0007] = { needs_hager_subdriver = true }, -- Hager HBnet PIR 1.1M + [0x000A] = { needs_hager_subdriver = true }, -- Hager HBnet PIR 2.2M + }, [0x1321] = { -- SONOFF_MANUFACTURER_ID [0x000C] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, [0x000D] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, diff --git a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua new file mode 100644 index 0000000000..2a1652a2be --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua @@ -0,0 +1,1776 @@ +-- Copyright © 2026 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 clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" +local descriptor = require "st.matter.generated.zap_clusters.Descriptor" + +local MATTER_DEVICE_ID = "MATTER_DEVICE_ID" +local PARENT_ID = "PARENT_ID" +local BUTTON_EPS = "__button_eps" + +local function create_parent_device(product_id) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 4x Button", + profile = t_utils.get_profile_definition("matter-bridge.yml"), + manufacturer_info = { + vendor_id = 0x1285, + product_id = product_id, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 1, + clusters = { + { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } + }, + device_types = { { device_type_id = 0x000E, device_type_revision = 1 } } -- AggregateNode + }, + { + endpoint_id = 2, + clusters = { + { + cluster_id = clusters.Descriptor.ID, + cluster_type = "SERVER", + cluster_revision = 1, + } + }, + device_types = { { device_type_id = 0x0039, device_type_revision = 1 } } -- BridgedNode + } + } + }) +end + +local function create_matter_device(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 4x Button", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + type = "MATTER", + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0006, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 8, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + { + endpoint_id = 9, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + { + endpoint_id = 10, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + { + endpoint_id = 11, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + } + }) +end + +-- Create Hager 2G Relay device with endpoints 3 & 4 (OnOff clusters) +local function create_hager_2g_relay(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 2G Relay", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + type = "MATTER", + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0006, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 3, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OnOff.attributes.OnOff.ID] = false + } + } + }, + device_types = { { device_type_id = 0x0100, device_type_revision = 1 } } + }, + { + endpoint_id = 4, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OnOff.attributes.OnOff.ID] = false + } + } + }, + device_types = { { device_type_id = 0x0100, device_type_revision = 1 } } + }, + } + }) +end + +-- Create Hager Dimmer device with ONLY dimmable endpoint (3) - no button endpoints +local function create_hager_dimmer_device_1g(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 Dimmer", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + type = "MATTER", + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0005, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 4, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OnOff.attributes.OnOff.ID] = false + } + }, + { + cluster_id = clusters.LevelControl.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.LevelControl.attributes.CurrentLevel.ID] = 254 + } + } + }, + device_types = { { device_type_id = 0x0101, device_type_revision = 1 } } + }, + } + }) +end + +local function create_hager_dimmer_device_2g(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 Dimmer", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0006, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 3, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OnOff.attributes.OnOff.ID] = false + } + }, + { + cluster_id = clusters.LevelControl.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.LevelControl.attributes.CurrentLevel.ID] = 254 + } + } + }, + device_types = { { device_type_id = 0x0101, device_type_revision = 1 } } + }, + { + endpoint_id = 8, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + { + endpoint_id = 9, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + } + }) +end + +local function create_matter_device_with_window(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 matter device with Window Covering", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0006, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 8, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + { + endpoint_id = 9, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + { + endpoint_id = 12, + clusters = { + { + cluster_id = clusters.WindowCovering.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.WindowCovering.types.Feature.LIFT | + clusters.WindowCovering.types.Feature.POSITION_AWARE_LIFT | + clusters.WindowCovering.types.Feature.ABSOLUTE_POSITION, + attributes = { + [clusters.WindowCovering.attributes.OperationalStatus.ID] = 0x00, + [clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID] = 0x0000, + } + } + }, + device_types = { { device_type_id = 0x0202, device_type_revision = 1 } } + }, + } + }) +end + +local function create_hager_pir_device(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 PIR with Buttons and Motion/Illuminance/Dimmer", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + type = "MATTER", + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0007, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 3, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OnOff.attributes.OnOff.ID] = false + } + }, + { + cluster_id = clusters.LevelControl.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.LevelControl.attributes.CurrentLevel.ID] = 254 + } + } + }, + device_types = { { device_type_id = 0x0101, device_type_revision = 1 } } + }, + { + endpoint_id = 6, + clusters = { + { + cluster_id = clusters.OccupancySensing.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OccupancySensing.attributes.Occupancy.ID] = 0 + } + } + }, + device_types = { { device_type_id = 0x0107, device_type_revision = 1 } } + }, + { + endpoint_id = 5, + clusters = { + { + cluster_id = clusters.IlluminanceMeasurement.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID] = 0 + } + } + }, + device_types = { { device_type_id = 0x0106, device_type_revision = 1 } } + }, + } + }) +end + +local function add_parent_device(parent) + test.mock_device.add_test_device(parent) + test.socket.device_lifecycle:__queue_receive({ parent.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ parent.id, "init" }) + test.mock_time.advance_time(5) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, 2, descriptor.ID, descriptor.attributes.PartsList.ID, nil) + }) +end + +local function subscribe_switch_events(parent, button_eps) + for _, ep in ipairs(button_eps) do + test.socket.matter:__expect_send({ + parent.id, + clusters.Switch.events.MultiPressComplete:subscribe(parent, ep) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Switch.events.LongPress:subscribe(parent, ep) + }) + end +end + +local function add_matter_device(matter_device, parent, expected_profile_change) + test.mock_device.add_test_device(matter_device) + test.socket.device_lifecycle:__queue_receive({ matter_device.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ matter_device.id, "doConfigure" }) + matter_device:expect_metadata_update({ profile = expected_profile_change }) + matter_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.device_lifecycle:__queue_receive({ matter_device.id, "init" }) + + matter_device:set_field(PARENT_ID, parent.id, { persist = true }) + matter_device:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) + parent:set_field(PARENT_ID, parent.id, { persist = true }) + parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) + parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) +end + +local function add_switch_matter_device(matter_device, parent) + test.mock_device.add_test_device(matter_device) + test.socket.device_lifecycle:__queue_receive({ matter_device.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ matter_device.id, "doConfigure" }) + matter_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.device_lifecycle:__queue_receive({ matter_device.id, "init" }) + + matter_device:set_field(PARENT_ID, parent.id, { persist = true }) + matter_device:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) + parent:set_field(PARENT_ID, parent.id, { persist = true }) + parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) + parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) +end + +local function button_supported_values (matter_device) + test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) + test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) + test.socket.capability:__expect_send(matter_device:generate_test_message("button3", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) + test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) +end + +local function initiate_info_changed(device, profile) + test.socket.device_lifecycle:__queue_receive(device:generate_info_changed({ profile = { id = profile } })) + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + test.mock_time.advance_time(3) +end + +local function configure_parent(device) + test.socket.device_lifecycle:__queue_receive({ device.id, "doConfigure" }) + device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.device_lifecycle:__queue_receive({ device.id, "init" }) + test.socket.matter:__expect_send({ + device.id, + cluster_base.subscribe(device, 2, descriptor.ID, descriptor.attributes.PartsList.ID, nil) + }) + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + test.mock_time.advance_time(3) +end + +local parent = create_parent_device(0x0006) -- 2G button product +local parent_1g = create_parent_device(0x0005) -- 1G button product +local parent_pir = create_parent_device(0x0007) -- PIR product + +local function test_init() + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test("Test: 4-Button Device Detection - Profile Changes from matter-bridge to 4-button When Four Button Endpoints Present", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local matter_device = create_matter_device("matter-bridge", parent) + add_matter_device(matter_device, parent, "4-button") + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(10), + data_types.Uint16(11), + })) + }) + + local matter_device_parent_id = matter_device:get_field(PARENT_ID) + local matter_device_id = matter_device:get_field(MATTER_DEVICE_ID) + local parent_id = parent:get_field(PARENT_ID) + local parent_matter_device_id = parent:get_field(MATTER_DEVICE_ID) + + assert(matter_device_parent_id == parent.id, "link_host_and_subhub 1/4") + assert(matter_device_id == matter_device.id, "link_host_and_subhub 2/4") + assert(parent_id == parent.id, "link_host_and_subhub 3/4") + assert(parent_matter_device_id == matter_device.id, "link_host_and_subhub 4/4") + + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x08) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x09) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x0A) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x0B) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 10, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 11, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + +end) + +test.register_coroutine_test("Test: Button Event Handling - Pushed, Double Press, and Held Events on 4-Button Device", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local matter_device = create_matter_device("4-button", parent) + add_matter_device(matter_device, parent, "4-button") + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(10), + data_types.Uint16(11), + })) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x08) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x09) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x0A) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x0B) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 10, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 11, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.wait_for_events() + initiate_info_changed(matter_device, "4-button") + + subscribe_switch_events(parent, { 8, 9, 10, 11 }) + button_supported_values(matter_device) + + + -- Test single press (pushed) on endpoint 8 (button1) + test.wait_for_events() + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 8, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 1 -- Single press + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) + + --Test double press on endpoint 8 (button1) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 8, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 2 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.button.double({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + matter_device, 8, { + new_position = 1, + previous_position = 0, + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.button.held({ state_change = true }))) + + -- Test press on endpoint 9 (button2) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 9, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 1 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.button.pushed({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 9, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 2 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.button.double({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + matter_device, 9, { + new_position = 1, + previous_position = 0, + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.button.held({ state_change = true }))) + + -- Test press on endpoint 10 (button3) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 10, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 1 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button3", capabilities.button.button.pushed({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 10, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 2 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button3", capabilities.button.button.double({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + matter_device, 10, { + new_position = 1, + previous_position = 0, + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button3", capabilities.button.button.held({ state_change = true }))) + + -- Test press on endpoint 11 (button4) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 11, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 1 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.pushed({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 11, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 2 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.double({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + matter_device, 11, { + new_position = 1, + previous_position = 0, + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.held({ state_change = true }))) +end) + +test.register_coroutine_test("Test: Device Type Handler - Handles Button (Type 15) and OnOff (Type 256) Device Types with Child Creation", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local matter_device = create_matter_device("4-button", parent) + add_matter_device(matter_device, parent, "4-button") + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(9), + data_types.Uint16(8), + data_types.Uint16(6), + })) + }) + + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 6) + }) + + test.wait_for_events() + + -- Receive DeviceTypeList report with device type 15 (button) for endpoint 8 + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array { + { + device_type = data_types.Uint32(15), + revision = data_types.Uint16(1) + } + }) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array { + { + device_type = data_types.Uint32(15), + revision = data_types.Uint16(1) + } + }) + }) + + -- Receive DeviceTypeList report with device type 256 (OnOff) for endpoint 6 + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 6, data_types.Array { + { + device_type = data_types.Uint32(256), + revision = data_types.Uint16(1) + } + }) + }) + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-binary", + parent_device_id = parent.id, + parent_assigned_child_key = "6" + }) + test.wait_for_events() + initiate_info_changed(matter_device, "2-button") + subscribe_switch_events(parent, { 8, 9 }) + test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) + test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) + + test.wait_for_events() + + local child_switch = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("light-binary.yml"), + device_network_id = string.format("%s:6", parent.id), + parent_device_id = parent.id, + parent_assigned_child_key = "6" + }) + test.wait_for_events() + initiate_info_changed(child_switch, "light-binary") + + test.socket.matter:__expect_send({ + parent.id, + clusters.OnOff.attributes.OnOff:subscribe(parent, nil) + }) +end) + +test.register_coroutine_test("Test: 2G Relay - Profile Changes Between light-binary and 2-button Based On Endpoint Availability", function() + test.socket.matter:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + add_parent_device(parent) + local relay = create_hager_2g_relay("light-binary", parent) + add_switch_matter_device(relay, parent) + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + -- Scenario 1: EP3 + EP4 present → light-binary profile + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(3), + data_types.Uint16(4), + })) + }) + -- + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 3) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 4) + }) + + relay:expect_metadata_update({ profile = "light-binary" }) + relay:expect_metadata_update({ profile = "light-binary" }) + test.wait_for_events() + -- Both endpoints are device type 256 (OnOff) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 3, data_types.Array { + { + device_type = data_types.Uint32(256), + revision = data_types.Uint16(1) + } + }) + }) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 4, data_types.Array { + { + device_type = data_types.Uint32(256), + revision = data_types.Uint16(1) + } + }) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-binary", + parent_device_id = parent.id, + parent_assigned_child_key = "4" + }) + local child_switch = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("light-binary.yml"), + device_network_id = string.format("%s:4", parent.id), + parent_device_id = parent.id, + parent_assigned_child_key = "4" + }) + + initiate_info_changed(relay, "light-binary") + test.socket.matter:__expect_send({ + parent.id, + clusters.OnOff.attributes.OnOff:subscribe(parent, nil) + }) + + initiate_info_changed(child_switch, "light-binary") + test.socket.matter:__expect_send({ + parent.id, + clusters.OnOff.attributes.OnOff:subscribe(parent, nil) + }) + + --Scenario 2: EP4 removed → profile changes to 2-button, child created for EP3 + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(3), + data_types.Uint16(8), + data_types.Uint16(9), + })) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-binary", + parent_device_id = parent.id, + parent_assigned_child_key = "3" + }) + + relay:expect_metadata_update({ profile = "2-button" }) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array { + { + device_type = data_types.Uint32(15), + revision = data_types.Uint16(1) + } + }) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array { + { + device_type = data_types.Uint32(15), + revision = data_types.Uint16(1) + } + }) + }) + + -- Scenario 3: EP4 reappears → profile changes back to light-binary + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(3), + data_types.Uint16(4), + })) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 4) + }) + relay:expect_metadata_update({ profile = "light-binary" }) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 4, data_types.Array { + { + device_type = data_types.Uint32(256), + revision = data_types.Uint16(1) + } + }) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-binary", + parent_device_id = parent.id, + parent_assigned_child_key = "4" + }) + + -- Scenario 4: EP3 removed → profile changes to 2-button, child created for EP4 + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(4), + data_types.Uint16(12), + data_types.Uint16(13), + })) + }) + + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 12) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 13) + }) + + relay:expect_metadata_update({ profile = "2-button" }) + + -- Scenario 5: EP3 and EP4 removed → 4-button profile + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(10), + data_types.Uint16(11), + })) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 10) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 11) + }) + relay:expect_metadata_update({ profile = "4-button" }) + + -- Scenario 6: Only EP4 present → 2-button profile, child created + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(4), + data_types.Uint16(8), + data_types.Uint16(9), + })) + }) + + relay:expect_metadata_update({ profile = "2-button" }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 4) + }) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 4, data_types.Array { + { + device_type = data_types.Uint32(256), + revision = data_types.Uint16(1) + } + }) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-binary", + parent_device_id = parent.id, + parent_assigned_child_key = "4" + }) +end) + +test.register_coroutine_test("Test: Dimmer Device - Child Creation for Dimmable Endpoint with Button Support", function() + test.socket.matter:__set_channel_ordering("relaxed") + + add_parent_device(parent) + local dimmer = create_hager_dimmer_device_2g("2-button", parent) + add_matter_device(dimmer, parent, "2-button") + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + --Scenario 1: 2 button endpoints (8, 9) detected → 2-button profile + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(3) + })) + }) + + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 3) + }) + dimmer:expect_metadata_update({ profile = "2-button" }) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 3, data_types.Array { + { + device_type = data_types.Uint32(257), + revision = data_types.Uint16(1) + } + }) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-level", + parent_device_id = parent.id, + parent_assigned_child_key = "3" + }) + + -- Create mock child device for EP3 + local child_dimmer = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("light-level.yml"), + device_network_id = string.format("%s:3", parent.id), + parent_device_id = parent.id, + parent_assigned_child_key = "3" + }) + initiate_info_changed(child_dimmer, "light-level") + + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) + }) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.CurrentLevel.ID, nil) + }) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MaxLevel.ID, nil) + }) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID, nil) + }) + + test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switch", component = "main", command = "on", args = {} } }) + child_dimmer.expect_native_cmd_handler_registration(child_dimmer, "switch", "on") + + test.socket.matter:__expect_send({ + parent.id, + clusters.OnOff.server.commands.On(child_dimmer, 3) + }) + test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 20 } } }) + + child_dimmer.expect_native_cmd_handler_registration(child_dimmer, "switchLevel", "setLevel") + + test.socket.matter:__expect_send({ + parent.id, + clusters.LevelControl.server.commands.MoveToLevelWithOnOff(child_dimmer, 3, 51, nil, 0, 0) + }) + +end) + +test.register_coroutine_test("Test: 1G Dimmer - Initialization and Profile Update to light-level", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local dimmer = create_hager_dimmer_device_2g("2-button", parent) + add_matter_device(dimmer, parent, "2-button") + test.wait_for_events() + configure_parent(parent) +end) + +test.register_coroutine_test("Test: 1G Dimmer - Host Commands and Level Control Capabilities", function() + test.socket.matter:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + + add_parent_device(parent_1g) + local dimmer = create_hager_dimmer_device_1g("light-level", parent_1g) + add_matter_device(dimmer, parent_1g, "light-level") + test.wait_for_events() + configure_parent(parent_1g) + + -- Send dimmable endpoint 4 detection (device type 257) to trigger profile change and subscriptions + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_1g, 2, data_types.Array({ + data_types.Uint16(4), + })) + }) + + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 4) + }) + + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_1g, 4, data_types.Array { + { + device_type = data_types.Uint32(257), + revision = data_types.Uint16(1) + } + }) + }) + + ----subscriptions from device_type_handler + + initiate_info_changed(dimmer, "light-level") + test.socket.matter:__expect_send({ + parent_1g.id, + cluster_base.subscribe(parent_1g, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + cluster_base.subscribe(parent_1g, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.CurrentLevel.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + cluster_base.subscribe(parent_1g, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MaxLevel.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + cluster_base.subscribe(parent_1g, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID, nil) + }) + + test.socket.capability:__queue_receive({ dimmer.id, { capability = "switch", component = "main", command = "on", args = {} } }) + dimmer.expect_native_cmd_handler_registration(dimmer, "switch", "on") + + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.OnOff.server.commands.On(dimmer, 4) + }) + test.socket.capability:__queue_receive({ dimmer.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 20 } } }) + + dimmer.expect_native_cmd_handler_registration(dimmer, "switchLevel", "setLevel") + + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.LevelControl.server.commands.MoveToLevelWithOnOff(dimmer, 4, 51, nil, 0, 0) + }) +end) + +test.register_coroutine_test("Test: PIR Device - Initialization with Motion and Illuminance Capabilities", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent_pir) + local pir_device = create_hager_pir_device("motion-illuminance", parent_pir) + add_matter_device(pir_device, parent_pir, "motion-illuminance") + test.wait_for_events() + configure_parent(parent_pir) + +end) + +test.register_coroutine_test("Test: PIR Device - Complete Functionality with Motion, Illuminance, and Dimmer Support", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent_pir) + local pir_device = create_hager_pir_device("motion-illuminance", parent_pir) + add_matter_device(pir_device, parent_pir, "motion-illuminance") + test.wait_for_events() + + configure_parent(parent_pir) + + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_pir, 2, data_types.Array({ + data_types.Uint16(3), + data_types.Uint16(4), + data_types.Uint16(5) + + })) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_pir, 3) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_pir, 4) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_pir, 5) + }) + + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_pir, 4, data_types.Array { + { + device_type = data_types.Uint32(263), + revision = data_types.Uint16(1) + } + }) + }) + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_pir, 5, data_types.Array { + { + device_type = data_types.Uint32(262), + revision = data_types.Uint16(1) + } + }) + }) + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_pir, 3, data_types.Array { + { + device_type = data_types.Uint32(257), + revision = data_types.Uint16(1) + } + }) + }) + + parent_pir:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-level", + parent_device_id = parent_pir.id, + parent_assigned_child_key = "3" + }) + test.wait_for_events() + initiate_info_changed(pir_device, "motion-illuminance") + test.socket.matter:__expect_send({ + parent_pir.id, + cluster_base.subscribe(parent_pir, nil, clusters.IlluminanceMeasurement.ID, clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + cluster_base.subscribe(parent_pir, nil, clusters.OccupancySensing.ID, clusters.OccupancySensing.attributes.Occupancy.ID, nil) + }) + + local child_dimmer = test.mock_device.build_test_child_device({ + label = "Hager G2 4x Button 1", + profile = t_utils.get_profile_definition("light-level.yml"), + device_network_id = string.format("%s:3", parent_pir.id), + parent_device_id = parent_pir.id, + parent_assigned_child_key = "3" + }) + + test.mock_device.add_test_device(child_dimmer) + initiate_info_changed(child_dimmer, "light-level") + test.socket.matter:__expect_send({ + parent_pir.id, + cluster_base.subscribe(parent_pir, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + cluster_base.subscribe(parent_pir, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.CurrentLevel.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + cluster_base.subscribe(parent_pir, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MaxLevel.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + cluster_base.subscribe(parent_pir, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID, nil) + }) + test.wait_for_events() + --Verify motion detected event + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(parent_pir, 6, 1) + }) + + test.socket.capability:__expect_send(pir_device:generate_test_message("main", capabilities.motionSensor.motion.active())) + + -- Verify illuminance measurement event + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.IlluminanceMeasurement.attributes.MeasuredValue:build_test_report_data(parent_pir, 5, 21370) + }) + test.socket.capability:__expect_send(pir_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance(137))) + + -- Send on command to OnOff endpoint (dimmer) + child_dimmer.expect_native_cmd_handler_registration(child_dimmer, "switch", "on") + + test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switch", component = "main", command = "on", args = {} } }) + test.socket.matter:__expect_send({ + parent_pir.id, + clusters.OnOff.commands.On(parent_pir, 3) + }) + -- Verify on state via attribute report + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.OnOff.attributes.OnOff:build_test_report_data(parent_pir, 3, true) + }) + test.socket.capability:__expect_send(child_dimmer:generate_test_message("main", capabilities.switch.switch.on())) + parent_pir.expect_native_attr_handler_registration(parent_pir, "switch", "switch") + + -- Set dimmer level to 50% + test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 50 } } }) + test.socket.matter:__expect_send({ + parent_pir.id, + clusters.LevelControl.commands.MoveToLevelWithOnOff(parent_pir, 3, 127, nil, 0, 0) + }) + child_dimmer.expect_native_cmd_handler_registration(child_dimmer, "switchLevel", "setLevel") + + -- Verify level via attribute report + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.LevelControl.attributes.CurrentLevel:build_test_report_data(parent_pir, 3, 127) + }) + test.socket.capability:__expect_send(child_dimmer:generate_test_message("main", capabilities.switchLevel.level(50))) + parent_pir.expect_native_attr_handler_registration(parent_pir, "switchLevel", "level") +end) + +test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile with Window Covering Child Device", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local window_covering_device = create_matter_device_with_window("2-button", parent) + add_matter_device(window_covering_device, parent, "2-button") + test.wait_for_events() + configure_parent(parent) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(12), + })) + }) + + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 12) + }) + + -- DeviceTypeList report for window covering endpoint (type 514 = Window Covering Device) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 12, data_types.Array { + { + device_type = data_types.Uint32(514), + revision = data_types.Uint16(1) + } + }) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "window-covering", + parent_device_id = parent.id, + parent_assigned_child_key = "12" + }) + + local child_wc = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("window-covering.yml"), + device_network_id = string.format("%s:12", parent.id), + parent_device_id = parent.id, + parent_assigned_child_key = "12" + }) + + test.mock_device.add_test_device(child_wc) + initiate_info_changed(child_wc, "window-covering") + + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.OperationalStatus.ID, nil) + }) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) + }) + + -- Open command + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "open", args = {} } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.UpOrOpen(parent, 12) }) + test.wait_for_events() + + -- Verify open state via attribute report + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.OperationalStatus:build_test_report_data(parent, 12, 0x01) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.opening())) + + + -- Close command + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "close", args = {} } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.DownOrClose(parent, 12) }) + + -- Verify close state via attribute report + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.OperationalStatus:build_test_report_data(parent, 12, 0x02) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closing())) + + -- Pause command + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "pause", args = {} } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.StopMotion(parent, 12) }) + test.wait_for_events() + + -- Set shade level to 50% + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 50 } } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.GoToLiftPercentage(parent, 12, 5000, nil, 0, 0) }) + test.wait_for_events() + + -- Verify shade level via attribute report + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 5000) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50))) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.partially_open())) + + + -- Set shade level to 100% + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 100 } } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.GoToLiftPercentage(parent, 12, 0, nil, 0, 0) }) + test.wait_for_events() + + -- Verify shade level via attribute report + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 0) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100))) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.open())) + + -- Set shade level to 0% + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 0 } } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.GoToLiftPercentage(parent, 12, 10000, nil, 0, 0) }) + + -- Verify shade level via attribute report + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 10000) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0))) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closed())) + +end) + +test.register_coroutine_test("Test: Window Covering - Preference Changes for Reverse Polarity and Preset Position", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local window_covering_device = create_matter_device_with_window("2-button", parent) + add_matter_device(window_covering_device, parent, "2-button") + test.wait_for_events() + configure_parent(parent) + + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(12), + })) + }) + -- + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 12) + }) + test.wait_for_events() + + -- DeviceTypeList reports for button endpoints (type 15 = Generic Switch) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array { + { + device_type = data_types.Uint32(15), + revision = data_types.Uint16(1) + } + }) + }) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array { + { + device_type = data_types.Uint32(15), + revision = data_types.Uint16(1) + } + }) + }) + + -- DeviceTypeList report for window covering endpoint (type 514 = Window Covering Device) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 12, data_types.Array { + { + device_type = data_types.Uint32(514), + revision = data_types.Uint16(1) + } + }) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "window-covering", + parent_device_id = parent.id, + parent_assigned_child_key = "12" + }) + + local child_wc = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("window-covering.yml"), + device_network_id = string.format("%s:12", parent.id), + parent_device_id = parent.id, + parent_assigned_child_key = "12" + }) + test.mock_device.add_test_device(child_wc) + + test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { reverse = "false" } })) + test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { reverse = "true" } })) + test.wait_for_events() + local reverse_preference_set = child_wc.preferences.reverse + assert(reverse_preference_set == "true", "reverse_preference_set is True") + -- + --Send open command - with reverse_polarity true, this should send DownOrClose + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "open", args = {} } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.DownOrClose(parent, 12) }) + + -- Send close command - with reverse_polarity true, this should send UpOrOpen + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "close", args = {} } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.UpOrOpen(parent, 12) }) + + -- Send Pause command + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "pause", args = {} } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.StopMotion(parent, 12) }) + + -- Position preset testing + test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { presetPosition = "50" } })) + test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { presetPosition = "20" } })) + + test.wait_for_events() + + local PRESET_LEVEL_KEY = child_wc.preferences.presetPosition + assert(PRESET_LEVEL_KEY == "20", " presetPosition is set to 20") + + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} } }) + test.socket.matter:__expect_send( + { parent.id, clusters.WindowCovering.server.commands.GoToLiftPercentage(parent, 12, 8000) } + ) + + test.wait_for_events() + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + })) + }) + + test.wait_for_events() + + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + test.mock_time.advance_time(5) + local BUTTON_EPS_FIELD = parent:get_field(BUTTON_EPS) + assert(BUTTON_EPS_FIELD[1] == 8) + assert(BUTTON_EPS_FIELD[2] == 9) + + test.wait_for_events() + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(12), + })) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 12) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 12, data_types.Array { + { + device_type = data_types.Uint32(514), + revision = data_types.Uint16(1) + } + }) + }) + child_wc:expect_metadata_update({ profile = "window-covering" }) + initiate_info_changed(child_wc, "window-covering") + + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.OperationalStatus.ID, nil) + }) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) + }) +end) + +test.run_registered_tests() From feccb91b9b801e1959fdcd8c7dcaf7f120c14944 Mon Sep 17 00:00:00 2001 From: JanJakubiszyn <101173721+JanJakubiszyn@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:11:56 +0200 Subject: [PATCH 420/538] Revert "Adding support for Hager WAASYS devices" --- .../matter-switch/fingerprints.yml | 20 - .../profiles/motion-illuminance.yml | 14 - .../profiles/window-covering.yml | 21 - .../SmartThings/matter-switch/src/init.lua | 3 +- .../src/sub_drivers/hager/can_handle.lua | 13 - .../src/sub_drivers/hager/init.lua | 475 ----- .../switch_handlers/attribute_handlers.lua | 2 +- .../src/switch_utils/device_configuration.lua | 1 + .../matter-switch/src/switch_utils/fields.lua | 10 - .../src/test/test_hager_waasys.lua | 1776 ----------------- 10 files changed, 3 insertions(+), 2332 deletions(-) delete mode 100644 drivers/SmartThings/matter-switch/profiles/motion-illuminance.yml delete mode 100644 drivers/SmartThings/matter-switch/profiles/window-covering.yml delete mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/hager/can_handle.lua delete mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua delete mode 100644 drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 222950789b..8637b37713 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -904,26 +904,6 @@ matterManufacturer: vendorId: 0x1285 productId: 0x0009 deviceProfileName: 4-button-batteryLevel - - id: "4741/5" - deviceLabel: Hager Switch 1G - vendorId: 0x1285 - productId: 0x0005 - deviceProfileName: matter-bridge - - id: "4741/6" - deviceLabel: Hager Switch 2G - vendorId: 0x1285 - productId: 0x0006 - deviceProfileName: matter-bridge - - id: "4741/7" - deviceLabel: Hager PIR 1.1M - vendorId: 0x1285 - productId: 0x0007 - deviceProfileName: matter-bridge - - id: "4741/10" - deviceLabel: Hager PIR 2.2M - vendorId: 0x1285 - productId: 0x000A - deviceProfileName: matter-bridge # HAOJAI - id: "5530/4113" deviceLabel: HAOJAI Smart Switch 3-key diff --git a/drivers/SmartThings/matter-switch/profiles/motion-illuminance.yml b/drivers/SmartThings/matter-switch/profiles/motion-illuminance.yml deleted file mode 100644 index dfe052043c..0000000000 --- a/drivers/SmartThings/matter-switch/profiles/motion-illuminance.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: motion-illuminance -components: -- id: main - capabilities: - - id: motionSensor - version: 1 - - id: illuminanceMeasurement - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: MotionSensor diff --git a/drivers/SmartThings/matter-switch/profiles/window-covering.yml b/drivers/SmartThings/matter-switch/profiles/window-covering.yml deleted file mode 100644 index b588b41ec9..0000000000 --- a/drivers/SmartThings/matter-switch/profiles/window-covering.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: window-covering -components: -- id: main - capabilities: - - id: windowShade - version: 1 - - id: windowShadePreset - version: 1 - - id: windowShadeLevel - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Blind -preferences: - - preferenceId: presetPosition - explicit: true - - preferenceId: reverse - explicit: true diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 3a8f32f345..e2a04e103a 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -1,4 +1,4 @@ --- Copyright © 2026 SmartThings, Inc. +-- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 local MatterDriver = require "st.matter.driver" @@ -374,7 +374,6 @@ local matter_driver_template = { 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.hager"), 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/hager/can_handle.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/can_handle.lua deleted file mode 100644 index 3ac75a5805..0000000000 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/hager/can_handle.lua +++ /dev/null @@ -1,13 +0,0 @@ --- Copyright © 2026 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -return function(opts, driver, device, ...) - local device_lib = require "st.device" - local get_product_override_field = require "switch_utils.utils".get_product_override_field - - local checked_device = device.network_type == device_lib.NETWORK_TYPE_MATTER and device or device:get_parent_device() - if get_product_override_field(checked_device, "needs_hager_subdriver") then - return true, require("sub_drivers.hager") - end - return false -end diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua deleted file mode 100644 index 448295247d..0000000000 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua +++ /dev/null @@ -1,475 +0,0 @@ --- Copyright © 2026 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local capabilities = require "st.capabilities" -local clusters = require "st.matter.clusters" -local cluster_base = require "st.matter.cluster_base" -local device_lib = require "st.device" -local buttonCfg = require "switch_utils.device_configuration".ButtonCfg -local create_child = require "switch_utils.device_configuration".ChildCfg.create_or_update_child_devices -local switch_utils = require "switch_utils.utils" -local fields = require "switch_utils.fields" - -local ACTIVE_EPS = "__active_EPS" -local MATTER_DEVICE_ID = "MATTER_DEVICE_ID" -local PARENT_ID = "PARENT_ID" -local CURRENT_LIFT = "__current_lift" -local BUTTON_EPS = "__button_eps" -local MAIN_WC_EP = "__main_wc_ep" - -local PRODUCT_ID = { - SWITCH_1G = 0x0005, - SWITCH_2G = 0x0006, - PIR_1_1M = 0x0007, - PIR_2_2M = 0x000A, -} - -local function subscribe (device, endpoint_id, cluster_id, attr_id, event_id) - device:send(cluster_base.subscribe(device, endpoint_id, cluster_id, attr_id, event_id)) -end - -local function get_parent (driver, device) - return driver:get_device_info(device:get_field(PARENT_ID) or nil) -end - -local function get_matter_device(device) - local matter_device_id = device:get_field(MATTER_DEVICE_ID) - if matter_device_id then - local driver = device.driver - if driver then - local matter_device = driver:get_device_info(matter_device_id) - if matter_device then - return matter_device - end - end - end - return nil -end - -local function extract_reported_endpoints(ib) - local eps = {} - if ib.data and ib.data.elements then - for _, el in ipairs(ib.data.elements) do - local ep_id = el.value - if type(ep_id) == "number" and ep_id ~= 1 and ep_id ~= 2 then - table.insert(eps, ep_id) - end - end - end - return eps -end - -local function detect_endpoint_changes (device, ib_elements) - local stored_eps = device:get_field(ACTIVE_EPS) or {} - ib_elements = ib_elements or {} - - local old_set, new_set = {}, {} - for _, ep_id in ipairs(stored_eps) do - old_set[ep_id] = true - end - for _, ep_id in ipairs(ib_elements) do - new_set[ep_id] = true - end - - local removed_eps, added_eps = {}, {} - - for ep_id in pairs(old_set) do - if not new_set[ep_id] then - table.insert(removed_eps, ep_id) - end - end - for ep_id in pairs(new_set) do - if not old_set[ep_id] then - table.insert(added_eps, ep_id) - end - end - return removed_eps, added_eps -end - -local function assign_profile_for_endpoint(device_type_id) - local profile = fields.device_type_profile_map[device_type_id] or "switch-binary" - return function(device, ep_id, is_child_device) - return profile, nil - end -end - -local function link_matter_device_and_parent(matter_device) - local parent = matter_device:get_parent_device() - matter_device:set_field(PARENT_ID, parent.id, { persist = true }) - matter_device:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) - parent:set_field(PARENT_ID, parent.id, { persist = true }) - parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) -end - -local function handle_set_preset(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) - local lift_value = device.preferences.presetPosition - local hundredths_lift_percent = (100 - tonumber(lift_value)) * 100 - device:send(clusters.WindowCovering.server.commands.GoToLiftPercentage(device, endpoint_id, hundredths_lift_percent)) -end - -local function handle_close(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) - local req = clusters.WindowCovering.server.commands.DownOrClose(device, endpoint_id) - if device.preferences.reverse then - req = clusters.WindowCovering.server.commands.UpOrOpen(device, endpoint_id) - end - device:send(req) -end - -local function handle_open(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) - local req = clusters.WindowCovering.server.commands.UpOrOpen(device, endpoint_id) - if device.preferences.reverse then - req = clusters.WindowCovering.server.commands.DownOrClose(device, endpoint_id) - end - device:send(req) -end - -local function handle_pause(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) - local req = clusters.WindowCovering.server.commands.StopMotion(device, endpoint_id) - device:send(req) -end - -local function handle_shade_level(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) - local lift_percentage_value = 100 - cmd.args.shadeLevel - local hundredths_lift_percentage = lift_percentage_value * 100 - local req = clusters.WindowCovering.server.commands.GoToLiftPercentage( - device, endpoint_id, hundredths_lift_percentage - ) - device:send(req) -end - -local current_pos_handler = function(attribute) - return function(driver, device, ib, response) - if ib.data.value == nil then - return - end - local windowShade = capabilities.windowShade.windowShade - local position = 100 - math.floor(ib.data.value / 100) - local reverse = device.preferences.reverse - device:emit_event_for_endpoint(ib.endpoint_id, attribute(position)) - if attribute == capabilities.windowShadeLevel.shadeLevel then - device:set_field(CURRENT_LIFT, position) - end - local lift_position = device:get_field(CURRENT_LIFT) - if lift_position == 100 then - device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closed() or windowShade.open()) - elseif lift_position > 0 then - device:emit_event_for_endpoint(ib.endpoint_id, windowShade.partially_open()) - elseif lift_position == 0 then - device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.open() or windowShade.closed()) - end - end -end - -local function current_status_handler(driver, device, ib, response) - local windowShade = capabilities.windowShade.windowShade - local reverse = device.preferences.reverse - local state = ib.data.value & clusters.WindowCovering.types.OperationalStatus.GLOBAL - if state == 1 then - device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closing() or windowShade.opening()) - elseif state == 2 then - device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.opening() or windowShade.closing()) - elseif state ~= 0 then - device:emit_event_for_endpoint(ib.endpoint_id, windowShade.unknown()) - end -end - -local function info_changed(driver, device, event, args) - if not device or not device.id or not device.profile then - return - end - - if device.profile.id ~= args.old_st_store.profile.id or device.network_type == device_lib.NETWORK_TYPE_CHILD then - local parent = device:get_parent_device() - local matter_device = get_matter_device(parent) - local map = {} - device.thread:call_with_delay(2, function() - if device:supports_capability(capabilities.button) then - local button_eps = parent:get_field(BUTTON_EPS) - local clean_eps = {} - table.sort(button_eps) - - for _, v in ipairs(button_eps or {}) do - table.insert(clean_eps, v) - end - - buttonCfg.update_button_component_map(matter_device, clean_eps[1], clean_eps) - for _, ep_id in ipairs(clean_eps) do - subscribe(device, ep_id, clusters.Switch.ID, nil, clusters.Switch.events.MultiPressComplete.ID) - subscribe(device, ep_id, clusters.Switch.ID, nil, clusters.Switch.events.LongPress.ID) - device:emit_event_for_endpoint(ep_id, capabilities.button.supportedButtonValues({ "pushed", "double", "held" })) - end - return - elseif device:supports_capability(capabilities.switch) then - map = { main = 3 } - subscribe(device, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) - if device:supports_capability(capabilities.switchLevel) then - map = { main = 4 } - subscribe(device, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.CurrentLevel.ID, nil) - subscribe(device, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MaxLevel.ID) - subscribe(device, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID) - end - elseif device:supports_capability(capabilities.motionSensor) then - subscribe(device, nil, clusters.OccupancySensing.ID, clusters.OccupancySensing.attributes.Occupancy.ID, nil) - subscribe(device, nil, clusters.IlluminanceMeasurement.ID, clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID, nil) - elseif device:supports_capability(capabilities.windowShadeLevel) then - map = { main = 5 } - subscribe(device, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.OperationalStatus.ID) - subscribe(device, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) - end - if device.id == matter_device.id then - parent:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, map, { persist = true }) - matter_device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, map, { persist = true }) - end - end) - end -end - -local function device_init (driver, device) - if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then - device.thread:call_with_delay(4, function() - info_changed(driver, device, nil, { - old_st_store = { profile = { } } - }) - end) - return - end - - if device:get_parent_device() ~= nil then - link_matter_device_and_parent(device) - local parent = device:get_parent_device() - device:extend_device("send", function(self, message) - return parent:send(message) - end) - else - subscribe(device, 2, clusters.Descriptor.ID, clusters.Descriptor.attributes.PartsList.ID) - device.thread:call_with_delay(3, function() - local matter_device = get_matter_device(device) - device:extend_device("emit_event_for_endpoint", function(self, ep_info, event) - local endpoint_id = type(ep_info) == "number" and ep_info or ep_info.endpoint_id - local child = self:get_child_by_parent_assigned_key(string.format("%d", endpoint_id)) - if child then - return child:emit_event(event) - end - if matter_device then - return matter_device:emit_event_for_endpoint(ep_info, event) - end - end) - end) - end - - device:set_component_to_endpoint_fn(switch_utils.component_to_endpoint) - device:set_endpoint_to_component_fn(switch_utils.endpoint_to_component) -end - -local function handle_descriptor_report(driver, device, ib, response) - local product_id = device.manufacturer_info and device.manufacturer_info.product_id - local parent = get_parent(driver, device) - - if not parent or ib.endpoint_id ~= 2 then - return - end - - local matter_device = get_matter_device(device) - - local new_eps = extract_reported_endpoints(ib) or {} - table.sort(new_eps) - local removed_eps, added_eps = detect_endpoint_changes(device, new_eps) - - device:set_field(ACTIVE_EPS, new_eps, { persist = true }) - - for _, ep_id in ipairs(added_eps or {}) do - subscribe(parent, ep_id, clusters.Descriptor.ID, clusters.Descriptor.attributes.DeviceTypeList.ID, nil) - - if product_id == PRODUCT_ID.SWITCH_1G and ep_id == 3 then - matter_device:try_update_metadata({ profile = "light-binary" }) - elseif product_id == PRODUCT_ID.SWITCH_2G then - if ep_id == 3 then - local profile = switch_utils.tbl_contains(new_eps, 4) and "light-binary" or "2-button" - matter_device:try_update_metadata({ profile = profile }) - elseif ep_id == 4 then - local profile = switch_utils.tbl_contains(new_eps, 3) and "light-binary" or "2-button" - matter_device:try_update_metadata({ profile = profile }) - end - end - end - - for _, ep_id in ipairs(removed_eps) do - local button_eps = parent:get_field(BUTTON_EPS) or {} - local clean_eps = {} - - for _, value in ipairs(button_eps) do - if value ~= ep_id then - table.insert(clean_eps, value) - end - end - table.sort(clean_eps) - parent:set_field(BUTTON_EPS, clean_eps, { persist = true }) - matter_device:set_field(BUTTON_EPS, clean_eps, { persist = true }) - - local child = parent:get_child_by_parent_assigned_key(tostring(ep_id)) - if child then - driver:try_delete_device(child.id) - end - - if ep_id == 3 then - if device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_1G then - matter_device:try_update_metadata({ profile = "2-button" }) - elseif device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_2G then - local has_ep4 = switch_utils.tbl_contains(new_eps, 4) - matter_device:try_update_metadata({ profile = has_ep4 and "2-button" or "4-button" }) - end - elseif ep_id == 4 then - if device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_2G then - local button_comb = switch_utils.tbl_contains(new_eps, 3) - if button_comb then - matter_device:try_update_metadata({ profile = "2-button" }) - create_child(driver, parent, { 3 }, 1, assign_profile_for_endpoint(fields.DEVICE_TYPE_ID.LIGHT.ON_OFF)) - else - matter_device:try_update_metadata({ profile = "4-button" }) - end - end - end - end -end - -local function device_type_handler (driver, device, ib) - local parent = get_parent(driver, device) - if not parent then - return - end - - local matter_device = get_matter_device(device) - local stored_btn_eps = parent:get_field(BUTTON_EPS) or {} - local new_btn_eps = {} - local ep_id = ib.endpoint_id - local value = ib.data.elements - - for _, v in ipairs(stored_btn_eps) do - table.insert(new_btn_eps, v) - end - for _, element in ipairs(value) do - local device_type_field = element.elements.device_type - local device_type_id = device_type_field and device_type_field.value - - if device_type_id == fields.DEVICE_TYPE_ID.GENERIC_SWITCH then - if not switch_utils.tbl_contains(new_btn_eps, ep_id) then - switch_utils.set_field_for_endpoint(parent, fields.SUPPORTS_MULTI_PRESS, ep_id) - switch_utils.set_field_for_endpoint(parent, fields.IGNORE_NEXT_MPC, ep_id) - table.insert(new_btn_eps, ep_id) - table.sort(new_btn_eps) - parent:set_field(BUTTON_EPS, new_btn_eps, { persist = true }) - end - end - - if device_type_id == fields.DEVICE_TYPE_ID.LIGHT.ON_OFF then - local active_eps = device:get_field(ACTIVE_EPS) - - if ep_id == 3 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_1G then - return - elseif ep_id == 4 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_2G then - if switch_utils.tbl_contains(active_eps, 3) then - local ep3 = parent:get_child_by_parent_assigned_key("3") - if ep3 then - driver:try_delete_device(ep3.id) - end - else - create_child(driver, parent, { 4 }, 1, assign_profile_for_endpoint(device_type_id)) - return - end - elseif ep_id == 3 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_2G then - if not switch_utils.tbl_contains(active_eps, 4) then - create_child(driver, parent, { 3 }, 1, assign_profile_for_endpoint(device_type_id)) - else - driver:try_delete_device(ep_id) - end - return - end - create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id)) - elseif device_type_id == fields.DEVICE_TYPE_ID.LIGHT.DIMMABLE then - if ep_id == 4 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_1G then - return - end - create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id)) - elseif device_type_id == fields.DEVICE_TYPE_ID.WINDOW_COVERING then - if matter_device:get_field(MAIN_WC_EP) == nil then - create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id)) - end - end - end -end - -local function do_configure (driver, device) - if device.network_type == device_lib.NETWORK_TYPE_MATTER then - - local wc_eps = device:get_endpoints(clusters.WindowCovering.ID) - local oc_eps = device:get_endpoints(clusters.OccupancySensing.ID) - local bt_eps = device:get_endpoints(clusters.Switch.ID) - local lvl_eps = device:get_endpoints(clusters.LevelControl.ID) - local product_id = device.manufacturer_info.product_id - - table.sort(wc_eps) - table.sort(oc_eps) - table.sort(lvl_eps) - - if #oc_eps > 0 then - device:try_update_metadata({ profile = "motion-illuminance" }) - elseif #wc_eps > 0 and product_id == PRODUCT_ID.SWITCH_1G then - device:try_update_metadata({ profile = "window-covering" }) - device:set_field(MAIN_WC_EP, wc_eps[1], { persist = true }) - elseif #bt_eps == 4 then - device:try_update_metadata({ profile = "4-button" }) - elseif #bt_eps == 2 then - device:try_update_metadata({ profile = "2-button" }) - elseif #lvl_eps > 0 and product_id == PRODUCT_ID.SWITCH_1G then - device:try_update_metadata({ profile = "light-level" }) - end - device:set_field(BUTTON_EPS, bt_eps, { persist = true }) - end -end - -local function added (driver, device) end -local function driver_switched(driver, device) end - -local hager_switch = { - NAME = "Hager Subdriver", - lifecycle_handlers = { - added = added, - init = device_init, - infoChanged = info_changed, - doConfigure = do_configure, - driverSwitched = driver_switched - }, - matter_handlers = { - attr = { - [clusters.Descriptor.ID] = { - [clusters.Descriptor.attributes.PartsList.ID] = handle_descriptor_report, - [clusters.Descriptor.attributes.DeviceTypeList.ID] = device_type_handler, - }, - [clusters.WindowCovering.ID] = { - [clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID] = current_pos_handler(capabilities.windowShadeLevel.shadeLevel), - [clusters.WindowCovering.attributes.OperationalStatus.ID] = current_status_handler, - }, - }, - }, - capability_handlers = { - [capabilities.windowShadePreset.ID] = { - [capabilities.windowShadePreset.commands.presetPosition.NAME] = handle_set_preset, - }, - [capabilities.windowShade.ID] = { - [capabilities.windowShade.commands.close.NAME] = handle_close, - [capabilities.windowShade.commands.open.NAME] = handle_open, - [capabilities.windowShade.commands.pause.NAME] = handle_pause, - }, - [capabilities.windowShadeLevel.ID] = { - [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = handle_shade_level, - }, - }, - can_handle = require("sub_drivers.hager.can_handle") -} - -return hager_switch diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index b7feed6344..45694bc613 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -246,7 +246,7 @@ end -- [[ OCCUPANCY CLUSTER ATTRIBUTES ]] -- function AttributeHandlers.occupancy_handler(driver, device, ib, response) - device:emit_event_for_endpoint(ib.endpoint_id, ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) + device:emit_event(ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 9c5235d1e2..772669ebd7 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -156,6 +156,7 @@ function ButtonDeviceConfiguration.update_button_component_map(device, default_e 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}) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index b0aca9d102..02c864fa4f 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -34,9 +34,7 @@ SwitchFields.DEVICE_TYPE_ID = { ELECTRICAL_SENSOR = 0x0510, FAN = 0x002B, GENERIC_SWITCH = 0x000F, - LIGHT_SENSOR = 0x0106, IRRIGATION_SYSTEM = 0x0040, - OCCUPANCY_SENSOR = 0x0107, MOUNTED_ON_OFF_CONTROL = 0x010F, MOUNTED_DIMMABLE_LOAD_CONTROL = 0x0110, ON_OFF_PLUG_IN_UNIT = 0x010A, @@ -52,7 +50,6 @@ SwitchFields.DEVICE_TYPE_ID = { COLOR_DIMMER = 0x0105, }, WATER_VALVE = 0x0042, - WINDOW_COVERING = 0x0202 } SwitchFields.device_type_profile_map = { @@ -67,7 +64,6 @@ SwitchFields.device_type_profile_map = { [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", - [SwitchFields.DEVICE_TYPE_ID.WINDOW_COVERING] = "window-covering" } -- COMPONENT_TO_ENDPOINT_MAP is here to preserve the endpoint mapping for @@ -120,12 +116,6 @@ SwitchFields.vendor_overrides = { [0x1189] = { -- LEDVANCE_MANUFACTURER_ID [0x0891] = { target_profile = "switch-binary", initial_profile = "light-binary" }, }, - [0x1285] = { -- HAGER_MANUFACTURER_ID - [0x0005] = { needs_hager_subdriver = true }, -- Hager HBnet 1g switch - [0x0006] = { needs_hager_subdriver = true }, -- Hager HBnet 2g switch - [0x0007] = { needs_hager_subdriver = true }, -- Hager HBnet PIR 1.1M - [0x000A] = { needs_hager_subdriver = true }, -- Hager HBnet PIR 2.2M - }, [0x1321] = { -- SONOFF_MANUFACTURER_ID [0x000C] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, [0x000D] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, diff --git a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua deleted file mode 100644 index 2a1652a2be..0000000000 --- a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua +++ /dev/null @@ -1,1776 +0,0 @@ --- Copyright © 2026 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 clusters = require "st.matter.clusters" -local cluster_base = require "st.matter.cluster_base" -local descriptor = require "st.matter.generated.zap_clusters.Descriptor" - -local MATTER_DEVICE_ID = "MATTER_DEVICE_ID" -local PARENT_ID = "PARENT_ID" -local BUTTON_EPS = "__button_eps" - -local function create_parent_device(product_id) - return test.mock_device.build_test_matter_device({ - label = "Hager G2 4x Button", - profile = t_utils.get_profile_definition("matter-bridge.yml"), - manufacturer_info = { - vendor_id = 0x1285, - product_id = product_id, - }, - endpoints = { - { - endpoint_id = 0, - clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, - device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } - }, - { - endpoint_id = 1, - clusters = { - { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } - }, - device_types = { { device_type_id = 0x000E, device_type_revision = 1 } } -- AggregateNode - }, - { - endpoint_id = 2, - clusters = { - { - cluster_id = clusters.Descriptor.ID, - cluster_type = "SERVER", - cluster_revision = 1, - } - }, - device_types = { { device_type_id = 0x0039, device_type_revision = 1 } } -- BridgedNode - } - } - }) -end - -local function create_matter_device(profile_name, parent) - return test.mock_device.build_test_matter_device({ - label = "Hager G2 4x Button", - profile = t_utils.get_profile_definition(profile_name .. ".yml"), - type = "MATTER", - manufacturer_info = { - vendor_id = 0x1285, - product_id = 0x0006, - }, - parent_device_id = parent.id, - endpoints = { - { - endpoint_id = 0, - clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, - device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } - }, - { - endpoint_id = 8, - clusters = { - { - cluster_id = clusters.Switch.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, - attributes = { - [clusters.Switch.attributes.MultiPressMax.ID] = 2 - } - } - }, - device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } - }, - { - endpoint_id = 9, - clusters = { - { - cluster_id = clusters.Switch.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, - attributes = { - [clusters.Switch.attributes.MultiPressMax.ID] = 2 - } - } - }, - device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } - }, - { - endpoint_id = 10, - clusters = { - { - cluster_id = clusters.Switch.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, - attributes = { - [clusters.Switch.attributes.MultiPressMax.ID] = 2 - } - } - }, - device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } - }, - { - endpoint_id = 11, - clusters = { - { - cluster_id = clusters.Switch.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, - attributes = { - [clusters.Switch.attributes.MultiPressMax.ID] = 2 - } - } - }, - device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } - }, - } - }) -end - --- Create Hager 2G Relay device with endpoints 3 & 4 (OnOff clusters) -local function create_hager_2g_relay(profile_name, parent) - return test.mock_device.build_test_matter_device({ - label = "Hager G2 2G Relay", - profile = t_utils.get_profile_definition(profile_name .. ".yml"), - type = "MATTER", - manufacturer_info = { - vendor_id = 0x1285, - product_id = 0x0006, - }, - parent_device_id = parent.id, - endpoints = { - { - endpoint_id = 0, - clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, - device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } - }, - { - endpoint_id = 3, - clusters = { - { - cluster_id = clusters.OnOff.ID, - cluster_type = "SERVER", - cluster_revision = 1, - attributes = { - [clusters.OnOff.attributes.OnOff.ID] = false - } - } - }, - device_types = { { device_type_id = 0x0100, device_type_revision = 1 } } - }, - { - endpoint_id = 4, - clusters = { - { - cluster_id = clusters.OnOff.ID, - cluster_type = "SERVER", - cluster_revision = 1, - attributes = { - [clusters.OnOff.attributes.OnOff.ID] = false - } - } - }, - device_types = { { device_type_id = 0x0100, device_type_revision = 1 } } - }, - } - }) -end - --- Create Hager Dimmer device with ONLY dimmable endpoint (3) - no button endpoints -local function create_hager_dimmer_device_1g(profile_name, parent) - return test.mock_device.build_test_matter_device({ - label = "Hager G2 Dimmer", - profile = t_utils.get_profile_definition(profile_name .. ".yml"), - type = "MATTER", - manufacturer_info = { - vendor_id = 0x1285, - product_id = 0x0005, - }, - parent_device_id = parent.id, - endpoints = { - { - endpoint_id = 0, - clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, - device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } - }, - { - endpoint_id = 4, - clusters = { - { - cluster_id = clusters.OnOff.ID, - cluster_type = "SERVER", - cluster_revision = 1, - attributes = { - [clusters.OnOff.attributes.OnOff.ID] = false - } - }, - { - cluster_id = clusters.LevelControl.ID, - cluster_type = "SERVER", - cluster_revision = 1, - attributes = { - [clusters.LevelControl.attributes.CurrentLevel.ID] = 254 - } - } - }, - device_types = { { device_type_id = 0x0101, device_type_revision = 1 } } - }, - } - }) -end - -local function create_hager_dimmer_device_2g(profile_name, parent) - return test.mock_device.build_test_matter_device({ - label = "Hager G2 Dimmer", - profile = t_utils.get_profile_definition(profile_name .. ".yml"), - manufacturer_info = { - vendor_id = 0x1285, - product_id = 0x0006, - }, - parent_device_id = parent.id, - endpoints = { - { - endpoint_id = 0, - clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, - device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } - }, - { - endpoint_id = 3, - clusters = { - { - cluster_id = clusters.OnOff.ID, - cluster_type = "SERVER", - cluster_revision = 1, - attributes = { - [clusters.OnOff.attributes.OnOff.ID] = false - } - }, - { - cluster_id = clusters.LevelControl.ID, - cluster_type = "SERVER", - cluster_revision = 1, - attributes = { - [clusters.LevelControl.attributes.CurrentLevel.ID] = 254 - } - } - }, - device_types = { { device_type_id = 0x0101, device_type_revision = 1 } } - }, - { - endpoint_id = 8, - clusters = { - { - cluster_id = clusters.Switch.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, - attributes = { - [clusters.Switch.attributes.MultiPressMax.ID] = 2 - } - } - }, - device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } - }, - { - endpoint_id = 9, - clusters = { - { - cluster_id = clusters.Switch.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, - attributes = { - [clusters.Switch.attributes.MultiPressMax.ID] = 2 - } - } - }, - device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } - }, - } - }) -end - -local function create_matter_device_with_window(profile_name, parent) - return test.mock_device.build_test_matter_device({ - label = "Hager G2 matter device with Window Covering", - profile = t_utils.get_profile_definition(profile_name .. ".yml"), - manufacturer_info = { - vendor_id = 0x1285, - product_id = 0x0006, - }, - parent_device_id = parent.id, - endpoints = { - { - endpoint_id = 0, - clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, - device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } - }, - { - endpoint_id = 8, - clusters = { - { - cluster_id = clusters.Switch.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, - attributes = { - [clusters.Switch.attributes.MultiPressMax.ID] = 2 - } - } - }, - device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } - }, - { - endpoint_id = 9, - clusters = { - { - cluster_id = clusters.Switch.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, - attributes = { - [clusters.Switch.attributes.MultiPressMax.ID] = 2 - } - } - }, - device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } - }, - { - endpoint_id = 12, - clusters = { - { - cluster_id = clusters.WindowCovering.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = clusters.WindowCovering.types.Feature.LIFT | - clusters.WindowCovering.types.Feature.POSITION_AWARE_LIFT | - clusters.WindowCovering.types.Feature.ABSOLUTE_POSITION, - attributes = { - [clusters.WindowCovering.attributes.OperationalStatus.ID] = 0x00, - [clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID] = 0x0000, - } - } - }, - device_types = { { device_type_id = 0x0202, device_type_revision = 1 } } - }, - } - }) -end - -local function create_hager_pir_device(profile_name, parent) - return test.mock_device.build_test_matter_device({ - label = "Hager G2 PIR with Buttons and Motion/Illuminance/Dimmer", - profile = t_utils.get_profile_definition(profile_name .. ".yml"), - type = "MATTER", - manufacturer_info = { - vendor_id = 0x1285, - product_id = 0x0007, - }, - parent_device_id = parent.id, - endpoints = { - { - endpoint_id = 0, - clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, - device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } - }, - { - endpoint_id = 3, - clusters = { - { - cluster_id = clusters.OnOff.ID, - cluster_type = "SERVER", - cluster_revision = 1, - attributes = { - [clusters.OnOff.attributes.OnOff.ID] = false - } - }, - { - cluster_id = clusters.LevelControl.ID, - cluster_type = "SERVER", - cluster_revision = 1, - attributes = { - [clusters.LevelControl.attributes.CurrentLevel.ID] = 254 - } - } - }, - device_types = { { device_type_id = 0x0101, device_type_revision = 1 } } - }, - { - endpoint_id = 6, - clusters = { - { - cluster_id = clusters.OccupancySensing.ID, - cluster_type = "SERVER", - cluster_revision = 1, - attributes = { - [clusters.OccupancySensing.attributes.Occupancy.ID] = 0 - } - } - }, - device_types = { { device_type_id = 0x0107, device_type_revision = 1 } } - }, - { - endpoint_id = 5, - clusters = { - { - cluster_id = clusters.IlluminanceMeasurement.ID, - cluster_type = "SERVER", - cluster_revision = 1, - attributes = { - [clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID] = 0 - } - } - }, - device_types = { { device_type_id = 0x0106, device_type_revision = 1 } } - }, - } - }) -end - -local function add_parent_device(parent) - test.mock_device.add_test_device(parent) - test.socket.device_lifecycle:__queue_receive({ parent.id, "added" }) - test.socket.device_lifecycle:__queue_receive({ parent.id, "init" }) - test.mock_time.advance_time(5) - test.socket.matter:__expect_send({ - parent.id, - cluster_base.subscribe(parent, 2, descriptor.ID, descriptor.attributes.PartsList.ID, nil) - }) -end - -local function subscribe_switch_events(parent, button_eps) - for _, ep in ipairs(button_eps) do - test.socket.matter:__expect_send({ - parent.id, - clusters.Switch.events.MultiPressComplete:subscribe(parent, ep) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Switch.events.LongPress:subscribe(parent, ep) - }) - end -end - -local function add_matter_device(matter_device, parent, expected_profile_change) - test.mock_device.add_test_device(matter_device) - test.socket.device_lifecycle:__queue_receive({ matter_device.id, "added" }) - test.socket.device_lifecycle:__queue_receive({ matter_device.id, "doConfigure" }) - matter_device:expect_metadata_update({ profile = expected_profile_change }) - matter_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - test.socket.device_lifecycle:__queue_receive({ matter_device.id, "init" }) - - matter_device:set_field(PARENT_ID, parent.id, { persist = true }) - matter_device:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) - parent:set_field(PARENT_ID, parent.id, { persist = true }) - parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) - parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) -end - -local function add_switch_matter_device(matter_device, parent) - test.mock_device.add_test_device(matter_device) - test.socket.device_lifecycle:__queue_receive({ matter_device.id, "added" }) - test.socket.device_lifecycle:__queue_receive({ matter_device.id, "doConfigure" }) - matter_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - test.socket.device_lifecycle:__queue_receive({ matter_device.id, "init" }) - - matter_device:set_field(PARENT_ID, parent.id, { persist = true }) - matter_device:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) - parent:set_field(PARENT_ID, parent.id, { persist = true }) - parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) - parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) -end - -local function button_supported_values (matter_device) - test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) - test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) - test.socket.capability:__expect_send(matter_device:generate_test_message("button3", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) - test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) -end - -local function initiate_info_changed(device, profile) - test.socket.device_lifecycle:__queue_receive(device:generate_info_changed({ profile = { id = profile } })) - test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") - test.mock_time.advance_time(3) -end - -local function configure_parent(device) - test.socket.device_lifecycle:__queue_receive({ device.id, "doConfigure" }) - device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - test.socket.device_lifecycle:__queue_receive({ device.id, "init" }) - test.socket.matter:__expect_send({ - device.id, - cluster_base.subscribe(device, 2, descriptor.ID, descriptor.attributes.PartsList.ID, nil) - }) - test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") - test.mock_time.advance_time(3) -end - -local parent = create_parent_device(0x0006) -- 2G button product -local parent_1g = create_parent_device(0x0005) -- 1G button product -local parent_pir = create_parent_device(0x0007) -- PIR product - -local function test_init() - test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") -end - -test.set_test_init_function(test_init) - -test.register_coroutine_test("Test: 4-Button Device Detection - Profile Changes from matter-bridge to 4-button When Four Button Endpoints Present", function() - test.socket.matter:__set_channel_ordering("relaxed") - add_parent_device(parent) - local matter_device = create_matter_device("matter-bridge", parent) - add_matter_device(matter_device, parent, "4-button") - test.wait_for_events() - configure_parent(parent) - test.wait_for_events() - - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(8), - data_types.Uint16(9), - data_types.Uint16(10), - data_types.Uint16(11), - })) - }) - - local matter_device_parent_id = matter_device:get_field(PARENT_ID) - local matter_device_id = matter_device:get_field(MATTER_DEVICE_ID) - local parent_id = parent:get_field(PARENT_ID) - local parent_matter_device_id = parent:get_field(MATTER_DEVICE_ID) - - assert(matter_device_parent_id == parent.id, "link_host_and_subhub 1/4") - assert(matter_device_id == matter_device.id, "link_host_and_subhub 2/4") - assert(parent_id == parent.id, "link_host_and_subhub 3/4") - assert(parent_matter_device_id == matter_device.id, "link_host_and_subhub 4/4") - - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x08) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x09) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x0A) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x0B) - }) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array({ - { device_type = 0x000F, revision = 0x0003 } - })) - }) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array({ - { device_type = 0x000F, revision = 0x0003 } - })) - }) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 10, data_types.Array({ - { device_type = 0x000F, revision = 0x0003 } - })) - }) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 11, data_types.Array({ - { device_type = 0x000F, revision = 0x0003 } - })) - }) - -end) - -test.register_coroutine_test("Test: Button Event Handling - Pushed, Double Press, and Held Events on 4-Button Device", function() - test.socket.matter:__set_channel_ordering("relaxed") - add_parent_device(parent) - local matter_device = create_matter_device("4-button", parent) - add_matter_device(matter_device, parent, "4-button") - test.wait_for_events() - configure_parent(parent) - test.wait_for_events() - - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(8), - data_types.Uint16(9), - data_types.Uint16(10), - data_types.Uint16(11), - })) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x08) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x09) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x0A) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x0B) - }) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array({ - { device_type = 0x000F, revision = 0x0003 } - })) - }) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array({ - { device_type = 0x000F, revision = 0x0003 } - })) - }) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 10, data_types.Array({ - { device_type = 0x000F, revision = 0x0003 } - })) - }) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 11, data_types.Array({ - { device_type = 0x000F, revision = 0x0003 } - })) - }) - test.wait_for_events() - initiate_info_changed(matter_device, "4-button") - - subscribe_switch_events(parent, { 8, 9, 10, 11 }) - button_supported_values(matter_device) - - - -- Test single press (pushed) on endpoint 8 (button1) - test.wait_for_events() - test.socket.matter:__queue_receive({ - matter_device.id, - clusters.Switch.events.MultiPressComplete:build_test_event_report( - matter_device, 8, { - new_position = 1, - previous_position = 1, - total_number_of_presses_counted = 1 -- Single press - } - ) - }) - test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) - - --Test double press on endpoint 8 (button1) - test.socket.matter:__queue_receive({ - matter_device.id, - clusters.Switch.events.MultiPressComplete:build_test_event_report( - matter_device, 8, { - new_position = 1, - previous_position = 1, - total_number_of_presses_counted = 2 - } - ) - }) - test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.button.double({ state_change = true }))) - test.socket.matter:__queue_receive({ - matter_device.id, - clusters.Switch.events.LongPress:build_test_event_report( - matter_device, 8, { - new_position = 1, - previous_position = 0, - } - ) - }) - test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.button.held({ state_change = true }))) - - -- Test press on endpoint 9 (button2) - test.socket.matter:__queue_receive({ - matter_device.id, - clusters.Switch.events.MultiPressComplete:build_test_event_report( - matter_device, 9, { - new_position = 1, - previous_position = 1, - total_number_of_presses_counted = 1 - } - ) - }) - test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.button.pushed({ state_change = true }))) - test.socket.matter:__queue_receive({ - matter_device.id, - clusters.Switch.events.MultiPressComplete:build_test_event_report( - matter_device, 9, { - new_position = 1, - previous_position = 1, - total_number_of_presses_counted = 2 - } - ) - }) - test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.button.double({ state_change = true }))) - test.socket.matter:__queue_receive({ - matter_device.id, - clusters.Switch.events.LongPress:build_test_event_report( - matter_device, 9, { - new_position = 1, - previous_position = 0, - } - ) - }) - test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.button.held({ state_change = true }))) - - -- Test press on endpoint 10 (button3) - test.socket.matter:__queue_receive({ - matter_device.id, - clusters.Switch.events.MultiPressComplete:build_test_event_report( - matter_device, 10, { - new_position = 1, - previous_position = 1, - total_number_of_presses_counted = 1 - } - ) - }) - test.socket.capability:__expect_send(matter_device:generate_test_message("button3", capabilities.button.button.pushed({ state_change = true }))) - test.socket.matter:__queue_receive({ - matter_device.id, - clusters.Switch.events.MultiPressComplete:build_test_event_report( - matter_device, 10, { - new_position = 1, - previous_position = 1, - total_number_of_presses_counted = 2 - } - ) - }) - test.socket.capability:__expect_send(matter_device:generate_test_message("button3", capabilities.button.button.double({ state_change = true }))) - test.socket.matter:__queue_receive({ - matter_device.id, - clusters.Switch.events.LongPress:build_test_event_report( - matter_device, 10, { - new_position = 1, - previous_position = 0, - } - ) - }) - test.socket.capability:__expect_send(matter_device:generate_test_message("button3", capabilities.button.button.held({ state_change = true }))) - - -- Test press on endpoint 11 (button4) - test.socket.matter:__queue_receive({ - matter_device.id, - clusters.Switch.events.MultiPressComplete:build_test_event_report( - matter_device, 11, { - new_position = 1, - previous_position = 1, - total_number_of_presses_counted = 1 - } - ) - }) - test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.pushed({ state_change = true }))) - test.socket.matter:__queue_receive({ - matter_device.id, - clusters.Switch.events.MultiPressComplete:build_test_event_report( - matter_device, 11, { - new_position = 1, - previous_position = 1, - total_number_of_presses_counted = 2 - } - ) - }) - test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.double({ state_change = true }))) - test.socket.matter:__queue_receive({ - matter_device.id, - clusters.Switch.events.LongPress:build_test_event_report( - matter_device, 11, { - new_position = 1, - previous_position = 0, - } - ) - }) - test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.held({ state_change = true }))) -end) - -test.register_coroutine_test("Test: Device Type Handler - Handles Button (Type 15) and OnOff (Type 256) Device Types with Child Creation", function() - test.socket.matter:__set_channel_ordering("relaxed") - add_parent_device(parent) - local matter_device = create_matter_device("4-button", parent) - add_matter_device(matter_device, parent, "4-button") - test.wait_for_events() - configure_parent(parent) - test.wait_for_events() - - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(9), - data_types.Uint16(8), - data_types.Uint16(6), - })) - }) - - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 6) - }) - - test.wait_for_events() - - -- Receive DeviceTypeList report with device type 15 (button) for endpoint 8 - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array { - { - device_type = data_types.Uint32(15), - revision = data_types.Uint16(1) - } - }) - }) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array { - { - device_type = data_types.Uint32(15), - revision = data_types.Uint16(1) - } - }) - }) - - -- Receive DeviceTypeList report with device type 256 (OnOff) for endpoint 6 - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 6, data_types.Array { - { - device_type = data_types.Uint32(256), - revision = data_types.Uint16(1) - } - }) - }) - parent:expect_device_create({ - type = "EDGE_CHILD", - label = "Hager G2 4x Button 1", - profile = "light-binary", - parent_device_id = parent.id, - parent_assigned_child_key = "6" - }) - test.wait_for_events() - initiate_info_changed(matter_device, "2-button") - subscribe_switch_events(parent, { 8, 9 }) - test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) - test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) - - test.wait_for_events() - - local child_switch = test.mock_device.build_test_child_device({ - profile = t_utils.get_profile_definition("light-binary.yml"), - device_network_id = string.format("%s:6", parent.id), - parent_device_id = parent.id, - parent_assigned_child_key = "6" - }) - test.wait_for_events() - initiate_info_changed(child_switch, "light-binary") - - test.socket.matter:__expect_send({ - parent.id, - clusters.OnOff.attributes.OnOff:subscribe(parent, nil) - }) -end) - -test.register_coroutine_test("Test: 2G Relay - Profile Changes Between light-binary and 2-button Based On Endpoint Availability", function() - test.socket.matter:__set_channel_ordering("relaxed") - test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") - test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") - add_parent_device(parent) - local relay = create_hager_2g_relay("light-binary", parent) - add_switch_matter_device(relay, parent) - test.wait_for_events() - configure_parent(parent) - test.wait_for_events() - - -- Scenario 1: EP3 + EP4 present → light-binary profile - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(3), - data_types.Uint16(4), - })) - }) - -- - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 3) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 4) - }) - - relay:expect_metadata_update({ profile = "light-binary" }) - relay:expect_metadata_update({ profile = "light-binary" }) - test.wait_for_events() - -- Both endpoints are device type 256 (OnOff) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 3, data_types.Array { - { - device_type = data_types.Uint32(256), - revision = data_types.Uint16(1) - } - }) - }) - - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 4, data_types.Array { - { - device_type = data_types.Uint32(256), - revision = data_types.Uint16(1) - } - }) - }) - - parent:expect_device_create({ - type = "EDGE_CHILD", - label = "Hager G2 4x Button 1", - profile = "light-binary", - parent_device_id = parent.id, - parent_assigned_child_key = "4" - }) - local child_switch = test.mock_device.build_test_child_device({ - profile = t_utils.get_profile_definition("light-binary.yml"), - device_network_id = string.format("%s:4", parent.id), - parent_device_id = parent.id, - parent_assigned_child_key = "4" - }) - - initiate_info_changed(relay, "light-binary") - test.socket.matter:__expect_send({ - parent.id, - clusters.OnOff.attributes.OnOff:subscribe(parent, nil) - }) - - initiate_info_changed(child_switch, "light-binary") - test.socket.matter:__expect_send({ - parent.id, - clusters.OnOff.attributes.OnOff:subscribe(parent, nil) - }) - - --Scenario 2: EP4 removed → profile changes to 2-button, child created for EP3 - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(3), - data_types.Uint16(8), - data_types.Uint16(9), - })) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) - }) - - parent:expect_device_create({ - type = "EDGE_CHILD", - label = "Hager G2 4x Button 1", - profile = "light-binary", - parent_device_id = parent.id, - parent_assigned_child_key = "3" - }) - - relay:expect_metadata_update({ profile = "2-button" }) - - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array { - { - device_type = data_types.Uint32(15), - revision = data_types.Uint16(1) - } - }) - }) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array { - { - device_type = data_types.Uint32(15), - revision = data_types.Uint16(1) - } - }) - }) - - -- Scenario 3: EP4 reappears → profile changes back to light-binary - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(3), - data_types.Uint16(4), - })) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 4) - }) - relay:expect_metadata_update({ profile = "light-binary" }) - - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 4, data_types.Array { - { - device_type = data_types.Uint32(256), - revision = data_types.Uint16(1) - } - }) - }) - - parent:expect_device_create({ - type = "EDGE_CHILD", - label = "Hager G2 4x Button 1", - profile = "light-binary", - parent_device_id = parent.id, - parent_assigned_child_key = "4" - }) - - -- Scenario 4: EP3 removed → profile changes to 2-button, child created for EP4 - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(4), - data_types.Uint16(12), - data_types.Uint16(13), - })) - }) - - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 12) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 13) - }) - - relay:expect_metadata_update({ profile = "2-button" }) - - -- Scenario 5: EP3 and EP4 removed → 4-button profile - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(8), - data_types.Uint16(9), - data_types.Uint16(10), - data_types.Uint16(11), - })) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 10) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 11) - }) - relay:expect_metadata_update({ profile = "4-button" }) - - -- Scenario 6: Only EP4 present → 2-button profile, child created - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(4), - data_types.Uint16(8), - data_types.Uint16(9), - })) - }) - - relay:expect_metadata_update({ profile = "2-button" }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 4) - }) - - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 4, data_types.Array { - { - device_type = data_types.Uint32(256), - revision = data_types.Uint16(1) - } - }) - }) - - parent:expect_device_create({ - type = "EDGE_CHILD", - label = "Hager G2 4x Button 1", - profile = "light-binary", - parent_device_id = parent.id, - parent_assigned_child_key = "4" - }) -end) - -test.register_coroutine_test("Test: Dimmer Device - Child Creation for Dimmable Endpoint with Button Support", function() - test.socket.matter:__set_channel_ordering("relaxed") - - add_parent_device(parent) - local dimmer = create_hager_dimmer_device_2g("2-button", parent) - add_matter_device(dimmer, parent, "2-button") - test.wait_for_events() - configure_parent(parent) - test.wait_for_events() - - --Scenario 1: 2 button endpoints (8, 9) detected → 2-button profile - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(8), - data_types.Uint16(9), - data_types.Uint16(3) - })) - }) - - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 3) - }) - dimmer:expect_metadata_update({ profile = "2-button" }) - - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 3, data_types.Array { - { - device_type = data_types.Uint32(257), - revision = data_types.Uint16(1) - } - }) - }) - - parent:expect_device_create({ - type = "EDGE_CHILD", - label = "Hager G2 4x Button 1", - profile = "light-level", - parent_device_id = parent.id, - parent_assigned_child_key = "3" - }) - - -- Create mock child device for EP3 - local child_dimmer = test.mock_device.build_test_child_device({ - profile = t_utils.get_profile_definition("light-level.yml"), - device_network_id = string.format("%s:3", parent.id), - parent_device_id = parent.id, - parent_assigned_child_key = "3" - }) - initiate_info_changed(child_dimmer, "light-level") - - test.socket.matter:__expect_send({ - parent.id, - cluster_base.subscribe(parent, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) - }) - test.socket.matter:__expect_send({ - parent.id, - cluster_base.subscribe(parent, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.CurrentLevel.ID, nil) - }) - test.socket.matter:__expect_send({ - parent.id, - cluster_base.subscribe(parent, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MaxLevel.ID, nil) - }) - test.socket.matter:__expect_send({ - parent.id, - cluster_base.subscribe(parent, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID, nil) - }) - - test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switch", component = "main", command = "on", args = {} } }) - child_dimmer.expect_native_cmd_handler_registration(child_dimmer, "switch", "on") - - test.socket.matter:__expect_send({ - parent.id, - clusters.OnOff.server.commands.On(child_dimmer, 3) - }) - test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 20 } } }) - - child_dimmer.expect_native_cmd_handler_registration(child_dimmer, "switchLevel", "setLevel") - - test.socket.matter:__expect_send({ - parent.id, - clusters.LevelControl.server.commands.MoveToLevelWithOnOff(child_dimmer, 3, 51, nil, 0, 0) - }) - -end) - -test.register_coroutine_test("Test: 1G Dimmer - Initialization and Profile Update to light-level", function() - test.socket.matter:__set_channel_ordering("relaxed") - add_parent_device(parent) - local dimmer = create_hager_dimmer_device_2g("2-button", parent) - add_matter_device(dimmer, parent, "2-button") - test.wait_for_events() - configure_parent(parent) -end) - -test.register_coroutine_test("Test: 1G Dimmer - Host Commands and Level Control Capabilities", function() - test.socket.matter:__set_channel_ordering("relaxed") - test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") - - add_parent_device(parent_1g) - local dimmer = create_hager_dimmer_device_1g("light-level", parent_1g) - add_matter_device(dimmer, parent_1g, "light-level") - test.wait_for_events() - configure_parent(parent_1g) - - -- Send dimmable endpoint 4 detection (device type 257) to trigger profile change and subscriptions - test.socket.matter:__queue_receive({ - parent_1g.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_1g, 2, data_types.Array({ - data_types.Uint16(4), - })) - }) - - test.socket.matter:__expect_send({ - parent_1g.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 4) - }) - - test.wait_for_events() - - test.socket.matter:__queue_receive({ - parent_1g.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_1g, 4, data_types.Array { - { - device_type = data_types.Uint32(257), - revision = data_types.Uint16(1) - } - }) - }) - - ----subscriptions from device_type_handler - - initiate_info_changed(dimmer, "light-level") - test.socket.matter:__expect_send({ - parent_1g.id, - cluster_base.subscribe(parent_1g, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) - }) - test.socket.matter:__expect_send({ - parent_1g.id, - cluster_base.subscribe(parent_1g, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.CurrentLevel.ID, nil) - }) - test.socket.matter:__expect_send({ - parent_1g.id, - cluster_base.subscribe(parent_1g, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MaxLevel.ID, nil) - }) - test.socket.matter:__expect_send({ - parent_1g.id, - cluster_base.subscribe(parent_1g, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID, nil) - }) - - test.socket.capability:__queue_receive({ dimmer.id, { capability = "switch", component = "main", command = "on", args = {} } }) - dimmer.expect_native_cmd_handler_registration(dimmer, "switch", "on") - - test.socket.matter:__expect_send({ - parent_1g.id, - clusters.OnOff.server.commands.On(dimmer, 4) - }) - test.socket.capability:__queue_receive({ dimmer.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 20 } } }) - - dimmer.expect_native_cmd_handler_registration(dimmer, "switchLevel", "setLevel") - - test.socket.matter:__expect_send({ - parent_1g.id, - clusters.LevelControl.server.commands.MoveToLevelWithOnOff(dimmer, 4, 51, nil, 0, 0) - }) -end) - -test.register_coroutine_test("Test: PIR Device - Initialization with Motion and Illuminance Capabilities", function() - test.socket.matter:__set_channel_ordering("relaxed") - add_parent_device(parent_pir) - local pir_device = create_hager_pir_device("motion-illuminance", parent_pir) - add_matter_device(pir_device, parent_pir, "motion-illuminance") - test.wait_for_events() - configure_parent(parent_pir) - -end) - -test.register_coroutine_test("Test: PIR Device - Complete Functionality with Motion, Illuminance, and Dimmer Support", function() - test.socket.matter:__set_channel_ordering("relaxed") - add_parent_device(parent_pir) - local pir_device = create_hager_pir_device("motion-illuminance", parent_pir) - add_matter_device(pir_device, parent_pir, "motion-illuminance") - test.wait_for_events() - - configure_parent(parent_pir) - - test.wait_for_events() - - test.socket.matter:__queue_receive({ - parent_pir.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_pir, 2, data_types.Array({ - data_types.Uint16(3), - data_types.Uint16(4), - data_types.Uint16(5) - - })) - }) - test.socket.matter:__expect_send({ - parent_pir.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_pir, 3) - }) - test.socket.matter:__expect_send({ - parent_pir.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_pir, 4) - }) - test.socket.matter:__expect_send({ - parent_pir.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_pir, 5) - }) - - test.socket.matter:__queue_receive({ - parent_pir.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_pir, 4, data_types.Array { - { - device_type = data_types.Uint32(263), - revision = data_types.Uint16(1) - } - }) - }) - test.socket.matter:__queue_receive({ - parent_pir.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_pir, 5, data_types.Array { - { - device_type = data_types.Uint32(262), - revision = data_types.Uint16(1) - } - }) - }) - test.socket.matter:__queue_receive({ - parent_pir.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_pir, 3, data_types.Array { - { - device_type = data_types.Uint32(257), - revision = data_types.Uint16(1) - } - }) - }) - - parent_pir:expect_device_create({ - type = "EDGE_CHILD", - label = "Hager G2 4x Button 1", - profile = "light-level", - parent_device_id = parent_pir.id, - parent_assigned_child_key = "3" - }) - test.wait_for_events() - initiate_info_changed(pir_device, "motion-illuminance") - test.socket.matter:__expect_send({ - parent_pir.id, - cluster_base.subscribe(parent_pir, nil, clusters.IlluminanceMeasurement.ID, clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID, nil) - }) - test.socket.matter:__expect_send({ - parent_pir.id, - cluster_base.subscribe(parent_pir, nil, clusters.OccupancySensing.ID, clusters.OccupancySensing.attributes.Occupancy.ID, nil) - }) - - local child_dimmer = test.mock_device.build_test_child_device({ - label = "Hager G2 4x Button 1", - profile = t_utils.get_profile_definition("light-level.yml"), - device_network_id = string.format("%s:3", parent_pir.id), - parent_device_id = parent_pir.id, - parent_assigned_child_key = "3" - }) - - test.mock_device.add_test_device(child_dimmer) - initiate_info_changed(child_dimmer, "light-level") - test.socket.matter:__expect_send({ - parent_pir.id, - cluster_base.subscribe(parent_pir, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) - }) - test.socket.matter:__expect_send({ - parent_pir.id, - cluster_base.subscribe(parent_pir, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.CurrentLevel.ID, nil) - }) - test.socket.matter:__expect_send({ - parent_pir.id, - cluster_base.subscribe(parent_pir, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MaxLevel.ID, nil) - }) - test.socket.matter:__expect_send({ - parent_pir.id, - cluster_base.subscribe(parent_pir, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID, nil) - }) - test.wait_for_events() - --Verify motion detected event - test.socket.matter:__queue_receive({ - parent_pir.id, - clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(parent_pir, 6, 1) - }) - - test.socket.capability:__expect_send(pir_device:generate_test_message("main", capabilities.motionSensor.motion.active())) - - -- Verify illuminance measurement event - test.socket.matter:__queue_receive({ - parent_pir.id, - clusters.IlluminanceMeasurement.attributes.MeasuredValue:build_test_report_data(parent_pir, 5, 21370) - }) - test.socket.capability:__expect_send(pir_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance(137))) - - -- Send on command to OnOff endpoint (dimmer) - child_dimmer.expect_native_cmd_handler_registration(child_dimmer, "switch", "on") - - test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switch", component = "main", command = "on", args = {} } }) - test.socket.matter:__expect_send({ - parent_pir.id, - clusters.OnOff.commands.On(parent_pir, 3) - }) - -- Verify on state via attribute report - test.socket.matter:__queue_receive({ - parent_pir.id, - clusters.OnOff.attributes.OnOff:build_test_report_data(parent_pir, 3, true) - }) - test.socket.capability:__expect_send(child_dimmer:generate_test_message("main", capabilities.switch.switch.on())) - parent_pir.expect_native_attr_handler_registration(parent_pir, "switch", "switch") - - -- Set dimmer level to 50% - test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 50 } } }) - test.socket.matter:__expect_send({ - parent_pir.id, - clusters.LevelControl.commands.MoveToLevelWithOnOff(parent_pir, 3, 127, nil, 0, 0) - }) - child_dimmer.expect_native_cmd_handler_registration(child_dimmer, "switchLevel", "setLevel") - - -- Verify level via attribute report - test.socket.matter:__queue_receive({ - parent_pir.id, - clusters.LevelControl.attributes.CurrentLevel:build_test_report_data(parent_pir, 3, 127) - }) - test.socket.capability:__expect_send(child_dimmer:generate_test_message("main", capabilities.switchLevel.level(50))) - parent_pir.expect_native_attr_handler_registration(parent_pir, "switchLevel", "level") -end) - -test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile with Window Covering Child Device", function() - test.socket.matter:__set_channel_ordering("relaxed") - add_parent_device(parent) - local window_covering_device = create_matter_device_with_window("2-button", parent) - add_matter_device(window_covering_device, parent, "2-button") - test.wait_for_events() - configure_parent(parent) - - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(8), - data_types.Uint16(9), - data_types.Uint16(12), - })) - }) - - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 12) - }) - - -- DeviceTypeList report for window covering endpoint (type 514 = Window Covering Device) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 12, data_types.Array { - { - device_type = data_types.Uint32(514), - revision = data_types.Uint16(1) - } - }) - }) - - parent:expect_device_create({ - type = "EDGE_CHILD", - label = "Hager G2 4x Button 1", - profile = "window-covering", - parent_device_id = parent.id, - parent_assigned_child_key = "12" - }) - - local child_wc = test.mock_device.build_test_child_device({ - profile = t_utils.get_profile_definition("window-covering.yml"), - device_network_id = string.format("%s:12", parent.id), - parent_device_id = parent.id, - parent_assigned_child_key = "12" - }) - - test.mock_device.add_test_device(child_wc) - initiate_info_changed(child_wc, "window-covering") - - test.socket.matter:__expect_send({ - parent.id, - cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.OperationalStatus.ID, nil) - }) - test.socket.matter:__expect_send({ - parent.id, - cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) - }) - - -- Open command - test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "open", args = {} } }) - test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.UpOrOpen(parent, 12) }) - test.wait_for_events() - - -- Verify open state via attribute report - test.socket.matter:__queue_receive({ - parent.id, - clusters.WindowCovering.attributes.OperationalStatus:build_test_report_data(parent, 12, 0x01) - }) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.opening())) - - - -- Close command - test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "close", args = {} } }) - test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.DownOrClose(parent, 12) }) - - -- Verify close state via attribute report - test.socket.matter:__queue_receive({ - parent.id, - clusters.WindowCovering.attributes.OperationalStatus:build_test_report_data(parent, 12, 0x02) - }) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closing())) - - -- Pause command - test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "pause", args = {} } }) - test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.StopMotion(parent, 12) }) - test.wait_for_events() - - -- Set shade level to 50% - test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 50 } } }) - test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.GoToLiftPercentage(parent, 12, 5000, nil, 0, 0) }) - test.wait_for_events() - - -- Verify shade level via attribute report - test.socket.matter:__queue_receive({ - parent.id, - clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 5000) - }) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50))) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.partially_open())) - - - -- Set shade level to 100% - test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 100 } } }) - test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.GoToLiftPercentage(parent, 12, 0, nil, 0, 0) }) - test.wait_for_events() - - -- Verify shade level via attribute report - test.socket.matter:__queue_receive({ - parent.id, - clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 0) - }) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100))) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.open())) - - -- Set shade level to 0% - test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 0 } } }) - test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.GoToLiftPercentage(parent, 12, 10000, nil, 0, 0) }) - - -- Verify shade level via attribute report - test.socket.matter:__queue_receive({ - parent.id, - clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 10000) - }) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0))) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closed())) - -end) - -test.register_coroutine_test("Test: Window Covering - Preference Changes for Reverse Polarity and Preset Position", function() - test.socket.matter:__set_channel_ordering("relaxed") - add_parent_device(parent) - local window_covering_device = create_matter_device_with_window("2-button", parent) - add_matter_device(window_covering_device, parent, "2-button") - test.wait_for_events() - configure_parent(parent) - - test.wait_for_events() - - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(8), - data_types.Uint16(9), - data_types.Uint16(12), - })) - }) - -- - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 12) - }) - test.wait_for_events() - - -- DeviceTypeList reports for button endpoints (type 15 = Generic Switch) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array { - { - device_type = data_types.Uint32(15), - revision = data_types.Uint16(1) - } - }) - }) - - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array { - { - device_type = data_types.Uint32(15), - revision = data_types.Uint16(1) - } - }) - }) - - -- DeviceTypeList report for window covering endpoint (type 514 = Window Covering Device) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 12, data_types.Array { - { - device_type = data_types.Uint32(514), - revision = data_types.Uint16(1) - } - }) - }) - - parent:expect_device_create({ - type = "EDGE_CHILD", - label = "Hager G2 4x Button 1", - profile = "window-covering", - parent_device_id = parent.id, - parent_assigned_child_key = "12" - }) - - local child_wc = test.mock_device.build_test_child_device({ - profile = t_utils.get_profile_definition("window-covering.yml"), - device_network_id = string.format("%s:12", parent.id), - parent_device_id = parent.id, - parent_assigned_child_key = "12" - }) - test.mock_device.add_test_device(child_wc) - - test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { reverse = "false" } })) - test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { reverse = "true" } })) - test.wait_for_events() - local reverse_preference_set = child_wc.preferences.reverse - assert(reverse_preference_set == "true", "reverse_preference_set is True") - -- - --Send open command - with reverse_polarity true, this should send DownOrClose - test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "open", args = {} } }) - test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.DownOrClose(parent, 12) }) - - -- Send close command - with reverse_polarity true, this should send UpOrOpen - test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "close", args = {} } }) - test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.UpOrOpen(parent, 12) }) - - -- Send Pause command - test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "pause", args = {} } }) - test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.StopMotion(parent, 12) }) - - -- Position preset testing - test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { presetPosition = "50" } })) - test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { presetPosition = "20" } })) - - test.wait_for_events() - - local PRESET_LEVEL_KEY = child_wc.preferences.presetPosition - assert(PRESET_LEVEL_KEY == "20", " presetPosition is set to 20") - - test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} } }) - test.socket.matter:__expect_send( - { parent.id, clusters.WindowCovering.server.commands.GoToLiftPercentage(parent, 12, 8000) } - ) - - test.wait_for_events() - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(8), - data_types.Uint16(9), - })) - }) - - test.wait_for_events() - - test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") - test.mock_time.advance_time(5) - local BUTTON_EPS_FIELD = parent:get_field(BUTTON_EPS) - assert(BUTTON_EPS_FIELD[1] == 8) - assert(BUTTON_EPS_FIELD[2] == 9) - - test.wait_for_events() - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ - data_types.Uint16(8), - data_types.Uint16(9), - data_types.Uint16(12), - })) - }) - test.socket.matter:__expect_send({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 12) - }) - test.socket.matter:__queue_receive({ - parent.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 12, data_types.Array { - { - device_type = data_types.Uint32(514), - revision = data_types.Uint16(1) - } - }) - }) - child_wc:expect_metadata_update({ profile = "window-covering" }) - initiate_info_changed(child_wc, "window-covering") - - test.socket.matter:__expect_send({ - parent.id, - cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.OperationalStatus.ID, nil) - }) - test.socket.matter:__expect_send({ - parent.id, - cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) - }) -end) - -test.run_registered_tests() From 65a2532558893b02b8e2e6e4862148f545fcf707 Mon Sep 17 00:00:00 2001 From: r-slezak Date: Thu, 2 Jul 2026 17:08:24 +0200 Subject: [PATCH 421/538] Add support for Tuya thermostats (#3071) * Add support for Tuya thermostats * Add more unit tests * Update copyright notice in test_tuya_thermostat.lua Updated copyright information in test_tuya_thermostat.lua. * Update copyright and license comments in init.lua Updated copyright notice and licensing information. * Update test_tuya_thermostat.lua * Update drivers/Unofficial/tuya-zigbee/src/thermostat/init.lua Co-authored-by: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> * Remove thermostatMode enabledValues from profile * Move category to be below capabilities in Tuya Zigbee thermostat profile * Add newline at end of Tuya Zigbee fingerprints --------- Co-authored-by: Konrad K <33450498+KKlimczukS@users.noreply.github.com> Co-authored-by: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> --- .../Unofficial/tuya-zigbee/fingerprints.yml | 7 +- .../tuya-zigbee/profiles/thermostat.yml | 24 + drivers/Unofficial/tuya-zigbee/src/init.lua | 3 +- .../src/test/test_tuya_thermostat.lua | 545 ++++++++++++++++++ .../tuya-zigbee/src/thermostat/init.lua | 187 ++++++ 5 files changed, 764 insertions(+), 2 deletions(-) create mode 100644 drivers/Unofficial/tuya-zigbee/profiles/thermostat.yml create mode 100644 drivers/Unofficial/tuya-zigbee/src/test/test_tuya_thermostat.lua create mode 100644 drivers/Unofficial/tuya-zigbee/src/thermostat/init.lua diff --git a/drivers/Unofficial/tuya-zigbee/fingerprints.yml b/drivers/Unofficial/tuya-zigbee/fingerprints.yml index 7ee7460696..5c77617d1d 100644 --- a/drivers/Unofficial/tuya-zigbee/fingerprints.yml +++ b/drivers/Unofficial/tuya-zigbee/fingerprints.yml @@ -43,4 +43,9 @@ zigbeeManufacturer: deviceLabel: Tuya Switch 1 manufacturer: _TZE204_h2rctifa model: TS0601 - deviceProfileName: basic-switch \ No newline at end of file + deviceProfileName: basic-switch + - id: _TZE284_fziifcxj/TS0601 + deviceLabel: Tuya Thermostat + manufacturer: _TZE284_fziifcxj + model: TS0601 + deviceProfileName: thermostat diff --git a/drivers/Unofficial/tuya-zigbee/profiles/thermostat.yml b/drivers/Unofficial/tuya-zigbee/profiles/thermostat.yml new file mode 100644 index 0000000000..66ecb5661b --- /dev/null +++ b/drivers/Unofficial/tuya-zigbee/profiles/thermostat.yml @@ -0,0 +1,24 @@ +name: thermostat +components: + - id: main + capabilities: + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + config: + values: + - key: "heatingSetpoint.value" + range: [5, 30] + step: 0.5 + - id: thermostatMode + version: 1 + config: + values: + - key: "thermostatMode.value" + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: Thermostat diff --git a/drivers/Unofficial/tuya-zigbee/src/init.lua b/drivers/Unofficial/tuya-zigbee/src/init.lua index 8fb452abb2..6313132933 100644 --- a/drivers/Unofficial/tuya-zigbee/src/init.lua +++ b/drivers/Unofficial/tuya-zigbee/src/init.lua @@ -26,7 +26,8 @@ local unofficial_tuya_driver_template = { require("curtain"), require("motion-sensor"), require("smoke-detector"), - require("switch") + require("switch"), + require("thermostat") }, health_check = false, } diff --git a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_thermostat.lua b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_thermostat.lua new file mode 100644 index 0000000000..4df634bf18 --- /dev/null +++ b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_thermostat.lua @@ -0,0 +1,545 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Mock out globals +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local tuya_utils = require "tuya_utils" + +local Basic = clusters.Basic + +local mock_simple_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("thermostat.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "_TZE284_fziifcxj", + model = "TS0601", + server_clusters = { 0xEF00 } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_simple_device) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle doConfigure lifecycle event", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_simple_device.id, "doConfigure" }) + test.socket.zigbee:__expect_send({ mock_simple_device.id, tuya_utils.build_tuya_magic_spell_message(mock_simple_device) }) + test.socket.zigbee:__expect_send({ mock_simple_device.id, Basic.attributes.ApplicationVersion:configure_reporting(mock_simple_device, 30, 300, 1) }) + test.socket.zigbee:__expect_send({ mock_simple_device.id, zigbee_test_utils.build_bind_request(mock_simple_device, zigbee_test_utils.mock_hub_eui, Basic.ID) }) + mock_simple_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + {} +) + +test.register_coroutine_test( + "Handle added lifecycle event", + function() + test.socket.device_lifecycle:__queue_receive({ mock_simple_device.id, "added" }) + + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message( + "main", + capabilities.thermostatMode.supportedThermostatModes( + { + capabilities.thermostatMode.thermostatMode.antifreezing.NAME, + capabilities.thermostatMode.thermostatMode.auto.NAME, + capabilities.thermostatMode.thermostatMode.comfort.NAME, + capabilities.thermostatMode.thermostatMode.eco.NAME, + capabilities.thermostatMode.thermostatMode.off.NAME, + capabilities.thermostatMode.thermostatMode.on.NAME, + }, + { + visibility = { displayed = false } + } + ) + ) + ) + + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message( + "main", + capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 15.0, unit = "C"}) + ) + ) + + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message( + "main", + capabilities.temperatureMeasurement.temperature({value = 20.0, unit = "C"}) + ) + ) + + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message( + "main", + capabilities.thermostatMode.thermostatMode.auto() + ) + ) + + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message( + "main", + capabilities.battery.battery(100) + ) + ) + end, + {} +) + +test.register_message_test( + "Handle thermostatHeatingSetpoint setHeatingSetpoint", + { + { + channel = "capability", + direction = "receive", + message = { + mock_simple_device.id, + { + capability = "thermostatHeatingSetpoint", + component = "main", + command = "setHeatingSetpoint", + args = {12.5} + } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_simple_device.id, + tuya_utils.build_send_tuya_command( + mock_simple_device, + "\x04", + tuya_utils.DP_TYPE_VALUE, + "\x00\x00\x00\x7D", + 0x00 + ) + } + } + }, + {} +) + +test.register_message_test( + "Handle thermostatMode setThermostatMode (auto)", + { + { + channel = "capability", + direction = "receive", + message = { + mock_simple_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = {"auto"} + } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_simple_device.id, + tuya_utils.build_send_tuya_command( + mock_simple_device, + "\x02", + tuya_utils.DP_TYPE_ENUM, + "\x00", + 0x00 + ) + } + } + }, + {} +) + +test.register_message_test( + "Handle thermostatMode setThermostatMode (off)", + { + { + channel = "capability", + direction = "receive", + message = { + mock_simple_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = {"off"} + } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_simple_device.id, + tuya_utils.build_send_tuya_command( + mock_simple_device, + "\x02", + tuya_utils.DP_TYPE_ENUM, + "\x01", + 0x00 + ) + } + } + }, + {} +) + +test.register_message_test( + "Handle thermostatMode setThermostatMode (on)", + { + { + channel = "capability", + direction = "receive", + message = { + mock_simple_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = {"on"} + } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_simple_device.id, + tuya_utils.build_send_tuya_command( + mock_simple_device, + "\x02", + tuya_utils.DP_TYPE_ENUM, + "\x02", + 0x00 + ) + } + } + }, + {} +) + +test.register_message_test( + "Handle thermostatMode setThermostatMode (comfort)", + { + { + channel = "capability", + direction = "receive", + message = { + mock_simple_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = {"comfort"} + } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_simple_device.id, + tuya_utils.build_send_tuya_command( + mock_simple_device, + "\x02", + tuya_utils.DP_TYPE_ENUM, + "\x03", + 0x00 + ) + } + } + }, + {} +) + +test.register_message_test( + "Handle thermostatMode setThermostatMode (eco)", + { + { + channel = "capability", + direction = "receive", + message = { + mock_simple_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = {"eco"} + } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_simple_device.id, + tuya_utils.build_send_tuya_command( + mock_simple_device, + "\x02", + tuya_utils.DP_TYPE_ENUM, + "\x04", + 0x00 + ) + } + } + }, + {} +) + +test.register_message_test( + "Handle thermostatMode setThermostatMode (antifreezing)", + { + { + channel = "capability", + direction = "receive", + message = { + mock_simple_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = {"antifreezing"} + } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_simple_device.id, + tuya_utils.build_send_tuya_command( + mock_simple_device, + "\x02", + tuya_utils.DP_TYPE_ENUM, + "\x05", + 0x00 + ) + } + } + }, + {} +) + +test.register_message_test( + "Handle tuya cluster message report (heatingSetpoint)", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_simple_device.id, + tuya_utils.build_test_attr_report( + mock_simple_device, + "\x04", + tuya_utils.DP_TYPE_VALUE, + "\x00\x00\x00\x7D", + 0x01 + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_simple_device:generate_test_message( + "main", + capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 12.5, unit = "C"}) + ) + } + }, + {} +) + +test.register_message_test( + "Handle tuya cluster message report (setThermostatMode, auto)", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_simple_device.id, + tuya_utils.build_test_attr_report( + mock_simple_device, + "\x02", + tuya_utils.DP_TYPE_ENUM, + "\x00", + 0x01 + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_simple_device:generate_test_message( + "main", + capabilities.thermostatMode.thermostatMode.auto() + ) + } + }, + {} +) + +test.register_message_test( + "Handle tuya cluster message report (setThermostatMode, off)", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_simple_device.id, + tuya_utils.build_test_attr_report( + mock_simple_device, + "\x02", + tuya_utils.DP_TYPE_ENUM, + "\x01", + 0x01 + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_simple_device:generate_test_message( + "main", + capabilities.thermostatMode.thermostatMode.off() + ) + } + }, + {} +) + +test.register_message_test( + "Handle tuya cluster message report (setThermostatMode, on)", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_simple_device.id, + tuya_utils.build_test_attr_report( + mock_simple_device, + "\x02", + tuya_utils.DP_TYPE_ENUM, + "\x02", + 0x01 + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_simple_device:generate_test_message( + "main", + capabilities.thermostatMode.thermostatMode.on() + ) + } + }, + {} +) + +test.register_message_test( + "Handle tuya cluster message report (setThermostatMode, comfort)", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_simple_device.id, + tuya_utils.build_test_attr_report( + mock_simple_device, + "\x02", + tuya_utils.DP_TYPE_ENUM, + "\x03", + 0x01 + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_simple_device:generate_test_message( + "main", + capabilities.thermostatMode.thermostatMode.comfort() + ) + } + }, + {} +) + +test.register_message_test( + "Handle tuya cluster message report (setThermostatMode, eco)", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_simple_device.id, + tuya_utils.build_test_attr_report( + mock_simple_device, + "\x02", + tuya_utils.DP_TYPE_ENUM, + "\x04", + 0x01 + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_simple_device:generate_test_message( + "main", + capabilities.thermostatMode.thermostatMode.eco() + ) + } + }, + {} +) + +test.register_message_test( + "Handle tuya cluster message report (setThermostatMode, antifreezing)", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_simple_device.id, + tuya_utils.build_test_attr_report( + mock_simple_device, + "\x02", + tuya_utils.DP_TYPE_ENUM, + "\x05", + 0x01 + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_simple_device:generate_test_message( + "main", + capabilities.thermostatMode.thermostatMode.antifreezing() + ) + } + }, + {} +) + +test.run_registered_tests() diff --git a/drivers/Unofficial/tuya-zigbee/src/thermostat/init.lua b/drivers/Unofficial/tuya-zigbee/src/thermostat/init.lua new file mode 100644 index 0000000000..2179fb28a4 --- /dev/null +++ b/drivers/Unofficial/tuya-zigbee/src/thermostat/init.lua @@ -0,0 +1,187 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local defaults = require "st.zigbee.defaults" +local device_management = require "st.zigbee.device_management" +local clusters = require "st.zigbee.zcl.clusters" +local tuya_utils = require "tuya_utils" +local Basic = clusters.Basic +local packet_id = 0 + +local FINGERPRINTS = { + { mfr = "_TZE284_fziifcxj", model = "TS0601"} +} + +local function is_tuya_thermostat(opts, driver, device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end + +local function device_added(self, device) + device:emit_event(capabilities.thermostatMode.supportedThermostatModes({ + capabilities.thermostatMode.thermostatMode.antifreezing.NAME, + capabilities.thermostatMode.thermostatMode.auto.NAME, + capabilities.thermostatMode.thermostatMode.comfort.NAME, + capabilities.thermostatMode.thermostatMode.eco.NAME, + capabilities.thermostatMode.thermostatMode.off.NAME, + capabilities.thermostatMode.thermostatMode.on.NAME, + }, { visibility = { displayed = false } })) + device:emit_event(capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 15.0, unit = "C"})) + device:emit_event(capabilities.temperatureMeasurement.temperature({value = 20.0, unit = "C"})) + device:emit_event(capabilities.thermostatMode.thermostatMode.auto()) + device:emit_event(capabilities.battery.battery(100)) +end + +local function do_configure(driver, device) + -- configure ApplicationVersion to keep device online, tuya hub also uses this attribute + tuya_utils.send_magic_spell(device) + device:send(Basic.attributes.ApplicationVersion:configure_reporting(device, 30, 300, 1)) + device:send(device_management.build_bind_request( + device, + Basic.ID, + driver.environment_info.hub_zigbee_eui + )) +end + +local function increase_packet_id(pid) + return (pid + 1) % 65536 +end + +local function do_refresh(driver, device) + device.log.info("do_refresh called") +end + +local MODE_MAP = { + [capabilities.thermostatMode.thermostatMode.auto.NAME] = "\x00", + [capabilities.thermostatMode.thermostatMode.off.NAME] = "\x01", + [capabilities.thermostatMode.thermostatMode.on.NAME] = "\x02", + [capabilities.thermostatMode.thermostatMode.comfort.NAME] = "\x03", + [capabilities.thermostatMode.thermostatMode.eco.NAME] = "\x04", + [capabilities.thermostatMode.thermostatMode.antifreezing.NAME] = "\x05", +} + +local function set_thermostat_mode(driver, device, command) + local mode_value = MODE_MAP[command.args.mode] + if mode_value ~= nil then + tuya_utils.send_tuya_command(device, "\x02", tuya_utils.DP_TYPE_ENUM, mode_value, packet_id) + packet_id = increase_packet_id(packet_id) + end +end + +local function set_heating_setpoint(driver, device, command) + local value = command.args.setpoint + local setpoint_raw = math.floor(value * 10 + 0.5) + tuya_utils.send_tuya_command( + device, + "\x04", + tuya_utils.DP_TYPE_VALUE, + string.pack(">I4", setpoint_raw), + packet_id + ) + packet_id = increase_packet_id(packet_id) +end + +local function tuya_cluster_handler(driver, device, zb_rx) + local event + local raw = zb_rx.body.zcl_body.body_bytes + local dp = raw:byte(3) + local dp_type = raw:byte(4) + local dp_data_len = string.unpack(">I2", raw:sub(5, 6)) + local dp_data = raw:sub(7, 6 + dp_data_len) + + if dp == 0x04 then -- Target temperature + if dp_type == 0x02 and dp_data_len >= 4 then -- value + local target_temp_raw = string.unpack(">I4", dp_data:sub(1, 4)) + local target_temp = target_temp_raw / 10.0 + event = capabilities.thermostatHeatingSetpoint.heatingSetpoint({ + value = target_temp, + unit = "C" + }) + end + + elseif dp == 0x05 then -- Current temperature + if dp_type == 0x02 and dp_data_len >= 4 then -- value + local temp_raw = string.unpack(">I4", dp_data:sub(1, 4)) + local temp = temp_raw / 10.0 + event = capabilities.temperatureMeasurement.temperature({ + value = temp, + unit = "C" + }) + end + + elseif dp == 0x02 then -- Thermostat mode + if dp_type == 0x04 and dp_data_len >= 1 then -- enum + local mode = dp_data:byte(1) + if mode == 0x00 then -- auto + event = capabilities.thermostatMode.thermostatMode.auto() + elseif mode == 0x01 then -- off + event = capabilities.thermostatMode.thermostatMode.off() + elseif mode == 0x02 then -- on + event = capabilities.thermostatMode.thermostatMode.on() + elseif mode == 0x03 then -- comfort + event = capabilities.thermostatMode.thermostatMode.comfort() + elseif mode == 0x04 then -- eco + event = capabilities.thermostatMode.thermostatMode.eco() + elseif mode == 0x05 then -- antifreezing + event = capabilities.thermostatMode.thermostatMode.antifreezing() + end + end + + elseif dp == 0x06 then -- Battery level + if dp_type == 0x02 and dp_data_len >= 4 then -- value + local battery_level = string.unpack(">I4", dp_data:sub(1, 4)) + event = capabilities.battery.battery(battery_level) + end + end + + if event ~= nil then + device:emit_event(event) + end +end + +local tuya_thermostat_driver = { + NAME = "tuya thermostat", + supported_capabilities = { + capabilities.temperatureMeasurement, + capabilities.thermostatHeatingSetpoint, + capabilities.thermostatMode, + capabilities.battery, + capabilities.refresh + }, + zigbee_handlers = { + cluster = { + [tuya_utils.TUYA_PRIVATE_CLUSTER] = { + [tuya_utils.TUYA_PRIVATE_CMD_REPORT] = tuya_cluster_handler, + [tuya_utils.TUYA_PRIVATE_CMD_RESPONSE] = tuya_cluster_handler, + } + } + }, + capability_handlers = { + [capabilities.thermostatMode.ID] = { + [capabilities.thermostatMode.commands.setThermostatMode.NAME] = set_thermostat_mode, + }, + [capabilities.thermostatHeatingSetpoint.ID] = { + [capabilities.thermostatHeatingSetpoint.commands.setHeatingSetpoint.NAME] = set_heating_setpoint, + }, + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh, + }, + }, + lifecycle_handlers = { + added = device_added, + doConfigure = do_configure + }, + can_handle = is_tuya_thermostat +} + +defaults.register_for_default_handlers( + tuya_thermostat_driver, + tuya_thermostat_driver.supported_capabilities, + {} +) +return tuya_thermostat_driver From 6bae4060e1d9012cfecadfe26c1443d0a435a01d Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Fri, 3 Jul 2026 00:36:19 +0800 Subject: [PATCH 422/538] add firstled light Easy-Fit Driver (#3031) * add firstled light Easy-Fit Driver * add caps LevelStep ColorTemperatureStep --------- Co-authored-by: Chris Baumler --- .../zigbee-switch/fingerprints.yml | 5 + .../light-color-temp-time-restore.yml | 74 ++++++++++++ .../src/firstled-light/can_handle.lua | 13 +++ .../src/firstled-light/fingerprints.lua | 6 + .../zigbee-switch/src/firstled-light/init.lua | 63 +++++++++++ .../zigbee-switch/src/sub_drivers.lua | 3 +- .../src/test/test_firstled_light.lua | 106 ++++++++++++++++++ 7 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-switch/profiles/light-color-temp-time-restore.yml create mode 100644 drivers/SmartThings/zigbee-switch/src/firstled-light/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/firstled-light/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/firstled-light/init.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_firstled_light.lua diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index b44a13d3c1..1d67094618 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,11 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "ManDai Technology/DC2DC12MiV1" + deviceLabel: Easy-Fit Driver + manufacturer: ManDai Technology + model: DC2DC12MiV1 + deviceProfileName: light-color-temp-time-restore zigbeeGeneric: - id: "genericSwitch" deviceLabel: Zigbee Switch diff --git a/drivers/SmartThings/zigbee-switch/profiles/light-color-temp-time-restore.yml b/drivers/SmartThings/zigbee-switch/profiles/light-color-temp-time-restore.yml new file mode 100644 index 0000000000..45fd965a6c --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/light-color-temp-time-restore.yml @@ -0,0 +1,74 @@ +name: light-color-temp-time-restore +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + config: + values: + - key: "level.value" + range: [1, 100] + - id: statelessSwitchLevelStep + version: 1 + - id: colorTemperature + version: 1 + - id: statelessColorTemperatureStep + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light +preferences: + - title: "上电状态(power-on state/파워온 상태)" + name: powerOnMode + description: "上电状态(power-on state/파워온 상태)" + required: false + preferenceType: enumeration + definition: + options: + 0: "关闭(off/닫다)" + 1: "打开(on/열다)" + 2: "恢复记忆状态(restore/복원)" + default: 0 + - title: "驱动电流(drive current/구동 전류)" + name: ledDriveCurrent + description: "驱动电流(drive current/구동 전류)/50~333mA" + required: false + preferenceType: integer + definition: + minimum: 50 + maximum: 333 + default: 270 + - title: "亮度时间(dimmingTransitionTime/밝기 전환 시간)" + name: dimTransitionTime + description: "亮度过渡时间(dimming transition time/밝기 전환 시간)1000~10000ms" + required: false + preferenceType: integer + definition: + minimum: 1000 + maximum: 10000 + default: 1000 + - title: "色温时间(CCTTransitionTime/색온 전환 시간)" + name: colorTempTransitionTime + description: "色温过渡时间(CCT transition time/색온 전환 시간)1000~10000ms" + required: false + preferenceType: integer + definition: + minimum: 1000 + maximum: 10000 + default: 1000 + - title: "输出模式(output mode/출력 모드)" + name: outputMode + description: "输出模式(output mode/출력 모드)" + required: false + preferenceType: enumeration + definition: + options: + 0: "双路CW(dual CW/듀얼 CW)" + 1: "双路WC(dual WC/듀얼 WC)" + 2: "单路(single channel/단일 채널)" + default: 0 diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-light/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/firstled-light/can_handle.lua new file mode 100644 index 0000000000..6b02320ebf --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/firstled-light/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require "firstled-light.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("firstled-light") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-light/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-light/fingerprints.lua new file mode 100644 index 0000000000..ba8c2347b3 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/firstled-light/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "ManDai Technology", model = "DC2DC12MiV1" } +} diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-light/init.lua b/drivers/SmartThings/zigbee-switch/src/firstled-light/init.lua new file mode 100644 index 0000000000..ab81b0f0cf --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/firstled-light/init.lua @@ -0,0 +1,63 @@ +-- Copyright 2026 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" + +local PRIVATE_CLUSTER_ID = 0xFC00 + +local preference_map = { + ["outputMode"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0000, + data_type = data_types.Uint8 + }, + ["powerOnMode"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0001, + data_type = data_types.Uint8 + }, + ["ledDriveCurrent"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0002, + data_type = data_types.Uint16 + }, + ["dimTransitionTime"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0003, + data_type = data_types.Uint16 + }, + ["colorTempTransitionTime"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0004, + data_type = data_types.Uint16 + } +} + +local function device_info_changed(driver, device, event, args) + local preferences = device.preferences + local old_preferences = args.old_st_store.preferences + if preferences ~= nil then + for id, attr in pairs(preference_map) do + local old_value = old_preferences[id] + local value = preferences[id] + if value ~= nil and value ~= old_value then + value = tonumber(value) + device:send(cluster_base.write_attribute(device, + data_types.ClusterId(attr.cluster_id), + data_types.AttributeId(attr.attribute_id), + data_types.validate_or_build_type(value, attr.data_type, "payload"))) + end + end + end +end + +local firstled_light_handlers = { + NAME = "firstled-light handlers", + lifecycle_handlers = { + infoChanged = device_info_changed + }, + can_handle = require("firstled-light.can_handle") +} + +return firstled_light_handlers diff --git a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua index b29c746d71..60390d8074 100644 --- a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua @@ -37,5 +37,6 @@ return { lazy_load_if_possible("frient-IO"), lazy_load_if_possible("color_temp_range_handlers"), lazy_load_if_possible("stateless_handlers"), - lazy_load_if_possible("firstled-io") + lazy_load_if_possible("firstled-io"), + lazy_load_if_possible("firstled-light") } diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_firstled_light.lua b/drivers/SmartThings/zigbee-switch/src/test/test_firstled_light.lua new file mode 100644 index 0000000000..79b77ab12a --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_firstled_light.lua @@ -0,0 +1,106 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +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 PRIVATE_CLUSTER_ID = 0xFC00 + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("light-color-temp-time-restore.yml"), + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "ManDai Technology", + model = "DC2DC12MiV1", + server_clusters = { 0x0006, 0x0008, 0x0300 } + } + } + } +) + +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) + +-- ====================== Preferences ====================== +test.register_coroutine_test("infoChanged - outputMode 0", function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ preferences = { outputMode = "0" }})) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_attribute(mock_device, + data_types.ClusterId(PRIVATE_CLUSTER_ID), + data_types.AttributeId(0x0000), + data_types.validate_or_build_type(0, data_types.Uint8, "payload")) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - powerOnMode 0", function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ preferences = { powerOnMode = "0" }})) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_attribute(mock_device, + data_types.ClusterId(PRIVATE_CLUSTER_ID), + data_types.AttributeId(0x0001), + data_types.validate_or_build_type(0, data_types.Uint8, "payload")) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - ledDriveCurrent 100", function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ preferences = { ledDriveCurrent = "100" }})) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_attribute(mock_device, + data_types.ClusterId(PRIVATE_CLUSTER_ID), + data_types.AttributeId(0x0002), + data_types.validate_or_build_type(100, data_types.Uint16, "payload")) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - dimTransitionTime 2000", function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ preferences = { dimTransitionTime = "2000" }})) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_attribute(mock_device, + data_types.ClusterId(PRIVATE_CLUSTER_ID), + data_types.AttributeId(0x0003), + data_types.validate_or_build_type(2000, data_types.Uint16, "payload")) + }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test("infoChanged - colorTempTransitionTime 2000", function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ preferences = { colorTempTransitionTime = "2000" }})) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_attribute(mock_device, + data_types.ClusterId(PRIVATE_CLUSTER_ID), + data_types.AttributeId(0x0004), + data_types.validate_or_build_type(2000, data_types.Uint16, "payload")) + }) + end, + { + min_api_version = 19 + } +) + +test.run_registered_tests() From b157ea084a76ae263b713325fed4ec99853d9696 Mon Sep 17 00:00:00 2001 From: Woofser Date: Thu, 2 Jul 2026 13:54:30 -0500 Subject: [PATCH 423/538] Add Sombra Shades window shade sub-driver (#2991) * add Sombra Shades driver * Add new fingerprint for Sombra Shades model * Update model identifier for Sombra Shades * Correct Sombra Shades window shade sub-driver * Use position-based motion status and add power source support * Restore WM25/L-Z and SS25/L-Z fingerprints; keep change additive * Register settle timer in position-settle tests so mock time advance fires it --- .../zigbee-window-treatment/fingerprints.yml | 5 + .../src/sombra/can_handle.lua | 14 + .../src/sombra/fingerprints.lua | 8 + .../src/sombra/init.lua | 175 +++++++ .../src/sub_drivers.lua | 1 + .../test_zigbee_window_treatment_sombra.lua | 434 ++++++++++++++++++ 6 files changed, 637 insertions(+) create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/sombra/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/sombra/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/sombra/init.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_sombra.lua diff --git a/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml b/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml index b2e918746b..021d59d2d4 100644 --- a/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml +++ b/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml @@ -148,6 +148,11 @@ zigbeeManufacturer: manufacturer: Sombra Shades model: SS25/L-Z deviceProfileName: window-treatment-battery + - id: "Sombra Shades/SOMBRA/Z-M" + deviceLabel: Sombra Automated Shades + manufacturer: Sombra Shades + model: SOMBRA/Z-M + deviceProfileName: window-treatment-powerSource zigbeeGeneric: - id: "genericShade" diff --git a/drivers/SmartThings/zigbee-window-treatment/src/sombra/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/sombra/can_handle.lua new file mode 100644 index 0000000000..e407ea7198 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/sombra/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_window_shade = function(opts, driver, device) + local FINGERPRINTS = require("sombra.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("sombra") + end + end + return false +end + +return is_zigbee_window_shade diff --git a/drivers/SmartThings/zigbee-window-treatment/src/sombra/fingerprints.lua b/drivers/SmartThings/zigbee-window-treatment/src/sombra/fingerprints.lua new file mode 100644 index 0000000000..bac50c21bb --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/sombra/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { + { mfr = "Sombra Shades", model = "SOMBRA/Z-M" } +} + +return ZIGBEE_WINDOW_SHADE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-window-treatment/src/sombra/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/sombra/init.lua new file mode 100644 index 0000000000..634d7372e3 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/sombra/init.lua @@ -0,0 +1,175 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +-- require st provided libraries +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local window_shade_utils = require "window_shade_utils" +local device_management = require "st.zigbee.device_management" +local utils = require "st.utils" + +local Basic = clusters.Basic +local WindowCovering = clusters.WindowCovering +local PowerConfiguration = clusters.PowerConfiguration + +-- Sombra motors report position in the shade-industry convention +-- (0% = open, 100% = closed), which is passed straight through to +-- windowShadeLevel.shadeLevel with no inversion. +-- +-- Opening/Closing status is inferred from the direction the position is +-- changing, so it works on every Sombra motor variant -- battery and DC -- +-- regardless of any manufacturer-specific motion cluster. When position +-- reports stop arriving, a short settle timer emits the resting state. +local SETTLE_DELAY = 3 +local SETTLE_TIMER = "shade_settle_timer" + +----------------------------------------------------------------- +-- local functions +----------------------------------------------------------------- + +-- emit the resting windowShade state for a level (0 = open, 100 = closed) +local function emit_resting_state(device, level) + if level <= 0 then + device:emit_event(capabilities.windowShade.windowShade.open()) + elseif level >= 100 then + device:emit_event(capabilities.windowShade.windowShade.closed()) + else + device:emit_event(capabilities.windowShade.windowShade.partially_open()) + end +end + +-- cancel any pending settle timer +local function cancel_settle_timer(device) + local timer = device:get_field(SETTLE_TIMER) + if timer then + device.thread:cancel_timer(timer) + device:set_field(SETTLE_TIMER, nil) + end +end + +-- (re)start the settle timer; when it fires without a newer position report +-- the shade has stopped moving, so emit the resting state +local function schedule_settle(device) + cancel_settle_timer(device) + local timer = device.thread:call_with_delay(SETTLE_DELAY, function() + device:set_field(SETTLE_TIMER, nil) + local level = device:get_latest_state("main", capabilities.windowShadeLevel.ID, + capabilities.windowShadeLevel.shadeLevel.NAME) or 0 + emit_resting_state(device, level) + end) + device:set_field(SETTLE_TIMER, timer) +end + +-- this is do_refresh +local do_refresh = function(self, device) + device:send(WindowCovering.attributes.CurrentPositionLiftPercentage:read(device)) + device:send(PowerConfiguration.attributes.BatteryPercentageRemaining:read(device)) + device:send(Basic.attributes.PowerSource:read(device)) +end + +-- this is window_shade_level_cmd +local function window_shade_level_cmd(driver, device, command) + local go_to_level = command.args.shadeLevel + -- send levels without inverting as: 0% lift (open) to 100% lift (closed) + device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, go_to_level)) +end + +-- this is window_shade_preset_cmd +local function window_shade_preset_cmd(driver, device, command) + local level = window_shade_utils.get_preset_level(device, command.component) + -- send levels without inverting as: 0% lift (open) to 100% lift (closed) + device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, level)) +end + +-- this is device_added +local function device_added(self, device) + device:emit_event(capabilities.windowShade.supportedWindowShadeCommands({ "open", "close", "pause" }, {visibility = {displayed = false}})) + device.thread:call_with_delay(3, function(d) + do_refresh(self, device) + end) +end + +-- this is current_position_attr_handler +-- position is 0 = open, 100 = closed; direction is inferred from the change +local function current_position_attr_handler(driver, device, value, zb_rx) + local level = value.value + local previous = device:get_latest_state("main", capabilities.windowShadeLevel.ID, + capabilities.windowShadeLevel.shadeLevel.NAME) or 0 + local windowShade = capabilities.windowShade.windowShade + + device:emit_event(capabilities.windowShadeLevel.shadeLevel(level)) + + if level <= 0 then + cancel_settle_timer(device) + device:emit_event(windowShade.open()) + elseif level >= 100 then + cancel_settle_timer(device) + device:emit_event(windowShade.closed()) + else + if level < previous then + device:emit_event(windowShade.opening()) + elseif level > previous then + device:emit_event(windowShade.closing()) + end + schedule_settle(device) + end +end + +-- this is do_configure +local function do_configure(self, device) + -- configure elements + device:send(device_management.build_bind_request(device, WindowCovering.ID, self.environment_info.hub_zigbee_eui)) + 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:send(device_management.build_bind_request(device, Basic.ID, self.environment_info.hub_zigbee_eui)) + device:send(Basic.attributes.PowerSource:configure_reporting(device, 1, 3600)) + + -- read elements + device.thread:call_with_delay(3, function(d) + do_refresh(self, device) + end) +end + +-- this is battery_perc_attr_handler +local function battery_perc_attr_handler(driver, device, value, zb_rx) + local converted_value = value.value / 2 + converted_value = utils.round(converted_value) + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, + capabilities.battery.battery(utils.clamp_value(converted_value, 0, 100))) +end + +-- create the handler object +local sombra_roller_shade_handler = { + NAME = "sombra_roller_shade_handler", + lifecycle_handlers = { + added = device_added, + doConfigure = do_configure + }, + capability_handlers = { + [capabilities.windowShadeLevel.ID] = { + [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = window_shade_level_cmd + }, + [capabilities.windowShadePreset.ID] = { + [capabilities.windowShadePreset.commands.presetPosition.NAME] = window_shade_preset_cmd + }, + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh, + } + }, + zigbee_handlers = { + attr = { + [WindowCovering.ID] = { + [WindowCovering.attributes.CurrentPositionLiftPercentage.ID] = current_position_attr_handler, + }, + [PowerConfiguration.ID] = { + [PowerConfiguration.attributes.BatteryPercentageRemaining.ID] = battery_perc_attr_handler, + } + } + }, + can_handle = require("sombra.can_handle"), +} + +-- return the handler +return sombra_roller_shade_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/sub_drivers.lua b/drivers/SmartThings/zigbee-window-treatment/src/sub_drivers.lua index 959c8d8c22..3353c87fd4 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/sub_drivers.lua @@ -7,6 +7,7 @@ local sub_drivers = { lazy_load_if_possible("aqara"), lazy_load_if_possible("feibit"), lazy_load_if_possible("somfy"), + lazy_load_if_possible("sombra"), lazy_load_if_possible("invert-lift-percentage"), lazy_load_if_possible("rooms-beautiful"), lazy_load_if_possible("axis"), diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_sombra.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_sombra.lua new file mode 100644 index 0000000000..35ab9eab24 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_sombra.lua @@ -0,0 +1,434 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +-- 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 Basic = clusters.Basic +local WindowCovering = clusters.WindowCovering +local PowerConfiguration = clusters.PowerConfiguration + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("window-treatment-powerSource.yml"), + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Sombra Shades", + model = "SOMBRA/Z-M", + server_clusters = {0x0000, 0x0001, 0x0102} + } + } + } +) + +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.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}})) + ) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Window Shade state open", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 0) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Window Shade state closed", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 100) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "A falling position reports opening, then settles to partially open", + function() + test.socket.capability:__set_channel_ordering("relaxed") + -- establish a known starting position (fully closed) + test.socket.zigbee:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 100) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + ) + test.wait_for_events() + -- a lower position than before => opening + test.socket.zigbee:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 60) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(60)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + test.wait_for_events() + -- no further reports for the settle delay => partially open + test.mock_time.advance_time(3) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "A rising position reports closing, then settles to partially open", + function() + test.socket.capability:__set_channel_ordering("relaxed") + -- establish a known starting position (fully open) + test.socket.zigbee:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 0) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) + ) + test.wait_for_events() + -- a higher position than before => closing + test.socket.zigbee:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 40) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(40)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) + ) + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + test.wait_for_events() + -- no further reports for the settle delay => partially open + test.mock_time.advance_time(3) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "WindowShade open cmd test case", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { capability = "windowShade", component = "main", command = "open", args = {} } + } + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + WindowCovering.server.commands.UpOrOpen(mock_device) + }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "WindowShade close cmd test case", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { capability = "windowShade", component = "main", command = "close", args = {} } + } + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + WindowCovering.server.commands.DownOrClose(mock_device) + }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "WindowShade pause cmd test case", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { capability = "windowShade", component = "main", command = "pause", args = {} } + } + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + WindowCovering.server.commands.Stop(mock_device) + }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Set shade level command", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 45 } } + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + WindowCovering.server.commands.GoToLiftPercentage(mock_device, 45) + } + ) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Preset position command", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} } + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + WindowCovering.server.commands.GoToLiftPercentage(mock_device, 50) + } + ) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Battery Percentage Remaining test cases", + function() + local battery_test_map = { + [200] = 100, + [100] = 50, + [0] = 0 + } + for bat_perc_rem, batt_perc_out in pairs(battery_test_map) do + test.socket.zigbee:__queue_receive({ mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device, bat_perc_rem) }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(batt_perc_out)) ) + test.wait_for_events() + end + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Power Source test cases", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + + test.socket.zigbee:__queue_receive({ mock_device.id, + Basic.attributes.PowerSource:build_test_attr_report(mock_device, 3) }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.powerSource.powerSource.battery())) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, + Basic.attributes.PowerSource:build_test_attr_report(mock_device, 4) }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.powerSource.powerSource.dc())) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, + Basic.attributes.PowerSource:build_test_attr_report(mock_device, 0) }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.powerSource.powerSource.unknown())) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Refresh should generate expected messages", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }) + test.socket.zigbee:__expect_send({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Basic.attributes.PowerSource:read(mock_device) + }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "doConfigure should generate expected messages", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.zigbee:__set_channel_ordering("relaxed") + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, WindowCovering.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:configure_reporting(mock_device, 1, 3600, 1) + }) + 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.BatteryPercentageRemaining:configure_reporting(mock_device, 1, 3600, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Basic.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Basic.attributes.PowerSource:configure_reporting(mock_device, 1, 3600) + }) + + -- read values after delay + test.mock_time.advance_time(3) + test.socket.zigbee:__expect_send({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Basic.attributes.PowerSource:read(mock_device) + }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "added should generate expected messages", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added"}) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.windowShade.supportedWindowShadeCommands({ "open", "close", "pause" },{ visibility = { displayed = false }})) + ) + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + test.wait_for_events() + test.socket.zigbee:__set_channel_ordering("relaxed") + + -- read values after delay + test.mock_time.advance_time(3) + test.socket.zigbee:__expect_send({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Basic.attributes.PowerSource:read(mock_device) + }) + end, + { + min_api_version = 17 + } +) + +test.run_registered_tests() From 18f37510551fa787e540f1cf27d035d2d45b0b98 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 2 Jul 2026 13:56:56 -0500 Subject: [PATCH 424/538] WWSTCERT-12453 Aqara Smart Lock J200 (#3074) --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 +++++ .../matter-lock/src/new-matter-lock/fingerprints.lua | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 796cefd590..01e662c421 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -25,6 +25,11 @@ matterManufacturer: vendorId: 0x115F productId: 0x2804 deviceProfileName: lock + - id: "4447/10253" + deviceLabel: Aqara Smart Lock J200 + vendorId: 0x115F + productId: 0x280D + deviceProfileName: lock-modular #Eufy - id: "5427/1" deviceLabel: eufy Smart Lock E31 diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index 375b8c6189..d3127d4b8a 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -11,6 +11,7 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x115f, 0x280e}, -- AQARA Smart Gate Lock U500 {0x115f, 0x280f}, -- AQARA Smart Rim Lock U500 {0x115f, 0x2810}, -- AQARA Smart Glass Door Lock U500 + {0x115F, 0x280D}, -- Aqara Smart Lock J200 {0x147F, 0x0001}, -- U-tec {0x147F, 0x0007}, -- ULTRALOQ Bolt Pro Smart Matter Door Lock {0x147F, 0x0008}, -- Ultraloq, Bolt Smart Matter Door Lock From 4a65dd403106269ae8286f0e1b80cc3e3c98cd7b Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 2 Jul 2026 14:04:08 -0500 Subject: [PATCH 425/538] WWSTCERT-12567 Aqara Smart Lock U500 Rim (#3086) --- drivers/SmartThings/matter-lock/fingerprints.yml | 14 ++++++++++++++ .../src/new-matter-lock/fingerprints.lua | 5 +++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 01e662c421..575da17382 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -25,6 +25,20 @@ matterManufacturer: vendorId: 0x115F productId: 0x2804 deviceProfileName: lock + - id: "4447/10255" + deviceLabel: Aqara Smart Lock U500 Rim + vendorId: 0x115F + productId: 0x280F + deviceProfileName: lock-modular + - id: "4447/10256" + deviceLabel: Aqara Smart Lock U500 Glass Door + vendorId: 0x115F + productId: 0x2810 + deviceProfileName: lock-modular + - id: "4447/10314" + deviceLabel: Aqara Smart Lock U600 + vendorId: 0x115F + productId: 0x284A - id: "4447/10253" deviceLabel: Aqara Smart Lock J200 vendorId: 0x115F diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index d3127d4b8a..4b15b23b2b 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -9,8 +9,9 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x115f, 0x286A}, -- AQARA, U200 US {0x115f, 0x2805}, -- Aqara Smart Lock J200 Set {0x115f, 0x280e}, -- AQARA Smart Gate Lock U500 - {0x115f, 0x280f}, -- AQARA Smart Rim Lock U500 - {0x115f, 0x2810}, -- AQARA Smart Glass Door Lock U500 + {0x115F, 0x280F}, -- Aqara Smart Lock U500 Rim + {0x115F, 0x2810}, -- Aqara Smart Lock U500 Glass Door + {0x115F, 0x284A}, -- Aqara Smart Lock U600 {0x115F, 0x280D}, -- Aqara Smart Lock J200 {0x147F, 0x0001}, -- U-tec {0x147F, 0x0007}, -- ULTRALOQ Bolt Pro Smart Matter Door Lock From 59aea94ac9af614d186c5d3d80a0069b71cb3616 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Fri, 3 Jul 2026 03:13:21 +0800 Subject: [PATCH 426/538] add wallhero_thermostat_ACL-403STC1 (#2969) --- .../zigbee-thermostat/fingerprints.yml | 5 + .../thermostat-thirty-buttons-wallhero.yml | 249 ++++ .../zigbee-thermostat/src/init.lua | 4 +- .../zigbee-thermostat/src/sub_drivers.lua | 1 + .../test_zigbee_thermostat_wallhero_3in1.lua | 1240 +++++++++++++++++ .../src/wallhero/can_handle.lua | 13 + .../src/wallhero/fingerprints.lua | 6 + .../zigbee-thermostat/src/wallhero/init.lua | 232 +++ tools/localizations/cn.csv | 1 + 9 files changed, 1750 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-thermostat/profiles/thermostat-thirty-buttons-wallhero.yml create mode 100644 drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat_wallhero_3in1.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/wallhero/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/wallhero/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/wallhero/init.lua diff --git a/drivers/SmartThings/zigbee-thermostat/fingerprints.yml b/drivers/SmartThings/zigbee-thermostat/fingerprints.yml index d88b3ff89a..efa89be552 100644 --- a/drivers/SmartThings/zigbee-thermostat/fingerprints.yml +++ b/drivers/SmartThings/zigbee-thermostat/fingerprints.yml @@ -134,6 +134,11 @@ zigbeeManufacturer: manufacturer: LUMI model: lumi.airrtc.agl001 deviceProfileName: thermostat-aqara + - id: "WALL HERO/ACL-403STC1" + deviceLabel: WALLHERO Thermostat ACL-403STC1 + manufacturer: WALL HERO + model: ACL-403STC1 + deviceProfileName: thermostat-thirty-buttons-wallhero zigbeeGeneric: - id: "genericThermostat" deviceLabel: Zigbee Thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-thirty-buttons-wallhero.yml b/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-thirty-buttons-wallhero.yml new file mode 100644 index 0000000000..5a7bff92a8 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-thirty-buttons-wallhero.yml @@ -0,0 +1,249 @@ +name: thermostat-thirty-buttons-wallhero +components: + - id: main + label: 空调(AC) + capabilities: + - id: switch + version: 1 + - id: temperatureMeasurement + version: 1 + - id: temperatureSetpoint + version: 1 + - id: thermostatMode + version: 1 + - id: fanMode + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController + - id: fan + label: 新风(Fresh Air) + capabilities: + - id: switch + version: 1 + - id: fanMode + version: 1 + categories: + - name: Fan + - id: heat + label: 地暖(Floor Heating) + capabilities: + - id: switch + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + categories: + - name: Thermostat + - id: button1 + label: 回家(Home) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + label: 离家(Leave Home) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button3 + label: 晨起(Morning) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button4 + label: 睡眠(Sleep) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button5 + label: 阅读(Reading) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button6 + label: 观影(Movie) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button7 + label: 全开(All On) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button8 + label: 全关(All Off) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button9 + label: 空调温度调高(AC Temp Up) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button10 + label: 空调温度降低(AC Temp Down) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button11 + label: 开窗帘(Open Curtain) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button12 + label: 关窗帘(Close Curtain) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button13 + label: 空调开(AC On) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button14 + label: 空调关(AC Off) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button15 + label: 无风开(No Wind On) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button16 + label: 无风关(No Wind Off) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button17 + label: 制冷(Cooling) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button18 + label: 制热(Heating) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button19 + label: 新风开(Fresh Air On) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button20 + label: 新风关(Fresh Air Off) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button21 + label: 开电视(Turn On TV) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button22 + label: 关电视(Turn Off TV) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button23 + label: 开音乐(Play Music) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button24 + label: 关音乐(Stop Music) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button25 + label: 开纱帘(Open Screen Curtain) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button26 + label: 关纱帘(Close Screen Curtain) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button27 + label: 洗衣服(Wash Clothes) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button28 + label: 衣服护理(Clothes Care) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button29 + label: 用餐(Dining) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button30 + label: 开大门(Open Main Door) + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController diff --git a/drivers/SmartThings/zigbee-thermostat/src/init.lua b/drivers/SmartThings/zigbee-thermostat/src/init.lua index a72b3c9107..cfbbd76a0c 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/init.lua @@ -41,7 +41,9 @@ local THERMOSTAT_MODE_MAP = { [ThermostatSystemMode.AUTO] = ThermostatMode.thermostatMode.auto, [ThermostatSystemMode.COOL] = ThermostatMode.thermostatMode.cool, [ThermostatSystemMode.HEAT] = ThermostatMode.thermostatMode.heat, - [ThermostatSystemMode.EMERGENCY_HEATING] = ThermostatMode.thermostatMode.emergency_heat + [ThermostatSystemMode.EMERGENCY_HEATING] = ThermostatMode.thermostatMode.emergency_heat, + [ThermostatSystemMode.DRY] = ThermostatMode.thermostatMode.dryair, + [ThermostatSystemMode.FAN_ONLY] = ThermostatMode.thermostatMode.fanonly } local FAN_MODE_MAP = { diff --git a/drivers/SmartThings/zigbee-thermostat/src/sub_drivers.lua b/drivers/SmartThings/zigbee-thermostat/src/sub_drivers.lua index 7f62589c24..3b535c3cf0 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/sub_drivers.lua @@ -15,5 +15,6 @@ local sub_drivers = { lazy_load_if_possible("vimar"), lazy_load_if_possible("resideo_korea"), lazy_load_if_possible("aqara"), + lazy_load_if_possible("wallhero"), } return sub_drivers diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat_wallhero_3in1.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat_wallhero_3in1.lua new file mode 100644 index 0000000000..a93a3a36ee --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat_wallhero_3in1.lua @@ -0,0 +1,1240 @@ +-- 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. + +-- Mock out globals +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" + +local Scenes = clusters.Scenes +local OnOff = clusters.OnOff +local FanControl = clusters.FanControl +local Thermostat = clusters.Thermostat +local ThermostatMode = capabilities.thermostatMode +local FanMode = capabilities.fanMode +local button_attr = capabilities.button.button + +local SUPPORTED_FAN_MODES = { + { "auto", "high", "medium", "low"}, +} + +local mock_device = test.mock_device.build_test_zigbee_device( + { profile = t_utils.get_profile_definition("thermostat-thirty-buttons-wallhero.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "WALL HERO", + model = "ACL-403STC1", + server_clusters = { 0x0005,0x0006,0x0201,0x0202, 0x0203 } + } + } + } +) + +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( + "added lifecycle event", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + ThermostatMode.supportedThermostatModes({"cool", "dryair", "fanonly", "heat"}, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + FanMode.supportedFanModes( SUPPORTED_FAN_MODES[1] , { visibility = { displayed = false }}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "fan", + FanMode.supportedFanModes( SUPPORTED_FAN_MODES[1] , { visibility = { displayed = false }}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.temperatureSetpoint.temperatureSetpointRange({ value = { minimum = 16.00, maximum = 32.00 }, unit = "C" }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "heat", + capabilities.thermostatHeatingSetpoint.heatingSetpointRange({ value = { minimum = 16.00, maximum = 32.00 }, unit = "C" }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.switch.switch.off() + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "heat", + capabilities.switch.switch.off() + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "fan", + capabilities.switch.switch.off() + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + ThermostatMode.thermostatMode.cool() + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + FanMode.fanMode.auto() + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "fan", + FanMode.fanMode.auto() + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.temperatureSetpoint.temperatureSetpoint({value = 26, unit = "C"}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "heat", + capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 26, unit = "C"}) + ) + ) + + for _, component in pairs(mock_device.profile.components) do + if component.id ~= "main" and component.id ~= "heat" and component.id ~= "fan" then + test.socket.capability:__expect_send( + mock_device:generate_test_message( + component.id, + capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + component.id, + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }) + ) + ) + end + end + + test.socket.zigbee:__expect_send( + { + mock_device.id, + OnOff.attributes.OnOff:read(mock_device):to_endpoint(0x01) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + Thermostat.attributes.LocalTemperature:read(mock_device):to_endpoint(0x01) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + Thermostat.attributes.OccupiedCoolingSetpoint:read(mock_device):to_endpoint(0x01) + } + ) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + Thermostat.attributes.SystemMode:read(mock_device):to_endpoint(0x01) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + FanControl.attributes.FanMode:read(mock_device):to_endpoint(0x01) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + OnOff.attributes.OnOff:read(mock_device):to_endpoint(0x02) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + FanControl.attributes.FanMode:read(mock_device):to_endpoint(0x02) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + OnOff.attributes.OnOff:read(mock_device):to_endpoint(0x03) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:read(mock_device):to_endpoint(0x03) + } + ) + test.socket:set_time_advance_per_select(0.1) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + + +test.register_message_test( + "Reported on status should be handled: on ep 1", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, + true):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Reported off status should be handled: off ep 1", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, + false):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.switch.switch.off()) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Temperature reports using the temperatureMeasurement should be handled : ep1", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.LocalTemperature:build_test_attr_report(mock_device, + 2500):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" })) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Cooling setpoint reports temperatureSetpoint are handled : ep1", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.OccupiedCoolingSetpoint:build_test_attr_report(mock_device, + 2500):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.temperatureSetpoint.temperatureSetpoint({ value = 25.0, unit = "C" })) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Thermostat running mode reports 3 cool are handled : ep1", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.ThermostatRunningMode:build_test_attr_report(mock_device, + 3):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode("cool")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Thermostat running mode reports 4 heat are handled : ep1", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.ThermostatRunningMode:build_test_attr_report(mock_device, + 4):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode("heat")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Thermostat running mode reports 8 dryair are handled : ep1", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.ThermostatRunningMode:build_test_attr_report(mock_device, + 8):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode("dryair")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Thermostat running mode reports 1 auto are handled : ep1", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.ThermostatRunningMode:build_test_attr_report(mock_device, + 1):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode("auto")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "FanControl fan mode reports 1 low are handled : ep1", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, FanControl.attributes.FanMode:build_test_attr_report(mock_device, + 1):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.fanMode("low")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "FanControl fan mode reports 2 medium are handled : ep1", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, FanControl.attributes.FanMode:build_test_attr_report(mock_device, + 2):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.fanMode("medium")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "FanControl fan mode reports 3 high are handled : ep1", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, FanControl.attributes.FanMode:build_test_attr_report(mock_device, + 3):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.fanMode("high")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "FanControl fan mode reports 5 auto are handled : ep1", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, FanControl.attributes.FanMode:build_test_attr_report(mock_device, + 5):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.fanMode("auto")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Reported on status should be handled: on ep 3", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, + true):from_endpoint(0x03) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("heat", capabilities.switch.switch.on()) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Reported off status should be handled: off ep 3", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, + false):from_endpoint(0x03) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("heat", capabilities.switch.switch.off()) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Heating setpoint reports heatingSetpoint are handled : ep3", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:build_test_attr_report(mock_device, + 2500):from_endpoint(0x03) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("heat", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 25.0, unit = "C" })) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Reported on status should be handled: on ep 2", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, + true):from_endpoint(0x02) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("fan", capabilities.switch.switch.on()) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Reported off status should be handled: off ep 2", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, + false):from_endpoint(0x02) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("fan", capabilities.switch.switch.off()) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "FanControl fan mode reports are handled : ep2", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, FanControl.attributes.FanMode:build_test_attr_report(mock_device, + 1):from_endpoint(0x02) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("fan", capabilities.fanMode.fanMode("low")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "FanControl fan mode reports are handled : ep2", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, FanControl.attributes.FanMode:build_test_attr_report(mock_device, + 2):from_endpoint(0x02) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("fan", capabilities.fanMode.fanMode("medium")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "FanControl fan mode reports are handled : ep2", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, FanControl.attributes.FanMode:build_test_attr_report(mock_device, + 3):from_endpoint(0x02) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("fan", capabilities.fanMode.fanMode("high")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "FanControl fan mode reports 5 auto are handled : ep2", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, FanControl.attributes.FanMode:build_test_attr_report(mock_device, + 5):from_endpoint(0x02) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("fan", capabilities.fanMode.fanMode("auto")) + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability on command switch on should be handled : ep1", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "switch", component = "main", command = "on", args = { } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x01) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability off command switch off should be handled : ep1", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "switch", component = "main", command = "off", args = { } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x01) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability on command switch on should be handled : ep3", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "switch", component = "heat", command = "on", args = { } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x03) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability off command switch off should be handled : ep3", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "switch", component = "heat", command = "off", args = { } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x03) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability on command switch on should be handled : ep2", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "switch", component = "fan", command = "on", args = { } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, OnOff.server.commands.On(mock_device):to_endpoint(0x02) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability off command switch off should be handled : ep2", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "switch", component = "fan", command = "off", args = { } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x02) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability temperatureSetpoint command setpoint 27 ep1", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "temperatureSetpoint", component = "main", command = "setTemperatureSetpoint", args = { 27 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, 2700):to_endpoint(0x01) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability thermostat command cool ep1", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "thermostatMode", component = "main", command = "setThermostatMode", args = {"cool" } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, Thermostat.attributes.SystemMode:write(mock_device, 3):to_endpoint(0x01) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability thermostat command dryair ep1", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "thermostatMode", component = "main", command = "setThermostatMode", args = {"dryair" } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, Thermostat.attributes.SystemMode:write(mock_device, 8):to_endpoint(0x01) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability thermostat command fanonly ep1", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "thermostatMode", component = "main", command = "setThermostatMode", args = {"fanonly" } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, Thermostat.attributes.SystemMode:write(mock_device, 7):to_endpoint(0x01) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability thermostat command heat ep1", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "thermostatMode", component = "main", command = "setThermostatMode", args = { "heat"} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, Thermostat.attributes.SystemMode:write(mock_device, 4):to_endpoint(0x01) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability fanMode command auto ep1", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "fanMode", component = "main", command = "setFanMode", args = {"auto"} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, FanControl.attributes.FanMode:write(mock_device, 5):to_endpoint(0x01) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability fanMode command low ep1", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "fanMode", component = "main", command = "setFanMode", args = {"low"} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, FanControl.attributes.FanMode:write(mock_device, 1):to_endpoint(0x01) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability fanMode command medium ep1", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "fanMode", component = "main", command = "setFanMode", args = {"medium"} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, FanControl.attributes.FanMode:write(mock_device, 2):to_endpoint(0x01) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability fanMode command high ep1", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "fanMode", component = "main", command = "setFanMode" , args = {"high"} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, FanControl.attributes.FanMode:write(mock_device, 3):to_endpoint(0x01) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability thermostatHeatingSetpoint command setHeatingSetpoint 27 ep3", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "thermostatHeatingSetpoint", component = "heat", command = "setHeatingSetpoint", args = { 27 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 2700):to_endpoint(0x03) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability fanMode command auto ep2", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "fanMode", component = "fan", command = "setFanMode", args = {"auto"} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, FanControl.attributes.FanMode:write(mock_device, 5):to_endpoint(0x02) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability fanMode command low ep2", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "fanMode", component = "fan", command = "setFanMode", args = {"low"} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, FanControl.attributes.FanMode:write(mock_device, 1):to_endpoint(0x02) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability fanMode command medium ep2", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "fanMode", component = "fan", command = "setFanMode", args = {"medium"} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, FanControl.attributes.FanMode:write(mock_device, 2):to_endpoint(0x02) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Capability fanMode command high ep2", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "fanMode", component = "fan", command = "setFanMode" , args = {"high"} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, FanControl.attributes.FanMode:write(mock_device, 3):to_endpoint(0x02) } + } + }, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "RecallScene command should be handled", + function() + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 4) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button1", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 5) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button2", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 6) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button3", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 7) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button4", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 8) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button5", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 9) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button6", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 10) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button7", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 11) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button8", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 12) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button9", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 13) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button10", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 14) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button11", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 15) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button12", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 16) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button13", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 17) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button14", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 18) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button15", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 19) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button16", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 20) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button17", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 21) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button18", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 22) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button19", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 23) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button20", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 24) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button21", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 25) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button22", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 26) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button23", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 27) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button24", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 28) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button25", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 29) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button26", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 30) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button27", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 31) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button28", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 32) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button29", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 33) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button30", button_attr.pushed({ state_change = true })) + ) + + test.wait_for_events() + test.socket.zigbee:__queue_receive({ mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, Scenes.ID, Scenes.server.commands.RecallScene.ID, 0x0000, "\x05\x00\x00\x00\x05\x00", 34) }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/wallhero/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/wallhero/can_handle.lua new file mode 100644 index 0000000000..476deedf2b --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/wallhero/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 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-thermostat/src/wallhero/fingerprints.lua b/drivers/SmartThings/zigbee-thermostat/src/wallhero/fingerprints.lua new file mode 100644 index 0000000000..d1a79015f1 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/wallhero/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "WALL HERO", model = "ACL-403STC1" } +} diff --git a/drivers/SmartThings/zigbee-thermostat/src/wallhero/init.lua b/drivers/SmartThings/zigbee-thermostat/src/wallhero/init.lua new file mode 100644 index 0000000000..807f2e564b --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/wallhero/init.lua @@ -0,0 +1,232 @@ +-- 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 capabilities = require "st.capabilities" +local log = require "log" +local socket = require "cosock.socket" +local zcl_clusters = require "st.zigbee.zcl.clusters" + +local Scenes = zcl_clusters.Scenes +local OnOff = zcl_clusters.OnOff +local FanControl = zcl_clusters.FanControl +local Thermostat = zcl_clusters.Thermostat +local ThermostatMode = capabilities.thermostatMode +local FanMode = capabilities.fanMode + +local function scenes_cluster_handler(driver, device, zb_rx) + local additional_fields = { + state_change = true + } + --Endpoint 1 to 3 are respectively the air conditioner, fresh air, and floor heating. Endpoint 4 to Endpoint 33 are buttons. + local ep = zb_rx.address_header.src_endpoint.value - 3 + local button_name = "button" .. ep + local event = capabilities.button.button.pushed(additional_fields) + local comp = device.profile.components[button_name] + if comp ~= nil then + device:emit_component_event(comp, event) + else + log.warn("Attempted to emit button event for unknown button: " .. button_name) + 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 and attr.on() or attr.off()) +end + +local function on_handler(driver, device, command) + device:send_to_component(command.component, OnOff.server.commands.On(device)) +end + +local function off_handler(driver, device, command) + device:send_to_component(command.component, OnOff.server.commands.Off(device)) +end + +local function caps_temperatureSetpoint_handler(driver, device, command) + local temperature = tonumber(command.args.setpoint)*100 + device:send_to_component(command.component, Thermostat.attributes.OccupiedCoolingSetpoint:write(device,temperature)) +end + +local SUPPORTED_FAN_MODES = { + { "auto", "high", "medium", "low"}, +} + +local FAN_MODE_TO_ZIGBEE = { + ["auto"] = 0x05, + ["low"] = 0x01, + ["medium"] = 0x02, + ["high"] = 0x03 +} + +local ZIGBEE_TO_FAN_MODES = { + [1] = "low" , + [2] = "medium" , + [3] = "high" , + [5] = "auto" +} + +local function fan_mode_attr_handler(driver, device, value, zb_rx) + local ep = zb_rx.address_header.src_endpoint.value + local str = ZIGBEE_TO_FAN_MODES[value.value] + if ep == 1 then + device:emit_component_event(device.profile.components.main,FanMode.fanMode({ value = str })) + elseif ep == 2 then + device:emit_component_event(device.profile.components.fan,FanMode.fanMode({ value = str })) + end +end + +local function setFanMode_handler(driver, device, command) + local value = FAN_MODE_TO_ZIGBEE[command.args.fanMode] + device:send_to_component(command.component, FanControl.attributes.FanMode:write(device, value)) +end + +local function thermostat_attr_occupiedCoolingSetpoint_handler(driver, device, value, zb_rx) + local ep = zb_rx.address_header.src_endpoint.value + if ep == 1 then + local temp = value.value/100 + device:emit_component_event(device.profile.components.main,capabilities.temperatureSetpoint.temperatureSetpoint({value = temp, unit = "C"})) + end +end + +local function do_refresh(driver, device) + device:send_to_component("main", OnOff.attributes.OnOff:read(device)) + device:send_to_component("main", Thermostat.attributes.LocalTemperature:read(device)) + device:send_to_component("main", Thermostat.attributes.OccupiedCoolingSetpoint:read(device)) + + device:send_to_component("main", Thermostat.attributes.SystemMode:read(device)) + device:send_to_component("main", FanControl.attributes.FanMode:read(device)) + device:send_to_component("fan", OnOff.attributes.OnOff:read(device)) + + device:send_to_component("fan", FanControl.attributes.FanMode:read(device)) + device:send_to_component("heat", OnOff.attributes.OnOff:read(device)) + device:send_to_component("heat", Thermostat.attributes.OccupiedHeatingSetpoint:read(device)) +end + +local function added_handler(driver, device) + device:emit_component_event(device.profile.components.main,ThermostatMode.supportedThermostatModes({"cool", "dryair", "fanonly", "heat"}, { visibility = { displayed = false } })) + device:emit_component_event(device.profile.components.main,FanMode.supportedFanModes( SUPPORTED_FAN_MODES[1] , { visibility = { displayed = false }})) + device:emit_component_event(device.profile.components.fan,FanMode.supportedFanModes( SUPPORTED_FAN_MODES[1] , { visibility = { displayed = false }})) + + device:emit_component_event(device.profile.components.main,capabilities.temperatureSetpoint.temperatureSetpointRange({ value = { minimum = 16.00, maximum = 32.00 }, unit = "C" })) + device:emit_component_event(device.profile.components.heat,capabilities.thermostatHeatingSetpoint.heatingSetpointRange({ value = { minimum = 16.00, maximum = 32.00 }, unit = "C" })) + + device:emit_component_event(device.profile.components.main,capabilities.switch.switch.off()) + device:emit_component_event(device.profile.components.heat,capabilities.switch.switch.off()) + device:emit_component_event(device.profile.components.fan,capabilities.switch.switch.off()) + + device:emit_component_event(device.profile.components.main,ThermostatMode.thermostatMode.cool()) + device:emit_component_event(device.profile.components.main,FanMode.fanMode.auto()) + device:emit_component_event(device.profile.components.fan,FanMode.fanMode.auto()) + device:emit_component_event(device.profile.components.main,capabilities.temperatureSetpoint.temperatureSetpoint({value = 26, unit = "C"})) + device:emit_component_event(device.profile.components.heat,capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 26, unit = "C"})) + + for _, component in pairs(device.profile.components) do + if component.id ~= "main" and component.id ~= "heat" and component.id ~= "fan" then + device:emit_component_event(component, + capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } })) + device:emit_component_event(component, + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } })) + -- Without this time delay, the state of some buttons cannot be updated + socket.sleep(1) + end + end + do_refresh(driver, device) +end + +local function component_to_endpoint(device, component_id) + local ep_num + if component_id == "main" then + ep_num = 1 + elseif component_id == "fan" then + ep_num = 2 + elseif component_id == "heat" then + ep_num = 3 + end + return ep_num or device.fingerprinted_endpoint_id +end + +local function endpoint_to_component(device, ep) + if ep > 3 then + ep = ep - 3 + local button_comp = string.format("button%d", ep) + if device.profile.components[button_comp] ~= nil then + return button_comp + else + return nil + end + else + if ep == 1 then + return "main" + elseif ep == 2 then + return "fan" + else + return "heat" + end + end +end + +local device_init = function(self, device) + device:set_component_to_endpoint_fn(component_to_endpoint) + device:set_endpoint_to_component_fn(endpoint_to_component) +end + +local wallhero_thermostat_3in1 = { + NAME = "Wall Hero thermostat 3in1", + supported_capabilities = { + capabilities.switch, + capabilities.temperatureSetpoint + }, + lifecycle_handlers = { + init = device_init, + added = added_handler + }, + health_check = false, + zigbee_handlers = { + cluster = { + [Scenes.ID] = { + [Scenes.server.commands.RecallScene.ID] = scenes_cluster_handler, + } + }, + attr = { + [OnOff.ID] = { + [OnOff.attributes.OnOff.ID] = on_off_attr_handler + }, + [FanControl.ID] = { + [FanControl.attributes.FanMode.ID] = fan_mode_attr_handler + }, + [Thermostat.ID] = { + [Thermostat.attributes.OccupiedCoolingSetpoint.ID] = thermostat_attr_occupiedCoolingSetpoint_handler + } + } + }, + capability_handlers = { + [capabilities.switch.ID] = { + [capabilities.switch.commands.on.NAME] = on_handler, + [capabilities.switch.commands.off.NAME] = off_handler + }, + [capabilities.temperatureSetpoint.ID] = { + [capabilities.temperatureSetpoint.commands.setTemperatureSetpoint.NAME] = caps_temperatureSetpoint_handler + }, + [capabilities.fanMode.ID] = { + [capabilities.fanMode.commands.setFanMode.NAME] = setFanMode_handler + }, + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + + can_handle = require("wallhero.can_handle") +} + +return wallhero_thermostat_3in1 diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index d1a3ed3369..007f8059b5 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,3 +141,4 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"WALLHERO Thermostat ACL-403STC1",墙壁侠温控ACL-403STC1 From 79b44011cd988d951032a12469d5ee97f82ce746 Mon Sep 17 00:00:00 2001 From: Konrad K <33450498+KKlimczukS@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:55:09 +0200 Subject: [PATCH 427/538] fingerprint for Sonoff SNZB-02B (#3079) --- drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml b/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml index d897ca7ecf..ee36659670 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml +++ b/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml @@ -88,6 +88,11 @@ zigbeeManufacturer: manufacturer: SONOFF model: SNZB-02DR2 deviceProfileName: humidity-temp-battery + - id: "SONOFF/SNZB-02B" + deviceLabel: "SONOFF SNZB-02B" + manufacturer: SONOFF + model: SNZB-02B + deviceProfileName: humidity-temp-battery - id: "Third Reality/3RTHS24BZ" deviceLabel: ThirdReality Temperature and Humidity Sensor manufacturer: Third Reality, Inc From f1303a2014943278cf54d676a8c84f8d9c814538 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:50:04 -0500 Subject: [PATCH 428/538] Matter Window Covering: Add embedded clusters for Descriptor, Global clusters (#3088) --- .../ClosureControl/server/commands/MoveTo.lua | 2 +- .../types/OverallCurrentStateStruct.lua | 2 +- .../types/OverallTargetStateStruct.lua | 2 +- .../server/commands/SetTarget.lua | 2 +- .../ClosureDimension/server/commands/Step.lua | 2 +- .../types/DimensionStateStruct.lua | 2 +- .../src/embedded_clusters/Descriptor/init.lua | 55 +++++++++++ .../server/attributes/PartsList.lua | 78 +++++++++++++++ .../Descriptor/server/attributes/TagList.lua | 80 ++++++++++++++++ .../Descriptor/server/attributes/init.lua | 27 ++++++ .../Descriptor/types/Feature.lua | 56 +++++++++++ .../Descriptor/types/init.lua | 9 ++ .../src/embedded_clusters/Global/init.lua | 8 ++ .../Global/types/SemanticTagStruct.lua | 94 +++++++++++++++++++ .../Global/types/ThreeLevelAutoEnum.lua | 38 ++++++++ .../embedded_clusters/Global/types/init.lua | 18 ++++ .../src/sub_drivers/closure/can_handle.lua | 3 +- .../closure_handlers/attribute_handlers.lua | 4 + .../closure_utils/embedded_cluster_utils.lua | 62 ++++++++++++ .../closure/closure_utils/utils.lua | 5 +- .../src/sub_drivers/closure/init.lua | 9 +- .../init.lua | 2 +- .../src/test/test_matter_closure.lua | 5 + .../src/test/test_matter_window_covering.lua | 5 + 24 files changed, 559 insertions(+), 11 deletions(-) create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/PartsList.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/TagList.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/Feature.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/SemanticTagStruct.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/ThreeLevelAutoEnum.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/init.lua create mode 100644 drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/embedded_cluster_utils.lua diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/MoveTo.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/MoveTo.lua index 80c1bca52d..fb83ed49c2 100644 --- a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/MoveTo.lua +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/MoveTo.lua @@ -25,7 +25,7 @@ MoveTo.field_defs = { field_id = 2, is_nullable = false, is_optional = true, - data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + data_type = require "embedded_clusters.Global.types.ThreeLevelAutoEnum", }, } diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallCurrentStateStruct.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallCurrentStateStruct.lua index 41af9d66db..cf4aaa91df 100644 --- a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallCurrentStateStruct.lua +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallCurrentStateStruct.lua @@ -24,7 +24,7 @@ OverallCurrentStateStruct.field_defs = { field_id = 2, is_nullable = false, is_optional = true, - data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + data_type = require "embedded_clusters.Global.types.ThreeLevelAutoEnum", }, { name = "secure_state", diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallTargetStateStruct.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallTargetStateStruct.lua index eac6492815..f52fff4d1c 100644 --- a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallTargetStateStruct.lua +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallTargetStateStruct.lua @@ -24,7 +24,7 @@ OverallTargetStateStruct.field_defs = { field_id = 2, is_nullable = false, is_optional = true, - data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + data_type = require "embedded_clusters.Global.types.ThreeLevelAutoEnum", }, } diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/SetTarget.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/SetTarget.lua index d2f4a51b78..c14a550f16 100644 --- a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/SetTarget.lua +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/SetTarget.lua @@ -25,7 +25,7 @@ SetTarget.field_defs = { field_id = 2, is_nullable = false, is_optional = true, - data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + data_type = require "embedded_clusters.Global.types.ThreeLevelAutoEnum", }, } diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/Step.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/Step.lua index ce70c7e680..597d2ddd74 100644 --- a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/Step.lua +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/Step.lua @@ -25,7 +25,7 @@ Step.field_defs = { field_id = 2, is_nullable = false, is_optional = true, - data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + data_type = require "embedded_clusters.Global.types.ThreeLevelAutoEnum", }, } diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/DimensionStateStruct.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/DimensionStateStruct.lua index 56958b2020..ecf718cbf3 100644 --- a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/DimensionStateStruct.lua +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/DimensionStateStruct.lua @@ -24,7 +24,7 @@ DimensionStateStruct.field_defs = { field_id = 2, is_nullable = false, is_optional = true, - data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + data_type = require "embedded_clusters.Global.types.ThreeLevelAutoEnum", }, } diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/init.lua new file mode 100644 index 0000000000..78739a10db --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/init.lua @@ -0,0 +1,55 @@ +-- Copyright © 2026 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", + [0x0004] = "TagList", + } + 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", + ["TagList"] = "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-window-covering/src/embedded_clusters/Descriptor/server/attributes/PartsList.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/PartsList.lua new file mode 100644 index 0000000000..be83b6f878 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/PartsList.lua @@ -0,0 +1,78 @@ +-- Copyright © 2026 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-window-covering/src/embedded_clusters/Descriptor/server/attributes/TagList.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/TagList.lua new file mode 100644 index 0000000000..6bc889d407 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/TagList.lua @@ -0,0 +1,80 @@ +-- Copyright © 2026 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 TagList = { + ID = 0x0004, + NAME = "TagList", + _cluster = require "embedded_clusters.Descriptor", + base_type = require "st.matter.data_types.Array", + element_type = require "embedded_clusters.Global.types.SemanticTagStruct", +} + +function TagList: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, TagList.element_type) + end +end + +function TagList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function TagList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + + +function TagList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function TagList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function TagList: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 TagList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(TagList, {__call = TagList.new_value, __index = TagList.base_type}) +return TagList + diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/init.lua new file mode 100644 index 0000000000..ab933c601f --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/init.lua @@ -0,0 +1,27 @@ +-- Copyright © 2026 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-window-covering/src/embedded_clusters/Descriptor/types/Feature.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/Feature.lua new file mode 100644 index 0000000000..90c03f67b0 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/Feature.lua @@ -0,0 +1,56 @@ +-- Copyright © 2026 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.TAG_LIST = 0x0001 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + TAG_LIST = 0x0001, +} + +Feature.is_tag_list_set = function(self) + return (self.value & self.TAG_LIST) ~= 0 +end + +Feature.set_tag_list = function(self) + if self.value ~= nil then + self.value = self.value | self.TAG_LIST + else + self.value = self.TAG_LIST + end +end + +Feature.unset_tag_list = function(self) + self.value = self.value & (~self.TAG_LIST & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.TAG_LIST + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_tag_list_set = Feature.is_tag_list_set, + set_tag_list = Feature.set_tag_list, + unset_tag_list = Feature.unset_tag_list, +} + +Feature.augment_type = function(_cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/init.lua new file mode 100644 index 0000000000..e76712b2af --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/init.lua @@ -0,0 +1,9 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_loader = require "st.utils.lazy_loader" + +local DescriptorTypes = lazy_loader:new { prefix = "embedded_clusters.Descriptor.types." } + +return DescriptorTypes + diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/init.lua new file mode 100644 index 0000000000..5c0c673c40 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/init.lua @@ -0,0 +1,8 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local GlobalTypes = require "embedded_clusters.Global.types" + +local Global = {} +Global.types = GlobalTypes +return Global diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/SemanticTagStruct.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/SemanticTagStruct.lua new file mode 100644 index 0000000000..c0e6f8caa7 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/SemanticTagStruct.lua @@ -0,0 +1,94 @@ +-- Copyright © 2026 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 SemanticTagStruct = {} +local new_mt = StructureABC.new_mt({NAME = "SemanticTagStruct", ID = data_types.name_to_id_map["Structure"]}) + +SemanticTagStruct.field_defs = { + { + name = "mfg_code", + field_id = 0, + is_nullable = true, + is_optional = false, + data_type = require "st.matter.data_types.Uint16", + }, + { + name = "namespace_id", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint8", + }, + { + name = "tag", + field_id = 2, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint8", + }, + { + name = "label", + field_id = 3, + is_nullable = true, + is_optional = true, + data_type = require "st.matter.data_types.UTF8String1", + }, +} + +SemanticTagStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for _idx, field_def in ipairs(cls.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 tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif not (field_def.is_optional and tbl[field_def.name] == nil) then + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +SemanticTagStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = SemanticTagStruct.init +new_mt.__index.serialize = SemanticTagStruct.serialize + +SemanticTagStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + 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 + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(SemanticTagStruct, new_mt) + +return SemanticTagStruct + diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/ThreeLevelAutoEnum.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/ThreeLevelAutoEnum.lua new file mode 100644 index 0000000000..a203c9b505 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/ThreeLevelAutoEnum.lua @@ -0,0 +1,38 @@ +-- Copyright © 2026 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 ThreeLevelAutoEnum = {} +local new_mt = UintABC.new_mt({NAME = "ThreeLevelAutoEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.AUTO] = "AUTO", + [self.LOW] = "LOW", + [self.MEDIUM] = "MEDIUM", + [self.HIGH] = "HIGH", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.AUTO = 0x00 +new_mt.__index.LOW = 0x01 +new_mt.__index.MEDIUM = 0x02 +new_mt.__index.HIGH = 0x03 + +ThreeLevelAutoEnum.AUTO = 0x00 +ThreeLevelAutoEnum.LOW = 0x01 +ThreeLevelAutoEnum.MEDIUM = 0x02 +ThreeLevelAutoEnum.HIGH = 0x03 + +ThreeLevelAutoEnum.augment_type = function(_cls, val) + setmetatable(val, new_mt) +end + +setmetatable(ThreeLevelAutoEnum, new_mt) + + +return ThreeLevelAutoEnum + diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/init.lua new file mode 100644 index 0000000000..f4b0f9b225 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/init.lua @@ -0,0 +1,18 @@ +-- Copyright © 2026 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("embedded_clusters.Global.types." .. key) + end + return types_mt.__types_cache[key] +end + +local GlobalTypes = {} + +setmetatable(GlobalTypes, types_mt) + + +return GlobalTypes diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/can_handle.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/can_handle.lua index a643fa909e..0ca3579b75 100644 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/can_handle.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/can_handle.lua @@ -4,7 +4,8 @@ local CLOSURE_CONTROL_CLUSTER_ID = 0x0104 return function(opts, driver, device) - if #device:get_endpoints(CLOSURE_CONTROL_CLUSTER_ID) > 0 then + local embedded_cluster_utils = require "sub_drivers.closure.closure_utils.embedded_cluster_utils" + if #embedded_cluster_utils.get_endpoints(device, CLOSURE_CONTROL_CLUSTER_ID) > 0 then return true, require("sub_drivers.closure") end return false diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/attribute_handlers.lua index c574991c82..24faac339b 100644 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/attribute_handlers.lua @@ -8,6 +8,7 @@ local version = require "version" if version.api < 20 then clusters.ClosureControl = require "embedded_clusters.ClosureControl" clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" + clusters.Global = require "embedded_clusters.Global" end local fields = require "sub_drivers.closure.closure_utils.fields" @@ -63,6 +64,9 @@ end function ClosureAttrHandlers.tag_list_handler(driver, device, ib, response) if not ib.data.elements then return end local tag_value + if version.api < 16 then + clusters.Global.types.SemanticTagStruct:augment_type(ib.data) + end for _, v in ipairs(ib.data.elements) do local tag = v.elements if tag and tag.namespace_id and tag.namespace_id.value == 0x44 then diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/embedded_cluster_utils.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/embedded_cluster_utils.lua new file mode 100644 index 0000000000..022f86987a --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/embedded_cluster_utils.lua @@ -0,0 +1,62 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local clusters = require "st.matter.clusters" +local utils = require "st.utils" +local version = require "version" + +if version.api < 20 then + clusters.ClosureControl = require "embedded_clusters.ClosureControl" + clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" +end + +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" +end + +local embedded_cluster_utils = {} + +local embedded_clusters_16 = { + [clusters.Descriptor.ID] = clusters.Descriptor +} + +local embedded_clusters_20 = { + [clusters.ClosureControl.ID] = clusters.ClosureControl, + [clusters.ClosureDimension.ID] = clusters.ClosureDimension +} + +function embedded_cluster_utils.get_endpoints(device, cluster_id, opts) + -- If using older lua libs and need to check for an embedded cluster feature, + -- we must use the embedded cluster definitions here + if (version.api < 16 and embedded_clusters_16[cluster_id] ~= nil) + or (version.api < 20 and embedded_clusters_20[cluster_id] ~= nil) then + local embedded_cluster = embedded_clusters_16[cluster_id] or embedded_clusters_20[cluster_id] + local opts = opts or {} + if utils.table_size(opts) > 1 then + device.log.warn_with({hub_logs = true}, "Invalid options for get_endpoints") + return + end + local clus_has_features = function(clus, feature_bitmap) + if not feature_bitmap or not clus then return false end + return embedded_cluster.are_features_supported(feature_bitmap, clus.feature_map) + end + local eps = {} + for _, ep in ipairs(device.endpoints) do + for _, clus in ipairs(ep.clusters) do + if ((clus.cluster_id == cluster_id) + and (opts.feature_bitmap == nil or clus_has_features(clus, opts.feature_bitmap)) + and ((opts.cluster_type == nil and clus.cluster_type == "SERVER" or clus.cluster_type == "BOTH") + or (opts.cluster_type == clus.cluster_type)) + or (cluster_id == nil)) then + table.insert(eps, ep.endpoint_id) + if cluster_id == nil then break end + end + end + end + return eps + else + return device:get_endpoints(cluster_id, opts) + end + end + + return embedded_cluster_utils diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/utils.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/utils.lua index df6dc9015c..8597d029f5 100644 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/utils.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/utils.lua @@ -13,12 +13,13 @@ if version.api < 20 then end local fields = require "sub_drivers.closure.closure_utils.fields" +local embedded_cluster_utils = require "sub_drivers.closure.closure_utils.embedded_cluster_utils" local utils = {} function utils.find_default_endpoint(device, cluster) local res = device.MATTER_DEFAULT_ENDPOINT - local eps = device:get_endpoints(cluster) + 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 @@ -33,7 +34,7 @@ function utils.find_default_endpoint(device, cluster) end function utils.get_closure_dimension_eps(device) - local eps = device:get_endpoints(clusters.ClosureDimension.ID) or {} + local eps = embedded_cluster_utils.get_endpoints(device, clusters.ClosureDimension.ID) or {} table.sort(eps) local result = {} for _, ep in ipairs(eps) do diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/init.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/init.lua index 638f561415..397167d3df 100644 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/init.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/init.lua @@ -5,12 +5,19 @@ local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local log = require "log" local version = require "version" +local embedded_cluster_utils = require "sub_drivers.closure.closure_utils.embedded_cluster_utils" if version.api < 20 then clusters.ClosureControl = require "embedded_clusters.ClosureControl" clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" + clusters.Global = require "embedded_clusters.Global" end +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" +end + + local fields = require "sub_drivers.closure.closure_utils.fields" local closure_utils = require "sub_drivers.closure.closure_utils.utils" local attribute_handlers = require "sub_drivers.closure.closure_handlers.attribute_handlers" @@ -55,7 +62,7 @@ function ClosureLifecycleHandlers.device_added(driver, device) end function ClosureLifecycleHandlers.do_configure(driver, device) - if #device:get_endpoints(clusters.Descriptor.ID) == 0 then + if #embedded_cluster_utils.get_endpoints(device, clusters.Descriptor.ID) == 0 then log.warn( "Descriptor cluster not implemented on ClosureControl endpoint, " .. "cannot read TagList to determine closure type" diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua index 56407c6667..5db95ba28c 100644 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua @@ -119,7 +119,7 @@ local matter_window_covering_position_updates_while_moving_handler = { }, capability_handlers = { }, - can_handle = require("matter-window-covering-position-updates-while-moving.can_handle"), + can_handle = require("sub_drivers.matter-window-covering-position-updates-while-moving.can_handle"), shared_device_thread_enabled = true, } diff --git a/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua b/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua index 95a4c6f112..e39cb428c8 100644 --- a/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua +++ b/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua @@ -11,6 +11,11 @@ local version = require "version" if version.api < 20 then clusters.ClosureControl = require "embedded_clusters.ClosureControl" clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" + clusters.Global = require "embedded_clusters.Global" +end + +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" end -- --------------------------------------------------------------------------- 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 9f273037f3..cf2b9fd681 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 @@ -8,6 +8,11 @@ local t_utils = require "integration_test.utils" local uint32 = require "st.matter.data_types.Uint32" local clusters = require "st.matter.clusters" +local version = require "version" +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" +end + local WindowCovering = clusters.WindowCovering test.disable_startup_messages() From f8c68740466e0b9fd7de4c9c6f75b8cc601e2d09 Mon Sep 17 00:00:00 2001 From: Kwang-Hui Cho Date: Tue, 7 Jul 2026 08:15:43 +0900 Subject: [PATCH 429/538] Add eufy product --- .../SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua index 4b15b23b2b..fef7746990 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua @@ -28,6 +28,7 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x1533, 0x0012}, -- eufy, FamiLock E35 {0x1533, 0x0016}, -- eufy, FamiLock E32 {0x1533, 0x0014}, -- eufy, FamiLock E40 + {0x1533, 0x001F}, -- eufy, EAP {0x135D, 0x00B1}, -- Nuki, Smart Lock Pro {0x135D, 0x00B2}, -- Nuki, Smart Lock {0x135D, 0x00C1}, -- Nuki, Smart Lock From 7b18436b90cee44a916c873fcfd598d8a00cac81 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 7 Jul 2026 11:58:24 -0500 Subject: [PATCH 430/538] Add device profile for U600 (#3097) --- drivers/SmartThings/matter-lock/fingerprints.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 575da17382..6d4d068b71 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -39,6 +39,7 @@ matterManufacturer: deviceLabel: Aqara Smart Lock U600 vendorId: 0x115F productId: 0x284A + deviceProfileName: lock-modular - id: "4447/10253" deviceLabel: Aqara Smart Lock J200 vendorId: 0x115F From ed47433bd2f33bdf7252192baf855a979dccf5bd Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Fri, 15 May 2026 13:47:27 -0500 Subject: [PATCH 431/538] Starting of passing PR number --- .github/workflows/jenkins-driver-tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/jenkins-driver-tests.yml b/.github/workflows/jenkins-driver-tests.yml index 6494eadd15..aff7f6f150 100644 --- a/.github/workflows/jenkins-driver-tests.yml +++ b/.github/workflows/jenkins-driver-tests.yml @@ -40,6 +40,7 @@ jobs: -H "Authorization: Bearer ${JENKINS_WEBHOOK_TOKEN}" \ -d "{\"status_url\": \"$STATUS_URL\", \"version\": ${{ matrix.version }}, - \"commit\": ${{ github.event.pull_request.head.sha }} }" \ + \"commit\": ${{ github.event.pull_request.head.sha }}, + \"pr_id\": ${{ github.event.number }} }" \ "${JENKINS_WEBHOOK_URL}" set -x From d09e25692d00e51a83112cafbaf5e55dc5519420 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu, 9 Jul 2026 09:46:57 -0500 Subject: [PATCH 432/538] Z-Wave Lock: Support SLGA Migration (#3087) --- .../zwave-lock/profiles/base-lock-tamper.yml | 4 + .../zwave-lock/profiles/base-lock.yml | 4 + drivers/SmartThings/zwave-lock/src/init.lua | 225 ++---- .../zwave-lock/src/keywe-lock/can_handle.lua | 10 +- .../zwave-lock/src/keywe-lock/init.lua | 8 +- .../src/legacy-handlers/can_handle.lua | 12 + .../zwave-lock/src/legacy-handlers/init.lua | 133 +++ .../legacy-handlers/keywe-lock/can_handle.lua | 12 + .../src/legacy-handlers/keywe-lock/init.lua | 71 ++ .../samsung-lock/can_handle.lua | 11 + .../src/legacy-handlers/samsung-lock/init.lua | 96 +++ .../schlage-lock/can_handle.lua | 11 + .../src/legacy-handlers/schlage-lock/init.lua | 178 ++++ .../src/legacy-handlers/sub_drivers.lua | 11 + .../zwave-alarm-v1-lock/can_handle.lua | 10 + .../zwave-alarm-v1-lock/init.lua | 146 ++++ .../src/lock_handlers/capabilities.lua | 252 ++++++ .../src/lock_handlers/zwave_responses.lua | 243 ++++++ .../zwave-lock/src/lock_utils/constants.lua | 48 ++ .../zwave-lock/src/lock_utils/tables.lua | 268 ++++++ .../zwave-lock/src/lock_utils/utils.lua | 160 ++++ .../src/samsung-lock/can_handle.lua | 17 +- .../zwave-lock/src/samsung-lock/init.lua | 60 +- .../src/schlage-lock/can_handle.lua | 17 +- .../zwave-lock/src/schlage-lock/init.lua | 133 +-- .../zwave-lock/src/sub_drivers.lua | 1 + .../src/test/test_init_lifecycle_handlers.lua | 198 +++++ .../zwave-lock/src/test/test_keywe_lock.lua | 84 +- .../src/test/test_keywe_lock_legacy.lua | 101 +++ .../test/test_lock_code_slga_migration.lua | 127 +++ .../test/test_lock_credentials_commands.lua | 480 +++++++++++ .../src/test/test_lock_pre_configured.lua | 514 ++++++++++++ .../zwave-lock/src/test/test_lock_tables.lua | 761 ++++++++++++++++++ .../src/test/test_lock_users_commands.lua | 705 ++++++++++++++++ .../zwave-lock/src/test/test_samsung_lock.lua | 190 +++-- .../src/test/test_samsung_lock_legacy.lua | 183 +++++ .../zwave-lock/src/test/test_schlage_lock.lua | 189 +++-- .../src/test/test_schlage_lock_legacy.lua | 266 ++++++ .../test/test_zwave_lock_code_migration.lua | 251 ------ ...ve_lock.lua => test_zwave_lock_legacy.lua} | 41 +- .../src/test/test_zwave_responses.lua | 393 +++++++++ .../src/zwave-alarm-v1-lock/can_handle.lua | 15 +- .../src/zwave-alarm-v1-lock/init.lua | 100 +-- 43 files changed, 5866 insertions(+), 873 deletions(-) create mode 100644 drivers/SmartThings/zwave-lock/src/legacy-handlers/can_handle.lua create mode 100644 drivers/SmartThings/zwave-lock/src/legacy-handlers/init.lua create mode 100644 drivers/SmartThings/zwave-lock/src/legacy-handlers/keywe-lock/can_handle.lua create mode 100644 drivers/SmartThings/zwave-lock/src/legacy-handlers/keywe-lock/init.lua create mode 100644 drivers/SmartThings/zwave-lock/src/legacy-handlers/samsung-lock/can_handle.lua create mode 100644 drivers/SmartThings/zwave-lock/src/legacy-handlers/samsung-lock/init.lua create mode 100644 drivers/SmartThings/zwave-lock/src/legacy-handlers/schlage-lock/can_handle.lua create mode 100644 drivers/SmartThings/zwave-lock/src/legacy-handlers/schlage-lock/init.lua create mode 100644 drivers/SmartThings/zwave-lock/src/legacy-handlers/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-lock/src/legacy-handlers/zwave-alarm-v1-lock/can_handle.lua create mode 100644 drivers/SmartThings/zwave-lock/src/legacy-handlers/zwave-alarm-v1-lock/init.lua create mode 100644 drivers/SmartThings/zwave-lock/src/lock_handlers/capabilities.lua create mode 100644 drivers/SmartThings/zwave-lock/src/lock_handlers/zwave_responses.lua create mode 100644 drivers/SmartThings/zwave-lock/src/lock_utils/constants.lua create mode 100644 drivers/SmartThings/zwave-lock/src/lock_utils/tables.lua create mode 100644 drivers/SmartThings/zwave-lock/src/lock_utils/utils.lua create mode 100644 drivers/SmartThings/zwave-lock/src/test/test_init_lifecycle_handlers.lua create mode 100644 drivers/SmartThings/zwave-lock/src/test/test_keywe_lock_legacy.lua create mode 100644 drivers/SmartThings/zwave-lock/src/test/test_lock_code_slga_migration.lua create mode 100644 drivers/SmartThings/zwave-lock/src/test/test_lock_credentials_commands.lua create mode 100644 drivers/SmartThings/zwave-lock/src/test/test_lock_pre_configured.lua create mode 100644 drivers/SmartThings/zwave-lock/src/test/test_lock_tables.lua create mode 100644 drivers/SmartThings/zwave-lock/src/test/test_lock_users_commands.lua create mode 100644 drivers/SmartThings/zwave-lock/src/test/test_samsung_lock_legacy.lua create mode 100644 drivers/SmartThings/zwave-lock/src/test/test_schlage_lock_legacy.lua delete mode 100644 drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_code_migration.lua rename drivers/SmartThings/zwave-lock/src/test/{test_zwave_lock.lua => test_zwave_lock_legacy.lua} (97%) create mode 100644 drivers/SmartThings/zwave-lock/src/test/test_zwave_responses.lua diff --git a/drivers/SmartThings/zwave-lock/profiles/base-lock-tamper.yml b/drivers/SmartThings/zwave-lock/profiles/base-lock-tamper.yml index 5fbfb13f3d..e3a25ab57a 100644 --- a/drivers/SmartThings/zwave-lock/profiles/base-lock-tamper.yml +++ b/drivers/SmartThings/zwave-lock/profiles/base-lock-tamper.yml @@ -6,6 +6,10 @@ components: version: 1 - id: lockCodes version: 1 + - id: lockCredentials + version: 1 + - id: lockUsers + version: 1 - id: battery version: 1 - id: tamperAlert diff --git a/drivers/SmartThings/zwave-lock/profiles/base-lock.yml b/drivers/SmartThings/zwave-lock/profiles/base-lock.yml index f4957f9ad0..efb97c8b27 100644 --- a/drivers/SmartThings/zwave-lock/profiles/base-lock.yml +++ b/drivers/SmartThings/zwave-lock/profiles/base-lock.yml @@ -6,6 +6,10 @@ components: version: 1 - id: lockCodes version: 1 + - id: lockCredentials + version: 1 + - id: lockUsers + version: 1 - id: battery version: 1 - id: refresh diff --git a/drivers/SmartThings/zwave-lock/src/init.lua b/drivers/SmartThings/zwave-lock/src/init.lua index c3506c5005..8b2cf76eb8 100644 --- a/drivers/SmartThings/zwave-lock/src/init.lua +++ b/drivers/SmartThings/zwave-lock/src/init.lua @@ -1,175 +1,120 @@ --- Copyright 2022 SmartThings, Inc. +-- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 -local capabilities = require "st.capabilities" ---- @type st.zwave.CommandClass -local cc = require "st.zwave.CommandClass" --- @type st.zwave.Driver local ZwaveDriver = require "st.zwave.driver" --- @type st.zwave.defaults local defaults = require "st.zwave.defaults" ---- @type st.zwave.CommandClass.DoorLock -local DoorLock = (require "st.zwave.CommandClass.DoorLock")({ version = 1 }) +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.UserCode local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) ---- @type st.zwave.CommandClass.Battery -local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) ---- @type st.zwave.CommandClass.Time -local Time = (require "st.zwave.CommandClass.Time")({ version = 1 }) -local constants = require "st.zwave.constants" -local utils = require "st.utils" -local json = require "st.json" +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) -local SCAN_CODES_CHECK_INTERVAL = 30 -local MIGRATION_COMPLETE = "migrationComplete" -local MIGRATION_RELOAD_SKIPPED = "migrationReloadSkipped" +local capabilities = require "st.capabilities" -local function periodic_codes_state_verification(driver, device) - local scan_codes_state = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.scanCodes.NAME) - if scan_codes_state == "Scanning" then - driver:inject_capability_command(device, - { capability = capabilities.lockCodes.ID, - command = capabilities.lockCodes.commands.reloadAllCodes.NAME, - args = {} - } - ) - device.thread:call_with_delay( - SCAN_CODES_CHECK_INTERVAL, - function(d) - periodic_codes_state_verification(driver, device) - end - ) - end -end +local consts = require "lock_utils.constants" +local lock_utils = require "lock_utils.utils" +local table_utils = require "lock_utils.tables" +local zwave_handlers = require "lock_handlers.zwave_responses" +local capability_handlers = require "lock_handlers.capabilities" -local function populate_state_from_data(device) - if device.data.lockCodes ~= nil and device:get_field(MIGRATION_COMPLETE) ~= true then - -- build the lockCodes table - local lockCodes = {} - local lc_data = json.decode(device.data.lockCodes) - for k, v in pairs(lc_data) do - lockCodes[k] = v - end - -- Populate the devices `lockCodes` field - device:set_field(constants.LOCK_CODES, utils.deep_copy(lockCodes), { persist = true }) - -- Populate the devices state history cache - device.state_cache["main"] = device.state_cache["main"] or {} - device.state_cache["main"][capabilities.lockCodes.ID] = device.state_cache["main"][capabilities.lockCodes.ID] or {} - device.state_cache["main"][capabilities.lockCodes.ID][capabilities.lockCodes.lockCodes.NAME] = {value = json.encode(utils.deep_copy(lockCodes))} - device:set_field(MIGRATION_COMPLETE, true, { persist = true }) - end -end +local LockLifecycle = {} ---- Builds up initial state for the device ---- ---- @param self st.zwave.Driver ---- @param device st.zwave.Device -local function added_handler(self, device) - populate_state_from_data(device) - if device.data.lockCodes == nil or device:get_field(MIGRATION_RELOAD_SKIPPED) == true then - if (device:supports_capability(capabilities.lockCodes)) then - self:inject_capability_command(device, - { capability = capabilities.lockCodes.ID, - command = capabilities.lockCodes.commands.reloadAllCodes.NAME, - args = {} }) - device.thread:call_with_delay( - SCAN_CODES_CHECK_INTERVAL, - function(d) - periodic_codes_state_verification(self, device) - end - ) - end - else - device:set_field(MIGRATION_RELOAD_SKIPPED, true, { persist = true }) +function LockLifecycle.device_added(driver, device) + if device:supports_capability(capabilities.lockCodes) and device._provisioning_state == "TYPED" then + -- set the migrated field to true so new devices use lockCredentials/lockUsers from the start. + -- auto-migration is only run for typed devices, as provisioned devices have already been onboarded, + -- and should be migrated manually by the user. + device:emit_event(capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) end - device:send(DoorLock:OperationGet({})) - device:send(Battery:Get({})) - if (device:supports_capability(capabilities.tamperAlert)) then + if device:supports_capability(capabilities.tamperAlert) then device:emit_event(capabilities.tamperAlert.tamper.clear()) end + -- set initial state + driver:inject_capability_command(device, { + capability = capabilities.refresh.ID, + command = capabilities.refresh.commands.refresh.NAME, + args = {} + }) + -- ensure our user/credential state is accurate to the current device state + device.thread:call_with_delay(5, function() lock_utils.sync_device_state(device) end) end -local init_handler = function(driver, device, event) - populate_state_from_data(device) - -- temp fix before this can be changed from being persisted in memory - device:set_field(constants.CODE_STATE, nil, { persist = true }) -end +function LockLifecycle.init(driver, device) + -- Restore users/credentials capability state from the persistent store in case + -- the capability state cache was wiped since the last driver run. + table_utils.restore_from_persistent_store(device) -local do_refresh = function(self, device) - device:send(DoorLock:OperationGet({})) - device:send(Battery:Get({})) -end + local lock_pins_supported_by_profile = device:supports_capability(capabilities.lockCodes) + if lock_pins_supported_by_profile and device:get_field(consts.DRIVER_STATE.SLGA_MIGRATED) == true then + -- ensure lockCodes capability state is reflected correctly for already migrated devices + device:emit_event(capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + device:emit_event(capabilities.lockCredentials.supportedCredentials({ consts.CRED_TYPE_PIN }, { visibility = { displayed = false } })) + end ---- @param driver st.zwave.Driver ---- @param device st.zwave.Device ---- @param cmd table -local function update_codes(driver, device, cmd) - local delay = 0 - -- args.codes is json - for name, code in pairs(cmd.args.codes) do - -- these seem to come in the format "code[slot#]: code" - local code_slot = tonumber(string.gsub(name, "code", ""), 10) - if (code_slot ~= nil) then - if (code ~= nil and (code ~= "0" and code ~= "")) then - -- code changed - device.thread:call_with_delay(delay, function () - device:send(UserCode:Set({ - user_identifier = code_slot, - user_code = code, - user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS})) - end) - delay = delay + 2.2 - else - -- code deleted - device.thread:call_with_delay(delay, function () - device:send(UserCode:Set({user_identifier = code_slot, user_id_status = UserCode.user_id_status.AVAILABLE})) - end) - delay = delay + 2.2 - device.thread:call_with_delay(delay, function () - device:send(UserCode:Get({user_identifier = code_slot})) - end) - delay = delay + 2.2 - end - end + if device:supports_capability(capabilities.tamperAlert) then + -- ensure our user/credential state is accurate to the current device state + device:emit_event(capabilities.tamperAlert.tamper.clear()) end end -local function time_get_handler(driver, device, cmd) - local time = os.date("*t") - device:send_to_component( - Time:Report({ - hour_local_time = time.hour, - minute_local_time = time.min, - second_local_time = time.sec - }), - device:endpoint_to_component(cmd.src_channel) - ) +function LockLifecycle.driver_switched(driver, device) + if device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.migrated.NAME) == true then + device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) + end + device:try_update_metadata({ provisioning_state = "PROVISIONED" }) end local driver_template = { - supported_capabilities = { - capabilities.lock, - capabilities.lockCodes, - capabilities.battery, - capabilities.tamperAlert - }, lifecycle_handlers = { - added = added_handler, - init = init_handler, + added = LockLifecycle.device_added, + driver_switched = LockLifecycle.driver_switched, + init = LockLifecycle.init, + }, + zwave_handlers = { + [cc.TIME] = { + [0x01] = zwave_handlers.time_get_handler -- used by DanaLock + }, + [cc.NOTIFICATION] = { + [Notification.REPORT] = zwave_handlers.notification_report + }, + [cc.USER_CODE] = { + [UserCode.REPORT] = zwave_handlers.user_code_report, + [UserCode.USERS_NUMBER_REPORT] = zwave_handlers.users_number_report, + } }, capability_handlers = { - [capabilities.lockCodes.ID] = { - [capabilities.lockCodes.commands.updateCodes.NAME] = update_codes + [capabilities.lock.ID] = { + [capabilities.lock.commands.lock.NAME] = capability_handlers.lock, + [capabilities.lock.commands.unlock.NAME] = capability_handlers.unlock, + }, + [capabilities.lockUsers.ID] = { + [capabilities.lockUsers.commands.addUser.NAME] = capability_handlers.add_user, + [capabilities.lockUsers.commands.updateUser.NAME] = capability_handlers.update_user, + [capabilities.lockUsers.commands.deleteUser.NAME] = capability_handlers.delete_user, + [capabilities.lockUsers.commands.deleteAllUsers.NAME] = capability_handlers.delete_all_users, + }, + [capabilities.lockCredentials.ID] = { + [capabilities.lockCredentials.commands.addCredential.NAME] = capability_handlers.add_credential, + [capabilities.lockCredentials.commands.updateCredential.NAME] = capability_handlers.update_credential, + [capabilities.lockCredentials.commands.deleteCredential.NAME] = capability_handlers.delete_credential, + [capabilities.lockCredentials.commands.deleteAllCredentials.NAME] = capability_handlers.delete_all_credentials, }, [capabilities.refresh.ID] = { - [capabilities.refresh.commands.refresh.NAME] = do_refresh - } + [capabilities.refresh.commands.refresh.NAME] = capability_handlers.refresh, + }, }, - zwave_handlers = { - [cc.TIME] = { - [Time.GET] = time_get_handler -- used by DanaLock - } + supported_capabilities = { + capabilities.lock, + capabilities.lockCodes, + capabilities.lockUsers, + capabilities.lockCredentials, + capabilities.battery, + capabilities.tamperAlert, }, sub_drivers = require("sub_drivers"), shared_device_thread_enabled = true, diff --git a/drivers/SmartThings/zwave-lock/src/keywe-lock/can_handle.lua b/drivers/SmartThings/zwave-lock/src/keywe-lock/can_handle.lua index d8bcd5756e..fda854593d 100644 --- a/drivers/SmartThings/zwave-lock/src/keywe-lock/can_handle.lua +++ b/drivers/SmartThings/zwave-lock/src/keywe-lock/can_handle.lua @@ -2,9 +2,13 @@ -- Licensed under the Apache License, Version 2.0 local function can_handle_keywe_lock(opts, self, device, cmd, ...) - local KEYWE_MFR = 0x037B - if device.zwave_manufacturer_id == KEYWE_MFR then - return true, require("keywe-lock") + local consts = require("lock_utils.constants") + local slga_migrated = device:get_field(consts.DRIVER_STATE.SLGA_MIGRATED) + if slga_migrated then + local KEYWE_MFR = 0x037B + if device.zwave_manufacturer_id == KEYWE_MFR then + return true, require("legacy-handlers.keywe-lock") + end end return false end diff --git a/drivers/SmartThings/zwave-lock/src/keywe-lock/init.lua b/drivers/SmartThings/zwave-lock/src/keywe-lock/init.lua index a51af26e00..455d301473 100644 --- a/drivers/SmartThings/zwave-lock/src/keywe-lock/init.lua +++ b/drivers/SmartThings/zwave-lock/src/keywe-lock/init.lua @@ -9,9 +9,8 @@ local Association = (require "st.zwave.CommandClass.Association")({version=2}) local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) local access_control_event = Notification.event.access_control -local LockDefaults = require "st.zwave.defaults.lock" -local LockCodesDefaults = require "st.zwave.defaults.lockCodes" local TamperDefaults = require "st.zwave.defaults.tamperAlert" +local zwave_handlers = require "lock_handlers.zwave_responses" local TAMPER_CLEAR_DELAY = 10 @@ -39,8 +38,9 @@ local function notification_report_handler(self, device, cmd) if event ~= nil then device:emit_event(event) else - LockDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, cmd) - LockCodesDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, cmd) + zwave_handlers.door_operation_event_handler(self, device, cmd) + zwave_handlers.code_event_handler(self, device, cmd) + TamperDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, cmd) device.thread:call_with_delay( TAMPER_CLEAR_DELAY, diff --git a/drivers/SmartThings/zwave-lock/src/legacy-handlers/can_handle.lua b/drivers/SmartThings/zwave-lock/src/legacy-handlers/can_handle.lua new file mode 100644 index 0000000000..78757208a4 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/legacy-handlers/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local consts = require("lock_utils.constants") + local slga_migrated = device:get_field(consts.DRIVER_STATE.SLGA_MIGRATED) + if not slga_migrated then + local subdriver = require("legacy-handlers") + return true, subdriver + end + return false +end diff --git a/drivers/SmartThings/zwave-lock/src/legacy-handlers/init.lua b/drivers/SmartThings/zwave-lock/src/legacy-handlers/init.lua new file mode 100644 index 0000000000..3bcb8adfe4 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/legacy-handlers/init.lua @@ -0,0 +1,133 @@ +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local cc = require "st.zwave.CommandClass" +local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) +local UserCode = (require "st.zwave.CommandClass.UserCode")({version=1}) +local LockDefaults = require "st.zwave.defaults.lock" +local LockCodesDefaults = require "st.zwave.defaults.lockCodes" + +local init_handler = function(driver, device, event) + local constants = require "st.zwave.constants" + -- temp fix before this can be changed from being persisted in memory + device:set_field(constants.CODE_STATE, nil, { persist = true }) +end + +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param cmd table +local function update_codes(driver, device, cmd) + local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) + local delay = 0 + -- args.codes is json + for name, code in pairs(cmd.args.codes) do + -- these seem to come in the format "code[slot#]: code" + local code_slot = tonumber(string.gsub(name, "code", ""), 10) + if (code_slot ~= nil) then + if (code ~= nil and (code ~= "0" and code ~= "")) then + -- code changed + device.thread:call_with_delay(delay, function () + device:send(UserCode:Set({ + user_identifier = code_slot, + user_code = code, + user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS})) + end) + delay = delay + 2.2 + else + -- code deleted + device.thread:call_with_delay(delay, function () + device:send(UserCode:Set({user_identifier = code_slot, user_id_status = UserCode.user_id_status.AVAILABLE})) + end) + delay = delay + 2.2 + device.thread:call_with_delay(delay, function () + device:send(UserCode:Get({user_identifier = code_slot})) + end) + delay = delay + 2.2 + end + end + end +end + +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param cmd table +local function migrate(driver, device, cmd) + local LockCodesDefaults = require "st.zwave.defaults.lockCodes" + local get_lock_codes = LockCodesDefaults.get_lock_codes + local lock_users = {} + local lock_credentials = {} + local lock_codes = get_lock_codes(device) + local ordered_codes = {} + + for code in pairs(lock_codes) do + table.insert(ordered_codes, code) + end + + table.sort(ordered_codes) + for index = 1, #ordered_codes do + local code_slot, code_name = ordered_codes[index], lock_codes[ ordered_codes[index] ] + table.insert(lock_users, {userIndex = index, userType = "guest", userName = code_name}) + table.insert(lock_credentials, {userIndex = index, credentialIndex = tonumber(code_slot), credentialType = "pin"}) + end + device:emit_event(capabilities.lockCredentials.credentials(lock_credentials, { visibility = { displayed = false } })) + device:emit_event(capabilities.lockUsers.users(lock_users, { visibility = { displayed = false } })) + + local code_length = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.codeLength.NAME) + if code_length then + device:emit_event(capabilities.lockCredentials.minPinCodeLen(code_length, { visibility = { displayed = false } })) + device:emit_event(capabilities.lockCredentials.maxPinCodeLen(code_length, { visibility = { displayed = false } })) + end + + local max_codes = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodes.NAME) + if max_codes then + device:emit_event(capabilities.lockCredentials.pinUsersSupported(max_codes, { visibility = { displayed = false } })) + device:emit_event(capabilities.lockUsers.totalUsersSupported(max_codes, { visibility = { displayed = false } })) + else + -- if we don't have a code length, request it from the device + device:send(UserCode:UsersNumberGet({})) + end + + device:emit_event(capabilities.lockCredentials.supportedCredentials({"pin"}, { visibility = { displayed = false } })) + device:emit_event(capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + local consts = require("lock_utils.constants") + + device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) -- persist the migrated state to the datastore +end + +local legacy_capabilities = { + supported_capabilities = { + capabilities.lock, + capabilities.lockCodes, + capabilities.battery, + capabilities.tamperAlert + }, + lifecycle_handlers = { + init = init_handler, + }, + capability_handlers = { + [capabilities.lockCodes.ID] = { + [capabilities.lockCodes.commands.updateCodes.NAME] = update_codes, + [capabilities.lockCodes.commands.migrate.NAME] = migrate + }, + }, + zwave_handlers = { + [cc.NOTIFICATION] = { + [Notification.REPORT] = function(driver, device, cmd) + LockDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](driver, device, cmd) + LockCodesDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](driver, device, cmd) + local TamperDefaults = require "st.zwave.defaults.tamperAlert" + TamperDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](driver, device, cmd) + end + }, + [cc.USER_CODE] = { + [UserCode.REPORT] = LockCodesDefaults.zwave_handlers[cc.USER_CODE][UserCode.REPORT], + [UserCode.USERS_NUMBER_REPORT] = LockCodesDefaults.zwave_handlers[cc.USER_CODE][UserCode.USERS_NUMBER_REPORT], + } + }, + sub_drivers = require("legacy-handlers.sub_drivers"), + can_handle = require("legacy-handlers.can_handle"), + NAME = "legacy-handlers" +} + +return legacy_capabilities diff --git a/drivers/SmartThings/zwave-lock/src/legacy-handlers/keywe-lock/can_handle.lua b/drivers/SmartThings/zwave-lock/src/legacy-handlers/keywe-lock/can_handle.lua new file mode 100644 index 0000000000..0970a9c9ed --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/legacy-handlers/keywe-lock/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_keywe_lock(opts, self, device, cmd, ...) + local KEYWE_MFR = 0x037B + if device.zwave_manufacturer_id == KEYWE_MFR then + return true, require("legacy-handlers.keywe-lock") + end + return false +end + +return can_handle_keywe_lock diff --git a/drivers/SmartThings/zwave-lock/src/legacy-handlers/keywe-lock/init.lua b/drivers/SmartThings/zwave-lock/src/legacy-handlers/keywe-lock/init.lua new file mode 100644 index 0000000000..b40e6658b1 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/legacy-handlers/keywe-lock/init.lua @@ -0,0 +1,71 @@ +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local capabilities = require "st.capabilities" +local cc = require "st.zwave.CommandClass" + +local Association = (require "st.zwave.CommandClass.Association")({version=2}) +local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) +local access_control_event = Notification.event.access_control + +local LockDefaults = require "st.zwave.defaults.lock" +local LockCodesDefaults = require "st.zwave.defaults.lockCodes" +local TamperDefaults = require "st.zwave.defaults.tamperAlert" + +local TAMPER_CLEAR_DELAY = 10 + +local function clear_tamper_if_needed(device) + local current_tamper_state = device:get_latest_state("main", capabilities.tamperAlert.ID, capabilities.tamperAlert.tamper.NAME) + if current_tamper_state == "detected" then + device:emit_event(capabilities.tamperAlert.tamper.clear()) + end +end + +local function notification_report_handler(self, device, cmd) + local event + if (cmd.args.notification_type == Notification.notification_type.ACCESS_CONTROL) then + local event_code = cmd.args.event + if event_code == access_control_event.WINDOW_DOOR_HANDLE_IS_OPEN then + event = capabilities.lock.lock.unlocked() + elseif event_code == access_control_event.WINDOW_DOOR_HANDLE_IS_CLOSED then + event = capabilities.lock.lock.locked() + end + if event ~= nil then + event["data"] = {method = "manual"} + end + end + + if event ~= nil then + device:emit_event(event) + else + LockDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, cmd) + LockCodesDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, cmd) + TamperDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, cmd) + device.thread:call_with_delay( + TAMPER_CLEAR_DELAY, + function(d) + clear_tamper_if_needed(device) + end + ) + end +end + +local function do_configure(self, device) + device:send(Association:Set({grouping_identifier = 2, node_ids = {self.environment_info.hub_zwave_id}})) +end + +local keywe_lock = { + zwave_handlers = { + [cc.NOTIFICATION] = { + [Notification.REPORT] = notification_report_handler + } + }, + lifecycle_handlers = { + doConfigure = do_configure + }, + NAME = "Keywe Lock", + can_handle = require("keywe-lock.can_handle"), +} + +return keywe_lock \ No newline at end of file diff --git a/drivers/SmartThings/zwave-lock/src/legacy-handlers/samsung-lock/can_handle.lua b/drivers/SmartThings/zwave-lock/src/legacy-handlers/samsung-lock/can_handle.lua new file mode 100644 index 0000000000..dc97d03ece --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/legacy-handlers/samsung-lock/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, cmd) + local SAMSUNG_MFR = 0x022E + if device.zwave_manufacturer_id == SAMSUNG_MFR then + local subdriver = require("legacy-handlers.samsung-lock") + return true, subdriver + end + return false +end diff --git a/drivers/SmartThings/zwave-lock/src/legacy-handlers/samsung-lock/init.lua b/drivers/SmartThings/zwave-lock/src/legacy-handlers/samsung-lock/init.lua new file mode 100644 index 0000000000..6e28668b0b --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/legacy-handlers/samsung-lock/init.lua @@ -0,0 +1,96 @@ +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local capabilities = require "st.capabilities" +local cc = require "st.zwave.CommandClass" + +local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) +local UserCode = (require "st.zwave.CommandClass.UserCode")({version=1}) +local access_control_event = Notification.event.access_control + +local json = require "dkjson" +local constants = require "st.zwave.constants" + +local LockDefaults = require "st.zwave.defaults.lock" +local LockCodesDefaults = require "st.zwave.defaults.lockCodes" +local get_lock_codes = LockCodesDefaults.get_lock_codes +local clear_code_state = LockCodesDefaults.clear_code_state +local code_deleted = LockCodesDefaults.code_deleted + + +local function get_ongoing_code_set(device) + local code_id + local code_state = device:get_field(constants.CODE_STATE) + if code_state ~= nil then + for key, state in pairs(code_state) do + if state ~= nil then + code_id = key:match("setName(%d)") + end + end + end + return code_id +end + +local function notification_report_handler(self, device, cmd) + local event + if (cmd.args.notification_type == Notification.notification_type.ACCESS_CONTROL) then + local event_code = cmd.args.event + if event_code == access_control_event.AUTO_LOCK_NOT_FULLY_LOCKED_OPERATION then + event = capabilities.lock.lock.unlocked() + elseif event_code == access_control_event.NEW_USER_CODE_ADDED then + local code_id = get_ongoing_code_set(device) + if code_id ~= nil then + device:send(UserCode:Get({user_identifier = code_id})) + return + end + elseif event_code == access_control_event.NEW_USER_CODE_NOT_ADDED_DUE_TO_DUPLICATE_CODE then + local code_id = get_ongoing_code_set(device) + if code_id ~= nil then + event = capabilities.lockCodes.codeChanged(code_id .. " failed", { state_change = true }) + clear_code_state(device, code_id) + end + elseif event_code == access_control_event.NEW_PROGRAM_CODE_ENTERED_UNIQUE_CODE_FOR_LOCK_CONFIGURATION then + -- Update Master Code in the same way as in defaults... + LockCodesDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, cmd) + -- ...and delete rest of them, as lock does + local lock_codes = get_lock_codes(device) + for code_id, _ in pairs(lock_codes) do + if code_id ~= "0" then + code_deleted(device, code_id) + end + end + event = capabilities.lockCodes.lockCodes(json.encode(get_lock_codes(device)), { visibility = { displayed = false } }) + end + end + + if event ~= nil then + device:emit_event(event) + else + LockDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, cmd) + LockCodesDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, cmd) + end +end + +-- Used doConfigure instead of added to not overwrite parent driver's added_handler +local function do_configure(self, device) + -- taken directly from DTH + -- Samsung locks won't allow you to enter the pairing menu when locked, so it must be unlocked + device:emit_event(capabilities.lock.lock.unlocked()) + device:emit_event(capabilities.lockCodes.lockCodes(json.encode({["0"] = "Master Code"} ), { visibility = { displayed = false } })) +end + +local samsung_lock = { + zwave_handlers = { + [cc.NOTIFICATION] = { + [Notification.REPORT] = notification_report_handler + } + }, + lifecycle_handlers = { + doConfigure = do_configure + }, + NAME = "Samsung Lock", + can_handle = require("legacy-handlers.samsung-lock.can_handle"), +} + +return samsung_lock diff --git a/drivers/SmartThings/zwave-lock/src/legacy-handlers/schlage-lock/can_handle.lua b/drivers/SmartThings/zwave-lock/src/legacy-handlers/schlage-lock/can_handle.lua new file mode 100644 index 0000000000..e45491efe2 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/legacy-handlers/schlage-lock/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, cmd) + local SCHLAGE_MFR = 0x003B + if device.zwave_manufacturer_id == SCHLAGE_MFR then + local subdriver = require("legacy-handlers.schlage-lock") + return true, subdriver + end + return false +end diff --git a/drivers/SmartThings/zwave-lock/src/legacy-handlers/schlage-lock/init.lua b/drivers/SmartThings/zwave-lock/src/legacy-handlers/schlage-lock/init.lua new file mode 100644 index 0000000000..a6604b01f5 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/legacy-handlers/schlage-lock/init.lua @@ -0,0 +1,178 @@ +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local capabilities = require "st.capabilities" +local cc = require "st.zwave.CommandClass" +local constants = require "st.zwave.constants" +local json = require "dkjson" + +local UserCode = (require "st.zwave.CommandClass.UserCode")({version=1}) +local user_id_status = UserCode.user_id_status +local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) +local access_control_event = Notification.event.access_control +local Configuration = (require "st.zwave.CommandClass.Configuration")({version=2}) +local Basic = (require "st.zwave.CommandClass.Basic")({version=1}) +local Association = (require "st.zwave.CommandClass.Association")({version=1}) + +local LockCodesDefaults = require "st.zwave.defaults.lockCodes" + +local SCHLAGE_LOCK_CODE_LENGTH_PARAM = {number = 16, size = 1} + +local DEFAULT_COMMANDS_DELAY = 4.2 -- seconds + +local function set_code_length(self, device, cmd) + local length = cmd.args.length + if length >= 4 and length <= 8 then + device:send(Configuration:Set({ + parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number, + configuration_value = length, + size = SCHLAGE_LOCK_CODE_LENGTH_PARAM.size + })) + end +end + +local function reload_all_codes(self, device, cmd) + LockCodesDefaults.capability_handlers[capabilities.lockCodes.commands.reloadAllCodes](self, device, cmd) + local current_code_length = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.codeLength.NAME) + if current_code_length == nil then + device:send(Configuration:Get({parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number})) + end +end + +local function set_code(self, device, cmd) + if (cmd.args.codePIN == "") then + self:inject_capability_command(device, { + capability = capabilities.lockCodes.ID, + command = capabilities.lockCodes.commands.nameSlot.NAME, + args = {cmd.args.codeSlot, cmd.args.codeName}, + }) + else + -- copied from defaults with additional check for Schlage's configuration + if (cmd.args.codeName ~= nil and cmd.args.codeName ~= "") then + if (device:get_field(constants.CODE_STATE) == nil) then device:set_field(constants.CODE_STATE, { persist = true }) end + local code_state = device:get_field(constants.CODE_STATE) + code_state["setName"..cmd.args.codeSlot] = cmd.args.codeName + device:set_field(constants.CODE_STATE, code_state, { persist = true }) + end + local send_set_user_code = function () + device:send(UserCode:Set({ + user_identifier = cmd.args.codeSlot, + user_code = cmd.args.codePIN, + user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS}) + ) + end + local current_code_length = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.codeLength.NAME) + if current_code_length == nil then + device:send(Configuration:Get({parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number})) + device.thread:call_with_delay(DEFAULT_COMMANDS_DELAY, send_set_user_code) + else + send_set_user_code() + end + end +end + +local function do_configure(self, device) + device:send(Configuration:Get({parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number})) + device:send(Association:Set({grouping_identifier = 2, node_ids = {self.environment_info.hub_zwave_id}})) +end + +local function basic_set_handler(self, device, cmd) + device:emit_event(cmd.args.value == 0 and capabilities.lock.lock.unlocked() or capabilities.lock.lock.locked()) + device:send(Association:Remove({grouping_identifier = 1, node_ids = {self.environment_info.hub_zwave_id}})) +end + +local function configuration_report(self, device, cmd) + local parameter_number = cmd.args.parameter_number + if parameter_number == SCHLAGE_LOCK_CODE_LENGTH_PARAM.number then + local reported_code_length = cmd.args.configuration_value + local current_code_length = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.codeLength.NAME) + if current_code_length ~= nil and current_code_length ~= reported_code_length then + local all_codes_deleted_mocked_command = Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = access_control_event.ALL_USER_CODES_DELETED + }) + LockCodesDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, all_codes_deleted_mocked_command) + end + device:emit_event(capabilities.lockCodes.codeLength(reported_code_length)) + end +end + +local function is_user_code_report_mfr_specific(device, cmd) + local reported_user_id_status = cmd.args.user_id_status + local user_code = cmd.args.user_code + local code_id = cmd.args.user_identifier + + if reported_user_id_status == user_id_status.ENABLED_GRANT_ACCESS or -- OCCUPIED in UserCodeV1 + (reported_user_id_status == user_id_status.STATUS_NOT_AVAILABLE and user_code ~= nil) then + local code_state = device:get_field(constants.CODE_STATE) + return user_code == "**********" or user_code == nil or (code_state ~= nil and code_state["setName"..cmd.args.user_identifier] ~= nil) + else + return (code_id == 0 and reported_user_id_status == user_id_status.AVAILABLE) or + reported_user_id_status == user_id_status.STATUS_NOT_AVAILABLE + end +end + +local function user_code_report_handler(self, device, cmd) + local code_id = cmd.args.user_identifier + if is_user_code_report_mfr_specific(device, cmd) then + local reported_user_id_status = cmd.args.user_id_status + local user_code = cmd.args.user_code + local event + + if reported_user_id_status == user_id_status.ENABLED_GRANT_ACCESS or -- OCCUPIED in UserCodeV1 + (reported_user_id_status == user_id_status.STATUS_NOT_AVAILABLE and user_code ~= nil) then + local code_name = LockCodesDefaults.get_code_name(device, code_id) + local change_type = LockCodesDefaults.get_change_type(device, code_id) + event = capabilities.lockCodes.codeChanged(code_id..""..change_type, { state_change = true }) + event.data = {codeName = code_name} + if code_id ~= 0 then -- ~= MASTER_CODE + LockCodesDefaults.code_set_event(device, code_id, code_name) + end + elseif code_id == 0 and reported_user_id_status == user_id_status.AVAILABLE then + local lock_codes = LockCodesDefaults.get_lock_codes(device) + for _code_id, _ in pairs(lock_codes) do + LockCodesDefaults.code_deleted(device, _code_id) + end + device:emit_event(capabilities.lockCodes.lockCodes(json.encode(LockCodesDefaults.get_lock_codes(device)), { visibility = { displayed = false } })) + else -- user_id_status.STATUS_NOT_AVAILABLE + event = capabilities.lockCodes.codeChanged(code_id.." failed", { state_change = true }) + end + + if event ~= nil then + device:emit_event(event) + end + LockCodesDefaults.clear_code_state(device, code_id) + LockCodesDefaults.verify_set_code_completion(device, cmd, code_id) + else + LockCodesDefaults.zwave_handlers[cc.USER_CODE][UserCode.REPORT](self, device, cmd) + end +end + +local schlage_lock = { + capability_handlers = { + [capabilities.lockCodes.ID] = { + [capabilities.lockCodes.commands.setCodeLength.NAME] = set_code_length, + [capabilities.lockCodes.commands.reloadAllCodes.NAME] = reload_all_codes, + [capabilities.lockCodes.commands.setCode.NAME] = set_code + } + }, + zwave_handlers = { + [cc.USER_CODE] = { + [UserCode.REPORT] = user_code_report_handler + }, + [cc.CONFIGURATION] = { + [Configuration.REPORT] = configuration_report + }, + [cc.BASIC] = { + [Basic.SET] = basic_set_handler + } + }, + lifecycle_handlers = { + doConfigure = do_configure, + }, + NAME = "Schlage Lock", + can_handle = require("legacy-handlers.schlage-lock.can_handle"), +} + +return schlage_lock diff --git a/drivers/SmartThings/zwave-lock/src/legacy-handlers/sub_drivers.lua b/drivers/SmartThings/zwave-lock/src/legacy-handlers/sub_drivers.lua new file mode 100644 index 0000000000..4c94ca5559 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/legacy-handlers/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("legacy-handlers.zwave-alarm-v1-lock"), + lazy_load_if_possible("legacy-handlers.schlage-lock"), + lazy_load_if_possible("legacy-handlers.samsung-lock"), + lazy_load_if_possible("legacy-handlers.keywe-lock"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-lock/src/legacy-handlers/zwave-alarm-v1-lock/can_handle.lua b/drivers/SmartThings/zwave-lock/src/legacy-handlers/zwave-alarm-v1-lock/can_handle.lua new file mode 100644 index 0000000000..324f83e6f9 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/legacy-handlers/zwave-alarm-v1-lock/can_handle.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, cmd) + if opts.dispatcher_class == "ZwaveDispatcher" and cmd ~= nil and cmd.version ~= nil and cmd.version == 1 then + local subdriver = require("legacy-handlers.zwave-alarm-v1-lock") + return true, subdriver + end + return false +end diff --git a/drivers/SmartThings/zwave-lock/src/legacy-handlers/zwave-alarm-v1-lock/init.lua b/drivers/SmartThings/zwave-lock/src/legacy-handlers/zwave-alarm-v1-lock/init.lua new file mode 100644 index 0000000000..5865091852 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/legacy-handlers/zwave-alarm-v1-lock/init.lua @@ -0,0 +1,146 @@ +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local capabilities = require "st.capabilities" +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +--- @type st.zwave.CommandClass.Alarm +local Alarm = (require "st.zwave.CommandClass.Alarm")({ version = 1 }) +--- @type st.zwave.CommandClass.Battery +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +--- @type st.zwave.defaults.lockCodes +local lock_code_defaults = require "st.zwave.defaults.lockCodes" +local json = require "dkjson" + +local METHOD = { + KEYPAD = "keypad", + MANUAL = "manual", + COMMAND = "command", + AUTO = "auto" +} + +--- Default handler for alarm command class reports, these were largely OEM-defined +--- +--- This converts alarm V1 reports to correct lock events +--- +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param cmd st.zwave.CommandClass.Alarm.Report +local function alarm_report_handler(driver, device, cmd) + local alarm_type = cmd.args.alarm_type + local event = nil + local lock_codes = lock_code_defaults.get_lock_codes(device) + local code_id = nil + if (cmd.args.alarm_level ~= nil) then + code_id = tostring(cmd.args.alarm_level) + end + if (alarm_type == 9 or alarm_type == 17) then + event = capabilities.lock.lock.unknown() + elseif (alarm_type == 16 or alarm_type == 19) then + event = capabilities.lock.lock.unlocked() + if (device:supports_capability(capabilities.lockCodes) and code_id ~= nil) then + local code_name = lock_code_defaults.get_code_name(device, code_id) + event.data = {codeId = code_id, codeName = code_name, method = METHOD.KEYPAD} + end + elseif (alarm_type == 18) then + event = capabilities.lock.lock.locked() + if (device:supports_capability(capabilities.lockCodes) and code_id ~= nil) then + local code_name = lock_code_defaults.get_code_name(device, code_id) + event.data = {codeId = code_id, codeName = code_name, method = METHOD.KEYPAD} + end + elseif (alarm_type == 21) then + event = capabilities.lock.lock.locked() + if (cmd.args.alarm_level == 2) then + event["data"] = {method = METHOD.MANUAL} + else + event["data"] = {method = METHOD.KEYPAD} + end + elseif (alarm_type == 22) then + event = capabilities.lock.lock.unlocked() + event["data"] = {method = METHOD.MANUAL} + elseif (alarm_type == 23) then + event = capabilities.lock.lock.unknown() + event["data"] = {method = METHOD.COMMAND} + elseif (alarm_type == 24) then + event = capabilities.lock.lock.locked() + event["data"] = {method = METHOD.COMMAND} + elseif (alarm_type == 25) then + event = capabilities.lock.lock.unlocked() + event["data"] = {method = METHOD.COMMAND} + elseif (alarm_type == 26) then + event = capabilities.lock.lock.unknown() + event["data"] = {method = METHOD.AUTO} + elseif (alarm_type == 27) then + event = capabilities.lock.lock.locked() + event["data"] = {method = METHOD.AUTO} + elseif (alarm_type == 32) then + -- all user codes deleted + for code_id, _ in pairs(lock_codes) do + lock_code_defaults.code_deleted(device, code_id) + end + device:emit_event(capabilities.lockCodes.lockCodes(json.encode(lock_code_defaults.get_lock_codes(device)), { visibility = { displayed = false } })) + elseif (alarm_type == 33) then + -- user code deleted + if (code_id ~= nil) then + lock_code_defaults.clear_code_state(device, code_id) + if (lock_codes[code_id] ~= nil) then + lock_code_defaults.code_deleted(device, code_id) + device:emit_event(capabilities.lockCodes.lockCodes(json.encode(lock_code_defaults.get_lock_codes(device)), { visibility = { displayed = false } })) + end + end + elseif (alarm_type == 13 or alarm_type == 112) then + -- user code changed/set + if (code_id ~= nil) then + local code_name = lock_code_defaults.get_code_name(device, code_id) + local change_type = lock_code_defaults.get_change_type(device, code_id) + local code_changed_event = capabilities.lockCodes.codeChanged(code_id .. change_type, { state_change = true }) + code_changed_event["data"] = { codeName = code_name} + lock_code_defaults.code_set_event(device, code_id, code_name) + lock_code_defaults.clear_code_state(device, code_id) + device:emit_event(code_changed_event) + end + elseif (alarm_type == 34 or alarm_type == 113) then + -- duplicate lock code + if (code_id ~= nil) then + local code_changed_event = capabilities.lockCodes.codeChanged(code_id .. lock_code_defaults.CHANGE_TYPE.FAILED, { state_change = true }) + lock_code_defaults.clear_code_state(device, code_id) + device:emit_event(code_changed_event) + end + elseif (alarm_type == 130) then + -- batteries replaced + if (device:is_cc_supported(cc.BATTERY)) then + driver:call_with_delay(10, function(d) device:send(Battery:Get({})) end ) + end + elseif (alarm_type == 161) then + -- tamper alarm + event = capabilities.tamperAlert.tamper.detected() + elseif (alarm_type == 167) then + -- low battery + if (device:is_cc_supported(cc.BATTERY)) then + driver:call_with_delay(10, function(d) device:send(Battery:Get({})) end ) + end + elseif (alarm_type == 168) then + -- critical battery + event = capabilities.battery.battery(1) + elseif (alarm_type == 169) then + -- battery too low to operate + event = capabilities.battery.battery(0) + end + + if (event ~= nil) then + device:emit_event(event) + end +end + +local zwave_lock = { + zwave_handlers = { + [cc.ALARM] = { + [Alarm.REPORT] = alarm_report_handler + } + }, + NAME = "Z-Wave lock alarm V1", + can_handle = require("legacy-handlers.zwave-alarm-v1-lock.can_handle") +} + +return zwave_lock diff --git a/drivers/SmartThings/zwave-lock/src/lock_handlers/capabilities.lua b/drivers/SmartThings/zwave-lock/src/lock_handlers/capabilities.lua new file mode 100644 index 0000000000..13d45bc4dd --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/lock_handlers/capabilities.lua @@ -0,0 +1,252 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local lock_utils = require "lock_utils.utils" +local tables = require "lock_utils.tables" +local consts = require "lock_utils.constants" + +--- @type st.zwave.CommandClass.DoorLock +local DoorLock = (require "st.zwave.CommandClass.DoorLock")({ version = 1 }) +--- @type st.zwave.CommandClass.Battery +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +--- @type st.zwave.CommandClass.UserCode +local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) + + +local CapabilityHandlers = {} + + +-- [[ LOCK USERS CAPABILITY COMMANDS ]] -- + +function CapabilityHandlers.add_user(driver, device, command) + if lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.ADD, consts.COMMAND_RESULT.BUSY) + return + end + + -- Find the smallest positive userIndex not already in the table + local next_available_index = tables.next_index(device, "users") + local status = tables.add_entry(device, "users", { + userIndex = next_available_index, + userName = command.args.userName, + userType = command.args.userType, + }) + local additional_info = status == consts.COMMAND_RESULT.SUCCESS and { userIndex = next_available_index } + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.ADD, status, additional_info) +end + +function CapabilityHandlers.update_user(driver, device, command) + if lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.UPDATE, consts.COMMAND_RESULT.BUSY) + return + end + + local result_status + if not tables.find_entry(device, "users", command.args.userIndex) then + result_status = tables.add_entry(device, "users", { + userIndex = command.args.userIndex, + userName = command.args.userName, + userType = command.args.userType, + }) + else + result_status = tables.update_entry(device, "users", + command.args.userIndex, + { userName = command.args.userName, userType = command.args.userType } + ) + end + + local additional_info = result_status == consts.COMMAND_RESULT.SUCCESS and { userIndex = command.args.userIndex } + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.UPDATE, result_status, additional_info) +end + +function CapabilityHandlers.delete_user(driver, device, command) + if lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.DELETE, consts.COMMAND_RESULT.BUSY) + return + end + + local associated_credentials = tables.find_all_entries_by(device, "credentials", "userIndex", command.args.userIndex) + for _, associated_credential in ipairs(associated_credentials) do + -- Set busy state with the full user+credential context BEFORE injecting. + -- Injected capability commands are schema-validated, so extra args like userIndex + -- would be stripped. By setting device fields here we preserve the full context. + lock_utils.set_busy_state(device, consts.LOCK_USERS.DELETE, { + userIndex = command.args.userIndex, + credentialIndex = associated_credential.credentialIndex, + credentialType = consts.CRED_TYPE_PIN, + }) + driver:inject_capability_command(device, { + capability = capabilities.lockCredentials.ID, + command = capabilities.lockCredentials.commands.deleteCredential.NAME, + args = { + credentialIndex = associated_credential.credentialIndex, + credentialType = consts.CRED_TYPE_PIN, + } + }) + end + if #associated_credentials == 0 then + -- No associated credentials: delete the user entry directly and report the result + local status = tables.delete_entry(device, "users", command.args.userIndex) + local additional_info = status == consts.COMMAND_RESULT.SUCCESS and { userIndex = command.args.userIndex } + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.DELETE, status, additional_info) + end +end + +function CapabilityHandlers.delete_all_users(driver, device, command) + if lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.DELETE_ALL, consts.COMMAND_RESULT.BUSY) + return + end + -- Set busy state with DELETE_ALL context BEFORE injecting so the response handler + -- knows to clear both tables and emit results for both capabilities. + lock_utils.set_busy_state(device, consts.LOCK_USERS.DELETE_ALL, {}) + driver:inject_capability_command(device, { + capability = capabilities.lockCredentials.ID, + command = capabilities.lockCredentials.commands.deleteAllCredentials.NAME, + args = { credentialType = consts.CRED_TYPE_PIN } + }) +end + + +-- [[ LOCK CREDENTIALS CAPABILITY COMMANDS ]] -- + +function CapabilityHandlers.add_credential(driver, device, command) + if lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.ADD, consts.COMMAND_RESULT.BUSY) + return + end + + -- A userIndex of 0 means "auto-assign the next available slot" + local user_index = command.args.userIndex == 0 and tables.next_index(device, "users") or command.args.userIndex + local cred_index = command.args.userIndex == 0 and tables.next_index(device, "credentials") or command.args.userIndex + + if #(tables.get_state(device, "credentials") or {}) >= tables.get_max_entries(device, "credentials") then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.ADD, consts.COMMAND_RESULT.RESOURCE_EXHAUSTED) + return + end + + -- Set busy state and attempt to set the PIN on-device. + lock_utils.set_busy_state(device, consts.LOCK_CREDENTIALS.ADD, { + userIndex = user_index, + credentialIndex = cred_index, + credentialType = command.args.credentialType, + credentialName = command.args.credentialName, + }) + device:send(UserCode:Set({ + user_identifier = cred_index, + user_code = command.args.credentialData, + user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS + })) +end + +function CapabilityHandlers.update_credential(driver, device, command) + if lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.UPDATE, consts.COMMAND_RESULT.BUSY) + return + end + + if not tables.find_entry(device, "credentials", command.args.credentialIndex) then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.UPDATE, consts.COMMAND_RESULT.FAILURE) + return + end + + lock_utils.set_busy_state(device, consts.LOCK_CREDENTIALS.UPDATE, { + userIndex = command.args.userIndex, + credentialIndex = command.args.credentialIndex, + credentialType = command.args.credentialType, + credentialName = command.args.credentialName, + }) + device:send(UserCode:Set({ + user_identifier = command.args.credentialIndex, + user_code = command.args.credentialData, + user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS + })) +end + +function CapabilityHandlers.delete_credential(driver, device, command) + local cmd_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + + if cmd_in_progress == consts.LOCK_USERS.DELETE then + -- Injected by deleteUser; busy state was already set with the full LOCK_USERS.DELETE context. + local credential_args = device:get_field(consts.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE) or {} + device:send(UserCode:Set({ + user_identifier = credential_args.credentialIndex, + user_id_status = UserCode.user_id_status.AVAILABLE + })) + elseif lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE, consts.COMMAND_RESULT.BUSY) + else + -- Standalone deleteCredential: look up the credential to obtain its associated userIndex. + local found_cred = tables.find_entry(device, "credentials", command.args.credentialIndex) + if not found_cred then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE, consts.COMMAND_RESULT.FAILURE) + return + end + lock_utils.set_busy_state(device, consts.LOCK_CREDENTIALS.DELETE, { + credentialIndex = command.args.credentialIndex, + credentialType = command.args.credentialType, + userIndex = found_cred.userIndex, + }) + device:send(UserCode:Set({ + user_identifier = command.args.credentialIndex, + user_id_status = UserCode.user_id_status.AVAILABLE + })) + end +end + +function CapabilityHandlers.delete_all_credentials(driver, device, command) + local cmd_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + + local send_clear_commands = function() + local credentials = tables.get_state(device, "credentials") or {} + local delay = 0 + for _, credential in ipairs(credentials) do + device.thread:call_with_delay(delay, function() + device:send(UserCode:Set({ + user_identifier = credential.credentialIndex, + user_id_status = UserCode.user_id_status.AVAILABLE + })) + end) + delay = delay + 2 + end + -- Delayed cleanup: ensure all tables are cleared after individual responses complete + device.thread:call_with_delay(delay + 4, function() + if cmd_in_progress == consts.LOCK_USERS.DELETE_ALL then + tables.delete_all_entries(device, "users") + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.DELETE_ALL, consts.COMMAND_RESULT.SUCCESS) + end + tables.delete_all_entries(device, "credentials") + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE_ALL, consts.COMMAND_RESULT.SUCCESS) + lock_utils.clear_busy_state(device) + end) + end + + if cmd_in_progress == consts.LOCK_USERS.DELETE_ALL then + -- Injected by deleteAllUsers; busy state was already set with LOCK_USERS.DELETE_ALL context. + send_clear_commands() + elseif lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE_ALL, consts.COMMAND_RESULT.BUSY) + else + lock_utils.set_busy_state(device, consts.LOCK_CREDENTIALS.DELETE_ALL, command.args) + send_clear_commands() + end +end + + +-- [[ REFRESH CAPABILITY COMMANDS ]] -- + +function CapabilityHandlers.refresh(driver, device, cmd) + device:send(DoorLock:OperationGet({})) + device:send(Battery:Get({})) + + if device:supports_capability(capabilities.lockCredentials) then + -- If we are missing the cached values for these attributes, read them so we can properly manage them locally + if (device:get_latest_state("main", capabilities.lockCredentials.ID, capabilities.lockCredentials.pinUsersSupported.NAME) == nil) or + (device:get_latest_state("main", capabilities.lockUsers.ID, capabilities.lockUsers.totalUsersSupported.NAME) == nil) then + device:send(UserCode:UsersNumberGet({})) + end + end +end + +return CapabilityHandlers diff --git a/drivers/SmartThings/zwave-lock/src/lock_handlers/zwave_responses.lua b/drivers/SmartThings/zwave-lock/src/lock_handlers/zwave_responses.lua new file mode 100644 index 0000000000..cfabe41932 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/lock_handlers/zwave_responses.lua @@ -0,0 +1,243 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local socket = require "cosock.socket" + +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.UserCode +local UserCode = (require "st.zwave.CommandClass.UserCode")({version=1}) +local TamperDefaults = require "st.zwave.defaults.tamperAlert" + +local consts = require "lock_utils.constants" +local lock_utils = require "lock_utils.utils" +local tables = require "lock_utils.tables" + + +local ZwaveHandlers = {} + + +-- [[ USER CODE COMMAND CLASS ]] + +function ZwaveHandlers.user_code_report(driver, device, cmd) + -- zw report values + local credential_index = cmd.args.user_identifier + local user_id_status = cmd.args.user_id_status + + -- cached values from capability command + local command_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + + -- Determine boolean states of user code slot + local user_code_occupied = user_id_status == UserCode.user_id_status.ENABLED_GRANT_ACCESS or + (user_id_status == UserCode.user_id_status.STATUS_NOT_AVAILABLE and cmd.args.user_code) + local user_code_available = user_id_status == UserCode.user_id_status.AVAILABLE + + if command_in_progress == consts.SYNC.CODES_FROM_LOCK then + if user_code_occupied then + -- If an entry already exists at this index, this will be a no-op. + tables.add_entry(device, "users", { + userIndex = credential_index, + userName = "Guest " .. credential_index, + userType = "guest", + }) + tables.add_entry(device, "credentials", { + userIndex = credential_index, + credentialIndex = credential_index, + credentialType = consts.CRED_TYPE_PIN, + credentialName = "Guest " .. credential_index, + }) + -- reset the consecutive unoccupied codes counter since we found an occupied code + device:set_field(consts.SYNC.CONSECUTIVE_UNOCCUPIED_CODES, 0) + elseif user_code_available then + local consecutive_unoccupied_codes = (device:get_field(consts.SYNC.CONSECUTIVE_UNOCCUPIED_CODES) or 0) + 1 + device:set_field(consts.SYNC.CONSECUTIVE_UNOCCUPIED_CODES, consecutive_unoccupied_codes) + end + + -- Sync: continue to next code or finish + if credential_index >= tables.get_max_entries(device, "credentials") or + (device:get_field(consts.SYNC.CONSECUTIVE_UNOCCUPIED_CODES) or 0) > 5 then + -- stop if we hit the max number of codes supported by the lock, or if we have received 5 consecutive unoccupied codes. + device:set_field(consts.SYNC.CODE_INDEX, nil) + lock_utils.clear_busy_state(device) + else + local synced_code_index = device:get_field(consts.SYNC.CODE_INDEX) + 1 + device:set_field(consts.SYNC.CODE_INDEX, synced_code_index) + lock_utils.set_busy_state(device, consts.SYNC.CODES_FROM_LOCK, { checkingCode = synced_code_index }) + device:send(UserCode:Get({user_identifier = synced_code_index})) + end + return + elseif user_code_occupied then + -- Code slot is now occupied: add or update credential and associated user + lock_utils.set_credential_report_helper(device, credential_index) + elseif user_code_available then + if command_in_progress == consts.LOCK_CREDENTIALS.ADD then + lock_utils.emit_command_result(device, capabilities.lockCredentials, + consts.LOCK_CREDENTIALS.ADD, consts.COMMAND_RESULT.FAILURE) + lock_utils.clear_busy_state(device) + elseif command_in_progress == consts.LOCK_CREDENTIALS.DELETE or tables.find_entry(device, "credentials", credential_index) then + lock_utils.delete_credential_report_helper(device, credential_index) + end + end +end + +function ZwaveHandlers.users_number_report(driver, device, cmd) + device:emit_event(capabilities.lockUsers.totalUsersSupported( + cmd.args.supported_users, { state_change = true, visibility = { displayed = false } })) + device:emit_event(capabilities.lockCredentials.pinUsersSupported( + cmd.args.supported_users, { state_change = true, visibility = { displayed = false } })) +end + + +-- [[ NOTIFICATION COMMAND CLASS ]] -- + +function ZwaveHandlers.user_code_event_handler(driver, device, cmd) + if cmd.args.notification_type ~= Notification.notification_type.ACCESS_CONTROL then + return + end + -- zw event values + local event = cmd.args.event + local credential_index = tonumber(lock_utils.get_code_id_from_notification_event( + cmd.args.event_parameter, cmd.args.v1_alarm_level)) + -- cached value from capability event, if applicable + local command_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + -- This type includes many lock-related events, way too many to list, + -- including user code changes and door operations + local access_control_event = Notification.event.access_control + + if event == access_control_event.ALL_USER_CODES_DELETED then + tables.delete_all_entries(device, "credentials") + tables.delete_all_entries(device, "users") + + elseif event == access_control_event.SINGLE_USER_CODE_DELETED then + lock_utils.delete_credential_report_helper(device, credential_index) + + elseif event == access_control_event.NEW_USER_CODE_ADDED then + lock_utils.set_credential_report_helper(device, credential_index) + + elseif event == access_control_event.NEW_PROGRAM_CODE_ENTERED_UNIQUE_CODE_FOR_LOCK_CONFIGURATION then + -- aka "master code" changed + if command_in_progress == consts.LOCK_CREDENTIALS.UPDATE then + lock_utils.emit_command_result(device, capabilities.lockCredentials, + consts.LOCK_CREDENTIALS.UPDATE, consts.COMMAND_RESULT.SUCCESS) + lock_utils.clear_busy_state(device) + end + + elseif event == access_control_event.NEW_USER_CODE_NOT_ADDED_DUE_TO_DUPLICATE_CODE then + if command_in_progress then + lock_utils.emit_command_result(device, capabilities.lockCredentials, + consts.LOCK_CREDENTIALS.ADD, consts.COMMAND_RESULT.DUPLICATE) + lock_utils.clear_busy_state(device) + end + end +end + +function ZwaveHandlers.door_operation_event_handler(driver, device, cmd) + if cmd.args.notification_type ~= Notification.notification_type.ACCESS_CONTROL then + return + end + -- zw event value + local event = cmd.args.event + -- This type includes many lock-related events, way too many to list, + -- including user code changes and door operations + local access_control_event = Notification.event.access_control + -- send lock, unlock, or unknown event based on the event coded + local capability_event + if not (event >= access_control_event.MANUAL_LOCK_OPERATION and event <= access_control_event.LOCK_JAMMED) then + return -- This is the subset of event kinds that we care about for door operation reporting + elseif ((event >= access_control_event.MANUAL_LOCK_OPERATION and + event <= access_control_event.KEYPAD_UNLOCK_OPERATION) or + event == access_control_event.AUTO_LOCK_LOCKED_OPERATION) then + -- even event codes are unlocks, odd event codes are locks + local events = {[0] = capabilities.lock.lock.unlocked(), [1] = capabilities.lock.lock.locked()} + capability_event = events[event & 1] + elseif (event >= access_control_event.MANUAL_NOT_FULLY_LOCKED_OPERATION and + event <= access_control_event.LOCK_JAMMED) then + capability_event = capabilities.lock.lock.unknown() + else + return -- no lock event to send for this code + end + + local access_control_event_capability_map = { + [access_control_event.MANUAL_UNLOCK_OPERATION] = "manual", + [access_control_event.MANUAL_LOCK_OPERATION] = "manual", + [access_control_event.MANUAL_NOT_FULLY_LOCKED_OPERATION] = "manual", + [access_control_event.RF_LOCK_OPERATION] = "command", + [access_control_event.RF_UNLOCK_OPERATION] = "command", + [access_control_event.RF_NOT_FULLY_LOCKED_OPERATION] = "command", + [access_control_event.KEYPAD_LOCK_OPERATION] = "keypad", + [access_control_event.KEYPAD_UNLOCK_OPERATION] = "keypad", + [access_control_event.AUTO_LOCK_LOCKED_OPERATION] = "auto", + [access_control_event.AUTO_LOCK_NOT_FULLY_LOCKED_OPERATION] = "auto" + } + + capability_event.data = {} + capability_event.data.method = access_control_event_capability_map[event] + + if (event == access_control_event.MANUAL_UNLOCK_OPERATION and cmd.args.event_parameter == 2) then + capability_event.data.method = "keypad" -- some locks can distinguish being manually locked via keypad + elseif (event == access_control_event.KEYPAD_LOCK_OPERATION or event == access_control_event.KEYPAD_UNLOCK_OPERATION) then + local code_id = tonumber(lock_utils.get_code_id_from_notification_event( + cmd.args.event_parameter, cmd.args.v1_alarm_level)) -- Look up stored lockUsers data if applicable + if device:supports_capability(capabilities.lockUsers) then + local credential = tables.find_entry(device, "credentials", code_id) + if credential then + local user = tables.find_entry(device, "users", credential.userIndex) + capability_event.data.userIndex = credential.userIndex + if user then + capability_event.data.userName = user.userName + capability_event.data.userType = user.userType + end + else + capability_event.data.userIndex = code_id + end + end + end + + -- Delay timer logic to handle duplicate lock state reports + if device:get_latest_state( + "main", + capabilities.lock.ID, + capabilities.lock.lock.ID) == capability_event.value.value then + local preceding_event_time = device:get_field(consts.DELAY_LOCK_EVENT) or 0 + local time_diff = socket.gettime() - preceding_event_time + if time_diff < consts.MAX_DELAY then + device:set_field(consts.DELAY_LOCK_EVENT, time_diff) + end + end + + local timer = device:get_field(consts.DELAY_LOCK_EVENT_TIMER) + if timer ~= nil then + device.thread:cancel_timer(timer) + device:set_field(consts.DELAY_LOCK_EVENT_TIMER, nil) + end + + device:emit_event(capability_event) +end + +function ZwaveHandlers.notification_report(driver, device, cmd) + ZwaveHandlers.user_code_event_handler(driver, device, cmd) + ZwaveHandlers.door_operation_event_handler(driver, device, cmd) + -- Tamper events handled by default tamper handler + TamperDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](driver, device, cmd) +end + + +-- [[ TIME COMMAND CLASS ]] -- + +function ZwaveHandlers.time_get_handler(driver, device, cmd) + local Time = (require "st.zwave.CommandClass.Time")({ version = 1 }) + local time = os.date("*t") + device:send_to_component( + Time:Report({ + hour_local_time = time.hour, + minute_local_time = time.min, + second_local_time = time.sec + }), + device:endpoint_to_component(cmd.src_channel) + ) +end + +return ZwaveHandlers diff --git a/drivers/SmartThings/zwave-lock/src/lock_utils/constants.lua b/drivers/SmartThings/zwave-lock/src/lock_utils/constants.lua new file mode 100644 index 0000000000..f66c452beb --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/lock_utils/constants.lua @@ -0,0 +1,48 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lock_constants = {} + +lock_constants.DRIVER_STATE = { + BUSY = "busy", + COMMAND_IN_PROGRESS = "commandInProgress", + CREDENTIAL_ARGS_IN_USE = "currentCredential", + SLGA_MIGRATED = "slgaMigrated", +} + +lock_constants.SYNC = { + CODES_FROM_LOCK = "syncCodesFromLock", + CODE_INDEX = "syncCodeIndex", + CONSECUTIVE_UNOCCUPIED_CODES = "consecutiveUnoccupiedCodes", +} + +lock_constants.COMMAND_RESULT = { + SUCCESS = "success", + FAILURE = "failure", + DUPLICATE = "duplicate", + OCCUPIED = "occupied", + INVALID_COMMAND = "invalidCommand", + RESOURCE_EXHAUSTED = "resourceExhausted", + BUSY = "busy" +} + +lock_constants.LOCK_CREDENTIALS = { + ADD = "addCredential", + UPDATE = "updateCredential", + DELETE = "deleteCredential", + DELETE_ALL = "deleteAllCredentials" +} + +lock_constants.LOCK_USERS = { + ADD = "addUser", + UPDATE = "updateUser", + DELETE = "deleteUser", + DELETE_ALL = "deleteAllUsers" +} + +lock_constants.CRED_TYPE_PIN = "pin" +lock_constants.DELAY_LOCK_EVENT = "_delay_lock_event" +lock_constants.DELAY_LOCK_EVENT_TIMER = "_delay_lock_event_timer" +lock_constants.MAX_DELAY = 10 + +return lock_constants diff --git a/drivers/SmartThings/zwave-lock/src/lock_utils/tables.lua b/drivers/SmartThings/zwave-lock/src/lock_utils/tables.lua new file mode 100644 index 0000000000..bfe3562344 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/lock_utils/tables.lua @@ -0,0 +1,268 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local st_utils = require "st.utils" +local COMMAND_RESULT = require "lock_utils.constants".COMMAND_RESULT + +local table_utils = {} + +-- DEFS describes how each capability-backed state table is structured: +-- +-- capability SmartThings capability (used for device support checks and to get latest state) +-- attribute Capability attribute function (used to emit state) +-- max_entries Attribute of the capability that defines the maximum number of entries allowed in the table +-- match_key Key used to identify entries in flat tables +-- required_keys Keys that must be non-nil when adding an entry +-- persistent_field device:set_field key used to back up the table across restarts +-- + +local DEFS = { + users = { + capability = capabilities.lockUsers, + attribute = capabilities.lockUsers.users, + max_entries = capabilities.lockUsers.totalUsersSupported, + match_key = "userIndex", + required_keys = {"userIndex", "userType"}, + persistent_field = "persistedUsers", + }, + credentials = { + capability = capabilities.lockCredentials, + attribute = capabilities.lockCredentials.credentials, + max_entries = capabilities.lockCredentials.pinUsersSupported, + match_key = "credentialIndex", + required_keys = {"userIndex", "credentialIndex", "credentialType"}, + persistent_field = "persistedCredentials", + } +} + +-- Resolve a table name to its definition. Logs an error and returns nil if unknown. +local function resolve_table_def(device, table_name) + local def = DEFS[table_name] + if not def then + device.log.error(string.format("table_helpers: unknown table %q", table_name)) + end + return def +end + +-- Validate that an entry table contains all required keys. +local function validate_entry(device, entry, required_keys) + for _, key in ipairs(required_keys or {}) do + if entry[key] == nil then + device.log.error(string.format("table_helpers: entry missing required key %q", key)) + return false + end + end + return true +end + +-- Write the current table contents to the device's persistent field store so that +-- the state survives driver restarts and can be restored if the capability state +-- cache is wiped. +local function persist_table(device, def, data) + device:set_field(def.persistent_field, st_utils.deep_copy(data), { persist = true }) +end + +-- Read the current state for a table and return a deep-copied array. +-- Accepts either a string table name ("users", "credentials") or a DEFS entry directly. +-- Returns nil (with a warning) if the capability is unsupported by the device. +-- When the capability state cache has been wiped (get_latest_state returns nil), +-- falls back to the persistent field store so that callers always receive the +-- last-known state rather than an empty table. +--- @return table[] | nil +function table_utils.get_state(device, name_or_def) + local def = type(name_or_def) == "string" and resolve_table_def(device, name_or_def) or name_or_def + if not def then return nil end + if not device:supports_capability(def.capability, "main") then + device.log.warn(string.format( + "table_helpers: device does not support capability %q", def.capability.ID + )) + return + end + local state = device:get_latest_state("main", def.capability.ID, def.attribute.NAME) + if state ~= nil then + return st_utils.deep_copy(state) + end + -- Capability state cache is absent (e.g. after a hub reboot); fall back to the + -- persistent store so that callers see the last-known table contents. + return st_utils.deep_copy(device:get_field(def.persistent_field) or {}) +end + +-- Find an entry in a named table where the match_key equals value. +-- Returns the matching entry, or nil if not found. +function table_utils.find_entry(device, table_name, value) + local def = resolve_table_def(device, table_name) + if not def then return nil end + local t = table_utils.get_state(device, def) + if not t then return nil end + for _, entry in ipairs(t) do + if entry[def.match_key] == value then return entry end + end + return nil +end + +-- Find an entry in a named table where entry[key] equals value (arbitrary key search). +-- Returns the matching entry, or nil if not found. +function table_utils.find_entry_by(device, table_name, key, value) + local def = resolve_table_def(device, table_name) + if not def then return nil end + local t = table_utils.get_state(device, def) + if not t then return nil end + for _, entry in ipairs(t) do + if entry[key] == value then return entry end + end + return nil +end + +-- Find all entries in a named table where entry[key] equals value (arbitrary key search). +-- Returns an array of matching entries, or an empty array if none found. +function table_utils.find_all_entries_by(device, table_name, key, value) + local def = resolve_table_def(device, table_name) + if not def then return {} end + local t = table_utils.get_state(device, def) + if not t then return {} end + local matches = {} + for _, entry in ipairs(t) do + if entry[key] == value then table.insert(matches, entry) end + end + return matches +end + +-- Return the lowest positive integer not yet used as the match_key in the named table. +-- Used to auto-assign the next available slot for a new entry. +function table_utils.next_index(device, table_name) + local def = resolve_table_def(device, table_name) + if not def then return 1 end + local t = table_utils.get_state(device, def) or {} + local occupied = {} + for _, entry in ipairs(t) do occupied[entry[def.match_key]] = true end + local idx = 1 + while occupied[idx] do idx = idx + 1 end + return idx +end + +function table_utils.get_max_entries(device, table_name) + local def = resolve_table_def(device, table_name) + if not def then return end + return device:get_latest_state("main", def.capability.ID, def.max_entries.NAME, 20) -- arbitrary, default to 20 if the attribute is missing +end + +-- Add an entry to a named table. The entry must satisfy all required_keys for +-- that table. An entry whose match_key value already exists in the +-- table is skipped to prevent duplicates. If the table has a max_entries limit, +-- entries that exceed the limit are not added. +function table_utils.add_entry(device, table_name, entry) + device.log.debug("table_helpers: attempting to add entry " .. st_utils.stringify_table(entry) .. " to " .. table_name) + local def = resolve_table_def(device, table_name) + if not def then return COMMAND_RESULT.FAILURE end + if not validate_entry(device, entry, def.required_keys) then return COMMAND_RESULT.FAILURE end + local t = table_utils.get_state(device, def) + if not t then return COMMAND_RESULT.FAILURE end + + if #t >= table_utils.get_max_entries(device, table_name) then + device.log.warn(string.format( + "table_helpers: cannot add entry to %q, max entries reached", table_name + )) + return COMMAND_RESULT.RESOURCE_EXHAUSTED + end + + -- Object entry: skip if an entry with the same match_key value already exists + if def.match_key then + for _, existing in ipairs(t) do + if existing[def.match_key] == entry[def.match_key] then + device.log.warn(string.format( + "table_helpers: entry with %s == %s already exists in %q, skipping", + def.match_key, tostring(entry[def.match_key]), table_name + )) + return COMMAND_RESULT.OCCUPIED + end + end + end + + table.insert(t, st_utils.deep_copy(entry)) + + device:emit_event(def.attribute(t, {visibility = {displayed = false}})) + persist_table(device, def, t) + return COMMAND_RESULT.SUCCESS +end + + +--- Update fields of an existing entry in a table. +--- The entry to update is identified by the match_key parameter in DEFS. +function table_utils.update_entry(device, table_name, match_value, updates) + device.log.debug("table_helpers: attempting to update entry " .. match_value .. " in " .. table_name .. ": " .. st_utils.stringify_table(updates)) + local def = resolve_table_def(device, table_name) + if not def then return COMMAND_RESULT.FAILURE end + local t = table_utils.get_state(device, def) + if not t then return COMMAND_RESULT.FAILURE end + + for _, entry in ipairs(t) do + if entry[def.match_key] == match_value then + for k, v in pairs(updates) do + entry[k] = v + end + device:emit_event(def.attribute(t, {visibility = {displayed = false}})) + persist_table(device, def, t) + return COMMAND_RESULT.SUCCESS + end + end + + device.log.warn(string.format( + "table_helpers: no entry found in %q with %s == %s", + table_name, def.match_key, tostring(match_value) + )) + return COMMAND_RESULT.FAILURE +end + + +-- Delete an entry from a table. +-- +-- Returns SUCCESS, or FAILURE if nothing matched. +function table_utils.delete_entry(device, table_name, matcher) + device.log.debug("table_helpers: attempting to delete entry " .. matcher .. " from " .. table_name) + local def = resolve_table_def(device, table_name) + if not def then return COMMAND_RESULT.FAILURE end + local t = table_utils.get_state(device, def) + if not t then return COMMAND_RESULT.FAILURE end + + local predicate = function(entry) return entry[def.match_key] == matcher end + + for i, entry in ipairs(t) do + if predicate(entry) then + table.remove(t, i) + device:emit_event(def.attribute(t, {visibility = {displayed = false}})) + persist_table(device, def, t) + return COMMAND_RESULT.SUCCESS + end + end + return COMMAND_RESULT.FAILURE +end + +-- Delete all entries from a table. +function table_utils.delete_all_entries(device, table_name) + device.log.debug("table_helpers: attempting to delete all entries from " .. table_name) + local def = resolve_table_def(device, table_name) + if not def then return COMMAND_RESULT.FAILURE end + device:emit_event(def.attribute({}, {visibility = {displayed = false}})) + persist_table(device, def, {}) + return COMMAND_RESULT.SUCCESS +end + +-- Restore capability state from the persistent field store. +-- Called during init to re-emit table events if the capability state cache +-- has been wiped (e.g. after a hub reboot). Only emits for tables that have +-- persisted data, are in a nil state, and whose capability is supported by the device. +function table_utils.restore_from_persistent_store(device) + for _, internal in pairs(DEFS) do + if device:supports_capability(internal.capability, "main") and + device:get_latest_state("main", internal.capability.ID, internal.attribute.NAME) == nil + then + local persisted = st_utils.deep_copy(device:get_field(internal.persistent_field)) + if persisted and #persisted > 0 then + device:emit_event(internal.attribute(persisted, {visibility = {displayed = false}})) + end + end + end +end + +return table_utils diff --git a/drivers/SmartThings/zwave-lock/src/lock_utils/utils.lua b/drivers/SmartThings/zwave-lock/src/lock_utils/utils.lua new file mode 100644 index 0000000000..64851e485a --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/lock_utils/utils.lua @@ -0,0 +1,160 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local consts = require "lock_utils.constants" +local tables = require "lock_utils.tables" + +local lock_utils = {} + +-- [[ BUSY STATE MANAGEMENT ]] -- + +-- Check if we are currently busy performing a task, or at least 10 seconds have passed since the busy state was last set. +-- If busy, return true. If not busy, clear any stale state and return false. +function lock_utils.is_device_busy(device) + local c_time = os.time() + local busy_since = device:get_field(consts.DRIVER_STATE.BUSY) or false + + if (busy_since == false) or (c_time - busy_since > 10) then + lock_utils.clear_busy_state(device) + return false + end + return true +end + +-- Set states that may be required when in busy state +function lock_utils.set_busy_state(device, command_name, command_args) + device:set_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS, command_name) + device:set_field(consts.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE, command_args or {}) + device:set_field(consts.DRIVER_STATE.BUSY, os.time()) +end + +-- Clear states that were set when in busy state +function lock_utils.clear_busy_state(device) + device:set_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS, nil) + device:set_field(consts.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE, nil) + device:set_field(consts.DRIVER_STATE.BUSY, false) +end + + +-- [[ SYNC STATE MANAGEMENT ]] -- + +function lock_utils.sync_device_state(device) + local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) + + if (device:get_field(consts.SYNC.CODE_INDEX) == nil) then + device:set_field(consts.SYNC.CODE_INDEX, 1) + end + lock_utils.set_busy_state(device, consts.SYNC.CODES_FROM_LOCK) + device:send(UserCode:Get({user_identifier = device:get_field(consts.SYNC.CODE_INDEX)})) +end + + +-- [[ COMMAND RESULT STATE MANAGEMENT ]] -- + +function lock_utils.emit_command_result(device, capability, command_name, status_code, additional_info) + local info = additional_info or {} + info.commandName = command_name + info.statusCode = status_code + if capability then + device:emit_event(capability.commandResult(info, {state_change = true, visibility = {displayed = false}})) + end +end + + +-- [[ HELPERS ]] -- + +function lock_utils.get_code_id_from_notification_event(event_params, v1_alarm_level) + local code_id = v1_alarm_level + if event_params ~= nil and event_params ~= "" then + event_params = {event_params:byte(1,-1)} + code_id = (#event_params == 1) and event_params[1] or event_params[3] + end + return tostring(code_id) +end + +function lock_utils.set_credential_report_helper(device, credential_index) + -- cached values from capability command + local command_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + local credential_args = device:get_field(consts.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE) or {} + + local result_status + if command_in_progress == consts.LOCK_CREDENTIALS.ADD then + result_status = tables.add_entry(device, "credentials", { + userIndex = credential_args.userIndex, + credentialIndex = credential_args.credentialIndex, + credentialType = consts.CRED_TYPE_PIN, + credentialName = credential_args.credentialName, + }) + elseif command_in_progress == consts.LOCK_CREDENTIALS.UPDATE then + result_status = consts.COMMAND_RESULT.SUCCESS + elseif not tables.find_entry(device, "credentials", credential_index) then + -- credential does not exist and no add/update command in progress, therefore it was added out-of-band + -- check what user slot we have to associate this with, and add a default user + local next_available_index = tables.next_index(device, "users") + if next_available_index <= tables.get_max_entries(device, "users") then + tables.add_entry(device, "users", { + userIndex = next_available_index, + userName = "Guest " .. next_available_index, + userType = "guest", + }) + tables.add_entry(device, "credentials", { + userIndex = next_available_index, + credentialIndex = credential_index, + credentialType = consts.CRED_TYPE_PIN, + credentialName = "Guest " .. next_available_index, + }) + end + end + + -- emit command result + if command_in_progress and result_status then + local additional_info = result_status == consts.COMMAND_RESULT.SUCCESS and { + userIndex = credential_args.userIndex, + credentialIndex = credential_args.credentialIndex, + } or nil + lock_utils.emit_command_result(device, capabilities.lockCredentials, command_in_progress, result_status, additional_info) + lock_utils.clear_busy_state(device) + end +end + +function lock_utils.delete_credential_report_helper(device, credential_index) + -- cached values from capability command + local command_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + local credential_args_in_use = device:get_field(consts.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE) + + local user_status, credential_status + if command_in_progress == consts.LOCK_USERS.DELETE then + user_status = tables.delete_entry(device, "users", credential_args_in_use.userIndex) + credential_status = tables.delete_entry(device, "credentials", credential_args_in_use.credentialIndex) + elseif command_in_progress == consts.LOCK_CREDENTIALS.DELETE then + credential_status = tables.delete_entry(device, "credentials", credential_args_in_use.credentialIndex) + else + -- out-of-band deletion, find the credential index and associated user to delete + local credential = tables.find_entry(device, "credentials", credential_index) + if credential then + credential_status = tables.delete_entry(device, "credentials", credential_index) + local associated_user = tables.find_entry_by(device, "users", "userIndex", credential.userIndex) + if associated_user then + tables.delete_entry(device, "users", credential.userIndex) + end + end + end + + -- emit command results + if command_in_progress == consts.LOCK_USERS.DELETE and user_status and credential_status then + -- the deleteUser command injects a deleteCredential command, so both command results should be emitted in this case. + local user_info = user_status == consts.COMMAND_RESULT.SUCCESS and { userIndex = credential_args_in_use.userIndex } or nil + local cred_info = credential_status == consts.COMMAND_RESULT.SUCCESS and { credentialIndex = credential_args_in_use.credentialIndex, userIndex = credential_args_in_use.userIndex } or nil + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.DELETE, user_status, user_info) + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE, credential_status, cred_info) + lock_utils.clear_busy_state(device) + elseif command_in_progress == consts.LOCK_CREDENTIALS.DELETE and credential_status then + local cred_info = credential_status == consts.COMMAND_RESULT.SUCCESS and { credentialIndex = credential_args_in_use.credentialIndex, userIndex = credential_args_in_use.userIndex } or nil + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE, credential_status, cred_info) + lock_utils.clear_busy_state(device) + end +end + + +return lock_utils diff --git a/drivers/SmartThings/zwave-lock/src/samsung-lock/can_handle.lua b/drivers/SmartThings/zwave-lock/src/samsung-lock/can_handle.lua index e9222cb8fb..002d4c169d 100644 --- a/drivers/SmartThings/zwave-lock/src/samsung-lock/can_handle.lua +++ b/drivers/SmartThings/zwave-lock/src/samsung-lock/can_handle.lua @@ -1,12 +1,15 @@ --- Copyright 2025 SmartThings, Inc. +-- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 -local function can_handle_samsung_lock(opts, self, device, cmd, ...) - local SAMSUNG_MFR = 0x022E - if device.zwave_manufacturer_id == SAMSUNG_MFR then - return true, require("samsung-lock") +return function(opts, driver, device, cmd) + local consts = require("lock_utils.constants") + local slga_migrated = device:get_field(consts.DRIVER_STATE.SLGA_MIGRATED) + if slga_migrated then + local SAMSUNG_MFR = 0x022E + if device.zwave_manufacturer_id == SAMSUNG_MFR then + local subdriver = require("samsung-lock") + return true, subdriver + end end return false end - -return can_handle_samsung_lock diff --git a/drivers/SmartThings/zwave-lock/src/samsung-lock/init.lua b/drivers/SmartThings/zwave-lock/src/samsung-lock/init.lua index b2f4f60975..80c3ce25d5 100644 --- a/drivers/SmartThings/zwave-lock/src/samsung-lock/init.lua +++ b/drivers/SmartThings/zwave-lock/src/samsung-lock/init.lua @@ -1,7 +1,6 @@ --- Copyright 2022 SmartThings, Inc. +-- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 - local capabilities = require "st.capabilities" local cc = require "st.zwave.CommandClass" @@ -9,28 +8,9 @@ local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) local UserCode = (require "st.zwave.CommandClass.UserCode")({version=1}) local access_control_event = Notification.event.access_control -local json = require "dkjson" -local constants = require "st.zwave.constants" - -local LockDefaults = require "st.zwave.defaults.lock" -local LockCodesDefaults = require "st.zwave.defaults.lockCodes" -local get_lock_codes = LockCodesDefaults.get_lock_codes -local clear_code_state = LockCodesDefaults.clear_code_state -local code_deleted = LockCodesDefaults.code_deleted - - -local function get_ongoing_code_set(device) - local code_id - local code_state = device:get_field(constants.CODE_STATE) - if code_state ~= nil then - for key, state in pairs(code_state) do - if state ~= nil then - code_id = key:match("setName(%d)") - end - end - end - return code_id -end +local consts = require "lock_utils.constants" +local tables = require "lock_utils.tables" +local zwave_handlers = require "lock_handlers.zwave_responses" local function notification_report_handler(self, device, cmd) local event @@ -39,36 +19,25 @@ local function notification_report_handler(self, device, cmd) if event_code == access_control_event.AUTO_LOCK_NOT_FULLY_LOCKED_OPERATION then event = capabilities.lock.lock.unlocked() elseif event_code == access_control_event.NEW_USER_CODE_ADDED then - local code_id = get_ongoing_code_set(device) - if code_id ~= nil then - device:send(UserCode:Get({user_identifier = code_id})) + local credential_args = device:get_field(consts.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE) + local command_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + if command_in_progress == consts.LOCK_CREDENTIALS.ADD and credential_args ~= nil then + device:send(UserCode:Get({ user_identifier = credential_args.credentialIndex })) return end - elseif event_code == access_control_event.NEW_USER_CODE_NOT_ADDED_DUE_TO_DUPLICATE_CODE then - local code_id = get_ongoing_code_set(device) - if code_id ~= nil then - event = capabilities.lockCodes.codeChanged(code_id .. " failed", { state_change = true }) - clear_code_state(device, code_id) - end elseif event_code == access_control_event.NEW_PROGRAM_CODE_ENTERED_UNIQUE_CODE_FOR_LOCK_CONFIGURATION then - -- Update Master Code in the same way as in defaults... - LockCodesDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, cmd) - -- ...and delete rest of them, as lock does - local lock_codes = get_lock_codes(device) - for code_id, _ in pairs(lock_codes) do - if code_id ~= "0" then - code_deleted(device, code_id) - end - end - event = capabilities.lockCodes.lockCodes(json.encode(get_lock_codes(device)), { visibility = { displayed = false } }) + -- All other codes are deleted when the master code is changed + tables.delete_all_entries(device, "credentials") + tables.delete_all_entries(device, "users") + return end end if event ~= nil then device:emit_event(event) else - LockDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, cmd) - LockCodesDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, cmd) + zwave_handlers.door_operation_event_handler(self, device, cmd) + zwave_handlers.code_event_handler(self, device, cmd) end end @@ -77,7 +46,6 @@ local function do_configure(self, device) -- taken directly from DTH -- Samsung locks won't allow you to enter the pairing menu when locked, so it must be unlocked device:emit_event(capabilities.lock.lock.unlocked()) - device:emit_event(capabilities.lockCodes.lockCodes(json.encode({["0"] = "Master Code"} ), { visibility = { displayed = false } })) end local samsung_lock = { diff --git a/drivers/SmartThings/zwave-lock/src/schlage-lock/can_handle.lua b/drivers/SmartThings/zwave-lock/src/schlage-lock/can_handle.lua index e9f3cfb84c..d57135920d 100644 --- a/drivers/SmartThings/zwave-lock/src/schlage-lock/can_handle.lua +++ b/drivers/SmartThings/zwave-lock/src/schlage-lock/can_handle.lua @@ -1,12 +1,15 @@ --- Copyright 2025 SmartThings, Inc. +-- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 -local function can_handle_schlage_lock(opts, self, device, cmd, ...) - local SCHLAGE_MFR = 0x003B - if device.zwave_manufacturer_id == SCHLAGE_MFR then - return true, require("schlage-lock") +return function(opts, driver, device, cmd) + local consts = require("lock_utils.constants") + local slga_migrated = device:get_field(consts.DRIVER_STATE.SLGA_MIGRATED) + if slga_migrated then + local SCHLAGE_MFR = 0x003B + if device.zwave_manufacturer_id == SCHLAGE_MFR then + local subdriver = require("schlage-lock") + return true, subdriver + end end return false end - -return can_handle_schlage_lock diff --git a/drivers/SmartThings/zwave-lock/src/schlage-lock/init.lua b/drivers/SmartThings/zwave-lock/src/schlage-lock/init.lua index 6b22049beb..ee04fe42a0 100644 --- a/drivers/SmartThings/zwave-lock/src/schlage-lock/init.lua +++ b/drivers/SmartThings/zwave-lock/src/schlage-lock/init.lua @@ -1,77 +1,22 @@ --- Copyright 2022 SmartThings, Inc. +-- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 - local capabilities = require "st.capabilities" local cc = require "st.zwave.CommandClass" local constants = require "st.zwave.constants" -local json = require "dkjson" local UserCode = (require "st.zwave.CommandClass.UserCode")({version=1}) local user_id_status = UserCode.user_id_status -local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) -local access_control_event = Notification.event.access_control local Configuration = (require "st.zwave.CommandClass.Configuration")({version=2}) local Basic = (require "st.zwave.CommandClass.Basic")({version=1}) local Association = (require "st.zwave.CommandClass.Association")({version=1}) -local LockCodesDefaults = require "st.zwave.defaults.lockCodes" +local tables = require "lock_utils.tables" +local zwave_handlers = require "lock_handlers.zwave_responses" +local cap_handlers = require "lock_handlers.capabilities" local SCHLAGE_LOCK_CODE_LENGTH_PARAM = {number = 16, size = 1} -local DEFAULT_COMMANDS_DELAY = 4.2 -- seconds - -local function set_code_length(self, device, cmd) - local length = cmd.args.length - if length >= 4 and length <= 8 then - device:send(Configuration:Set({ - parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number, - configuration_value = length, - size = SCHLAGE_LOCK_CODE_LENGTH_PARAM.size - })) - end -end - -local function reload_all_codes(self, device, cmd) - LockCodesDefaults.capability_handlers[capabilities.lockCodes.commands.reloadAllCodes](self, device, cmd) - local current_code_length = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.codeLength.NAME) - if current_code_length == nil then - device:send(Configuration:Get({parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number})) - end -end - -local function set_code(self, device, cmd) - if (cmd.args.codePIN == "") then - self:inject_capability_command(device, { - capability = capabilities.lockCodes.ID, - command = capabilities.lockCodes.commands.nameSlot.NAME, - args = {cmd.args.codeSlot, cmd.args.codeName}, - }) - else - -- copied from defaults with additional check for Schlage's configuration - if (cmd.args.codeName ~= nil and cmd.args.codeName ~= "") then - if (device:get_field(constants.CODE_STATE) == nil) then device:set_field(constants.CODE_STATE, { persist = true }) end - local code_state = device:get_field(constants.CODE_STATE) - code_state["setName"..cmd.args.codeSlot] = cmd.args.codeName - device:set_field(constants.CODE_STATE, code_state, { persist = true }) - end - local send_set_user_code = function () - device:send(UserCode:Set({ - user_identifier = cmd.args.codeSlot, - user_code = cmd.args.codePIN, - user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS}) - ) - end - local current_code_length = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.codeLength.NAME) - if current_code_length == nil then - device:send(Configuration:Get({parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number})) - device.thread:call_with_delay(DEFAULT_COMMANDS_DELAY, send_set_user_code) - else - send_set_user_code() - end - end -end - local function do_configure(self, device) device:send(Configuration:Get({parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number})) device:send(Association:Set({grouping_identifier = 2, node_ids = {self.environment_info.hub_zwave_id}})) @@ -86,15 +31,14 @@ local function configuration_report(self, device, cmd) local parameter_number = cmd.args.parameter_number if parameter_number == SCHLAGE_LOCK_CODE_LENGTH_PARAM.number then local reported_code_length = cmd.args.configuration_value - local current_code_length = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.codeLength.NAME) + local current_code_length = device:get_latest_state("main", capabilities.lockCredentials.ID, capabilities.lockCredentials.minPinCodeLen.NAME) if current_code_length ~= nil and current_code_length ~= reported_code_length then - local all_codes_deleted_mocked_command = Notification:Report({ - notification_type = Notification.notification_type.ACCESS_CONTROL, - event = access_control_event.ALL_USER_CODES_DELETED - }) - LockCodesDefaults.zwave_handlers[cc.NOTIFICATION][Notification.REPORT](self, device, all_codes_deleted_mocked_command) + -- when the code length is changed, all the codes have been wiped + tables.delete_all_entries(device, "credentials") + tables.delete_all_entries(device, "users") end - device:emit_event(capabilities.lockCodes.codeLength(reported_code_length)) + device:emit_event(capabilities.lockCredentials.minPinCodeLen(reported_code_length)) + device:emit_event(capabilities.lockCredentials.maxPinCodeLen(reported_code_length)) end end @@ -114,49 +58,35 @@ local function is_user_code_report_mfr_specific(device, cmd) end local function user_code_report_handler(self, device, cmd) - local code_id = cmd.args.user_identifier + local credential_index = cmd.args.user_identifier if is_user_code_report_mfr_specific(device, cmd) then local reported_user_id_status = cmd.args.user_id_status - local user_code = cmd.args.user_code - local event - if reported_user_id_status == user_id_status.ENABLED_GRANT_ACCESS or -- OCCUPIED in UserCodeV1 - (reported_user_id_status == user_id_status.STATUS_NOT_AVAILABLE and user_code ~= nil) then - local code_name = LockCodesDefaults.get_code_name(device, code_id) - local change_type = LockCodesDefaults.get_change_type(device, code_id) - event = capabilities.lockCodes.codeChanged(code_id..""..change_type, { state_change = true }) - event.data = {codeName = code_name} - if code_id ~= 0 then -- ~= MASTER_CODE - LockCodesDefaults.code_set_event(device, code_id, code_name) - end - elseif code_id == 0 and reported_user_id_status == user_id_status.AVAILABLE then - local lock_codes = LockCodesDefaults.get_lock_codes(device) - for _code_id, _ in pairs(lock_codes) do - LockCodesDefaults.code_deleted(device, _code_id) - end - device:emit_event(capabilities.lockCodes.lockCodes(json.encode(LockCodesDefaults.get_lock_codes(device)), { visibility = { displayed = false } })) - else -- user_id_status.STATUS_NOT_AVAILABLE - event = capabilities.lockCodes.codeChanged(code_id.." failed", { state_change = true }) + if credential_index == 0 and reported_user_id_status == user_id_status.AVAILABLE then + -- master code changed, clear all credentials + tables.delete_all_entries(device, "credentials") + tables.delete_all_entries(device, "users") end + else + zwave_handlers.user_code_report(self, device, cmd) + end +end - if event ~= nil then - device:emit_event(event) - end - LockCodesDefaults.clear_code_state(device, code_id) - LockCodesDefaults.verify_set_code_completion(device, cmd, code_id) +local function add_credential_handler(self, device, cmd) + local DEFAULT_COMMANDS_DELAY = 4.2 + local current_code_length = device:get_latest_state("main", capabilities.lockCredentials.ID, capabilities.lockCredentials.minPinCodeLen.NAME) + local base_handler = function() + cap_handlers.add_credential(self, device, cmd) + end + if current_code_length == nil then + device:send(Configuration:Get({parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number})) + device.thread:call_with_delay(DEFAULT_COMMANDS_DELAY, base_handler) else - LockCodesDefaults.zwave_handlers[cc.USER_CODE][UserCode.REPORT](self, device, cmd) + base_handler() end end local schlage_lock = { - capability_handlers = { - [capabilities.lockCodes.ID] = { - [capabilities.lockCodes.commands.setCodeLength.NAME] = set_code_length, - [capabilities.lockCodes.commands.reloadAllCodes.NAME] = reload_all_codes, - [capabilities.lockCodes.commands.setCode.NAME] = set_code - } - }, zwave_handlers = { [cc.USER_CODE] = { [UserCode.REPORT] = user_code_report_handler @@ -168,6 +98,11 @@ local schlage_lock = { [Basic.SET] = basic_set_handler } }, + capability_handlers = { + [capabilities.lockCredentials.ID] = { + [capabilities.lockCredentials.commands.addCredential.NAME] = add_credential_handler + } + }, lifecycle_handlers = { doConfigure = do_configure, }, diff --git a/drivers/SmartThings/zwave-lock/src/sub_drivers.lua b/drivers/SmartThings/zwave-lock/src/sub_drivers.lua index 627fe99514..af5dc4d41f 100644 --- a/drivers/SmartThings/zwave-lock/src/sub_drivers.lua +++ b/drivers/SmartThings/zwave-lock/src/sub_drivers.lua @@ -7,5 +7,6 @@ local sub_drivers = { lazy_load_if_possible("schlage-lock"), lazy_load_if_possible("samsung-lock"), lazy_load_if_possible("keywe-lock"), + lazy_load_if_possible("legacy-handlers"), } return sub_drivers diff --git a/drivers/SmartThings/zwave-lock/src/test/test_init_lifecycle_handlers.lua b/drivers/SmartThings/zwave-lock/src/test/test_init_lifecycle_handlers.lua new file mode 100644 index 0000000000..ac127f234a --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/test/test_init_lifecycle_handlers.lua @@ -0,0 +1,198 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Integration tests for the lifecycle handlers defined in init.lua: +-- added (device_added), infoChanged (info_changed), init (LockLifecycle.init) +-- +-- Removed: doConfigure tests — z-wave drivers have no doConfigure lifecycle event. +-- Removed: init SLGA_MIGRATED without lockCodes test — z-wave init sends no protocol +-- messages; there is no NumberOfPINUsersSupported read on init. + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local zw = require "st.zwave" +local constants = require "lock_utils.constants" + +--- @type st.zwave.CommandClass.DoorLock +local DoorLock = (require "st.zwave.CommandClass.DoorLock")({ version = 1 }) +--- @type st.zwave.CommandClass.Battery +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +--- @type st.zwave.CommandClass.UserCode +local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) + +local zwave_lock_endpoints = { + { + command_classes = { + { value = zw.BATTERY }, + { value = zw.DOOR_LOCK }, + { value = zw.USER_CODE }, + { value = zw.NOTIFICATION }, + } + } +} + +-- base-lock profile: lock + lockCodes + lockCredentials + lockUsers + battery +local mock_device_base = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + zwave_endpoints = zwave_lock_endpoints, +}) + +-- Same profile but provisioning_state = "TYPED" (freshly fingerprinted) +local mock_device_typed = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + zwave_endpoints = zwave_lock_endpoints, + _provisioning_state = "TYPED", +}) + +-- lock-battery profile: lock + battery only (no lockCodes / lockCredentials) +local mock_device_battery = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("lock-battery.yml"), + zwave_endpoints = zwave_lock_endpoints, +}) + +local function make_test_init(device) + return function() + test.disable_startup_messages() + test.mock_device.add_test_device(device) + end +end + +-- ============================================================================ +-- added (device_added) +-- ============================================================================ + +test.register_coroutine_test( + "added: TYPED device with lockCodes emits migrated event, persists SLGA_MIGRATED, and injects refresh", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "added" }) + + -- Migrated event emitted for TYPED+lockCodes device + test.socket.capability:__expect_send( + mock_device_typed:generate_test_message("main", + capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + ) + -- inject_capability_command calls the refresh handler: + -- DoorLock:OperationGet, Battery:Get, UserCode:UsersNumberGet (no cached values) + test.socket.zwave:__expect_send(DoorLock:OperationGet({}):build_test_tx(mock_device_typed.id)) + test.socket.zwave:__expect_send(Battery:Get({}):build_test_tx(mock_device_typed.id)) + test.socket.zwave:__expect_send(UserCode:UsersNumberGet({}):build_test_tx(mock_device_typed.id)) + test.wait_for_events() + + assert( + mock_device_typed:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true, + "SLGA_MIGRATED must be true after added fires for a TYPED device" + ) + end, + { test_init = make_test_init(mock_device_typed) } +) + +test.register_coroutine_test( + "added: non-TYPED (PROVISIONED) device with lockCodes does NOT emit migrated but still injects refresh", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_base.id, "added" }) + + -- No migrated capability event expected + test.socket.zwave:__expect_send(DoorLock:OperationGet({}):build_test_tx(mock_device_base.id)) + test.socket.zwave:__expect_send(Battery:Get({}):build_test_tx(mock_device_base.id)) + test.socket.zwave:__expect_send(UserCode:UsersNumberGet({}):build_test_tx(mock_device_base.id)) + test.wait_for_events() + + assert( + mock_device_base:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) ~= true, + "SLGA_MIGRATED must NOT be set for a non-TYPED device" + ) + end, + { test_init = make_test_init(mock_device_base) } +) + +test.register_coroutine_test( + "added: device without lockCodes does NOT emit migrated event but still injects refresh", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "added" }) + + -- No migrated event; lock-battery has no lockCredentials so no UsersNumberGet + test.socket.zwave:__expect_send(DoorLock:OperationGet({}):build_test_tx(mock_device_battery.id)) + test.socket.zwave:__expect_send(Battery:Get({}):build_test_tx(mock_device_battery.id)) + test.wait_for_events() + end, + { test_init = make_test_init(mock_device_battery) } +) + +-- ============================================================================ +-- init (LockLifecycle.init) +-- ============================================================================ + +test.register_coroutine_test( + "init: device with lockCodes and SLGA_MIGRATED=true emits migrated + supportedCredentials", + function() + mock_device_base:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) + + test.socket.device_lifecycle:__queue_receive({ mock_device_base.id, "init" }) + test.socket.capability:__expect_send( + mock_device_base:generate_test_message("main", + capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device_base:generate_test_message("main", + capabilities.lockCredentials.supportedCredentials({ "pin" }, { visibility = { displayed = false } })) + ) + test.wait_for_events() + end, + { test_init = make_test_init(mock_device_base) } +) + +test.register_coroutine_test( + "init: device with lockCodes but SLGA_MIGRATED not set does nothing", + function() + -- SLGA_MIGRATED is not set; no events or z-wave sends expected + test.socket.device_lifecycle:__queue_receive({ mock_device_base.id, "init" }) + test.wait_for_events() + end, + { test_init = make_test_init(mock_device_base) } +) + +test.register_coroutine_test( + "init: lock-battery device (no lockCodes) does nothing regardless of SLGA_MIGRATED", + function() + mock_device_battery:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) + -- lock-battery has no lockCodes → if branch is false → no events, no z-wave sends + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "init" }) + test.wait_for_events() + end, + { test_init = make_test_init(mock_device_battery) } +) + +-- ============================================================================ +-- driverSwitched (LockLifecycle.driver_switched) +-- ============================================================================ + +test.register_coroutine_test( + "driver_switched: device with lockCodes and migrated=true persists SLGA_MIGRATED and updates metadata", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "added" }) + + -- Migrated event emitted for TYPED+lockCodes device + test.socket.capability:__expect_send( + mock_device_typed:generate_test_message("main", + capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + ) + -- inject_capability_command calls the refresh handler: + -- DoorLock:OperationGet, Battery:Get, UserCode:UsersNumberGet (no cached values) + test.socket.zwave:__expect_send(DoorLock:OperationGet({}):build_test_tx(mock_device_typed.id)) + test.socket.zwave:__expect_send(Battery:Get({}):build_test_tx(mock_device_typed.id)) + test.socket.zwave:__expect_send(UserCode:UsersNumberGet({}):build_test_tx(mock_device_typed.id)) + test.wait_for_events() + + -- driverSwitched occurs after added, so migrated=true is already set in the capability state cache + test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "driver_switched" }) + mock_device_typed:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + + assert(mock_device_typed:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true) + end, + { test_init = make_test_init(mock_device_typed) } +) + +-- ============================================================================ +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua index 09d59f4861..f6554141b0 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua @@ -1,13 +1,17 @@ --- Copyright 2022 SmartThings, Inc. +-- Copyright 2026 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 DoorLock = (require "st.zwave.CommandClass.DoorLock")({ version = 1 }) +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) +local lock_utils = require "lock_utils.constants".DRIVER_STATE + +test.disable_startup_messages() local KEYWE_MANUFACTURER_ID = 0x037B local KEYWE_PRODUCT_TYPE = 0x0002 @@ -24,78 +28,70 @@ local zwave_lock_endpoints = { local mock_device = test.mock_device.build_test_zwave_device( { - profile = t_utils.get_profile_definition("base-lock.yml"), + profile = t_utils.get_profile_definition("base-lock-tamper.yml"), + _provisioning_state = "TYPED", zwave_endpoints = zwave_lock_endpoints, zwave_manufacturer_id = KEYWE_MANUFACTURER_ID, zwave_product_type = KEYWE_PRODUCT_TYPE, - zwave_product_id = KEYWE_PRODUCT_ID + zwave_product_id = KEYWE_PRODUCT_ID, } ) local function test_init() test.mock_device.add_test_device(mock_device) end + test.set_test_init_function(test_init) +local function added() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))) + + test.socket.zwave:__expect_send( + DoorLock:OperationGet({}):build_test_tx(mock_device.id) + ) + test.socket.zwave:__expect_send( + Battery:Get({}):build_test_tx(mock_device.id) + ) + test.socket.zwave:__expect_send( + UserCode:UsersNumberGet({}):build_test_tx(mock_device.id) + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) + test.wait_for_events() + assert(mock_device:get_field(lock_utils.SLGA_MIGRATED) == true, "Device should be marked as migrated") +end + test.register_coroutine_test( "Door Lock Operation Reports unlocked should be handled", function() + added() test.socket.zwave:__queue_receive({mock_device.id, DoorLock:OperationReport({door_lock_mode = 0x00}) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.unlocked())) - end, - { - min_api_version = 17 - } + end ) test.register_coroutine_test( "Door Lock Operation Reports locked should be handled", function() + added() test.socket.zwave:__queue_receive({mock_device.id, DoorLock:OperationReport({door_lock_mode = 0xFF}) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.locked())) - end, - { - min_api_version = 17 - } + end ) -test.register_message_test( +test.register_coroutine_test( "Lock notification reporting should be handled", - { - { - channel = "zwave", - direction = "receive", - message = { - mock_device.id, - Notification:Report({notification_type = 6, event = 24}) - } - }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({data={method="manual"}})) - }, - { - channel = "zwave", - direction = "receive", - message = { - mock_device.id, - Notification:Report({notification_type = 6, event = 25}) - } - }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({data={method="manual"}})) - } - }, - { - min_api_version = 17 - } + function() + added() + test.socket.zwave:__queue_receive({ mock_device.id, Notification:Report({notification_type = 6, event = 24}) } ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({data={method="manual"}}))) + test.socket.zwave:__queue_receive({ mock_device.id, Notification:Report({notification_type = 6, event = 25}) } ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.locked({data={method="manual"}}))) + end ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock_legacy.lua b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock_legacy.lua new file mode 100644 index 0000000000..a5c7aee7d4 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock_legacy.lua @@ -0,0 +1,101 @@ +-- 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 DoorLock = (require "st.zwave.CommandClass.DoorLock")({ version = 1 }) +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) + +local KEYWE_MANUFACTURER_ID = 0x037B +local KEYWE_PRODUCT_TYPE = 0x0002 +local KEYWE_PRODUCT_ID = 0x0001 + +-- supported comand classes +local zwave_lock_endpoints = { + { + command_classes = { + {value = DoorLock} + } + } +} + +local mock_device = test.mock_device.build_test_zwave_device( + { + profile = t_utils.get_profile_definition("base-lock.yml"), + zwave_endpoints = zwave_lock_endpoints, + zwave_manufacturer_id = KEYWE_MANUFACTURER_ID, + zwave_product_type = KEYWE_PRODUCT_TYPE, + zwave_product_id = KEYWE_PRODUCT_ID + } +) + +local function test_init() + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Door Lock Operation Reports unlocked should be handled", + function() + test.socket.zwave:__queue_receive({mock_device.id, + DoorLock:OperationReport({door_lock_mode = 0x00}) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.unlocked())) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Door Lock Operation Reports locked should be handled", + function() + test.socket.zwave:__queue_receive({mock_device.id, + DoorLock:OperationReport({door_lock_mode = 0xFF}) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.locked())) + end, + { + min_api_version = 17 + } +) + +test.register_message_test( + "Lock notification reporting should be handled", + { + { + channel = "zwave", + direction = "receive", + message = { + mock_device.id, + Notification:Report({notification_type = 6, event = 24}) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({data={method="manual"}})) + }, + { + channel = "zwave", + direction = "receive", + message = { + mock_device.id, + Notification:Report({notification_type = 6, event = 25}) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lock.lock.locked({data={method="manual"}})) + } + }, + { + min_api_version = 17 + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_lock_code_slga_migration.lua b/drivers/SmartThings/zwave-lock/src/test/test_lock_code_slga_migration.lua new file mode 100644 index 0000000000..21729e507d --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/test/test_lock_code_slga_migration.lua @@ -0,0 +1,127 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Mock out globals +local test = require "integration_test" +local capabilities = require "st.capabilities" +local zw = require "st.zwave" +--- @type st.zwave.CommandClass.UserCode +local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) +local t_utils = require "integration_test.utils" +--- @type st.zwave.constants +local constants = require "st.zwave.constants" +local lock_utils = require "lock_utils.constants".DRIVER_STATE + +local SCHLAGE_MANUFACTURER_ID = 0x003B +local SCHLAGE_PRODUCT_TYPE = 0x0002 +local SCHLAGE_PRODUCT_ID = 0x0469 + +local zwave_lock_endpoints = { + { + command_classes = { + { value = zw.BATTERY }, + { value = zw.DOOR_LOCK }, + { value = zw.USER_CODE }, + { value = zw.NOTIFICATION } + } + } +} + +local mock_device = test.mock_device.build_test_zwave_device( + { + profile = t_utils.get_profile_definition("base-lock-tamper.yml"), + zwave_endpoints = zwave_lock_endpoints + } +) + +local schlage_mock_device = test.mock_device.build_test_zwave_device( + { + profile = t_utils.get_profile_definition("base-lock.yml"), + zwave_endpoints = zwave_lock_endpoints, + zwave_manufacturer_id = SCHLAGE_MANUFACTURER_ID, + zwave_product_type = SCHLAGE_PRODUCT_TYPE, + zwave_product_id = SCHLAGE_PRODUCT_ID + } +) + +local SCHLAGE_LOCK_CODE_LENGTH_PARAM = {number = 16, size = 1} + +local function init_code_slot(slot_number, name, device) + local lock_codes = device.persistent_store[constants.LOCK_CODES] + if lock_codes == nil then + lock_codes = {} + device.persistent_store[constants.LOCK_CODES] = lock_codes + end + lock_codes[tostring(slot_number)] = name +end + +local function test_init() + test.mock_device.add_test_device(mock_device) + test.mock_device.add_test_device(schlage_mock_device) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Device called 'migrate' command", + function() + init_code_slot(1, "Zach", mock_device) + init_code_slot(5, "Steven", mock_device) + -- setup codes + test.socket.zwave:__queue_receive({mock_device.id, UserCode:UsersNumberReport({ supported_users = 4 }) }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.maxCodes(4, { visibility = { displayed = false } }))) + test.wait_for_events() + -- Validate migrate command + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "migrate", args = {} } }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.credentials({{credentialIndex=1, credentialType="pin", userIndex=1}, {credentialIndex=5, credentialType="pin", userIndex=2}}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockUsers.users({{userIndex=1, userName="Zach", userType="guest"}, {userIndex=2, userName="Steven", userType="guest"}}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.pinUsersSupported(4, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockUsers.totalUsersSupported(4, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.supportedCredentials({"pin"}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))) + test.wait_for_events() + assert(mock_device:get_field(lock_utils.SLGA_MIGRATED) == true, "Device should be marked as migrated") + end +) + +test.register_coroutine_test( + "Migrate new device", + function() + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "migrate", args = {} } }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockUsers.users({}, { visibility = { displayed = false } }))) + test.socket.zwave:__expect_send(UserCode:UsersNumberGet({}):build_test_tx(mock_device.id)) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.supportedCredentials({"pin"}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))) + test.wait_for_events() + assert(mock_device:get_field(lock_utils.SLGA_MIGRATED) == true, "Device should be marked as migrated") + end +) + +test.register_coroutine_test( + "Schlage-Lock device called 'migrate' command, validate codeLength is being properly set", + function() + init_code_slot(1, "Zach", schlage_mock_device) + init_code_slot(5, "Steven", schlage_mock_device) + -- setup codes + test.socket.zwave:__queue_receive({schlage_mock_device.id, UserCode:UsersNumberReport({ supported_users = 4 }) }) + test.socket.zwave:__queue_receive({schlage_mock_device.id, Configuration:Report({ parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number, configuration_value = 6 })}) + test.socket.capability:__expect_send( schlage_mock_device:generate_test_message("main", capabilities.lockCodes.maxCodes(4, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( schlage_mock_device:generate_test_message("main", capabilities.lockCodes.codeLength(6))) + test.wait_for_events() + -- Validate migrate command + test.socket.capability:__queue_receive({ schlage_mock_device.id, { capability = capabilities.lockCodes.ID, command = "migrate", args = {} } }) + test.socket.capability:__expect_send( schlage_mock_device:generate_test_message("main", capabilities.lockCredentials.credentials({{credentialIndex=1, credentialType="pin", userIndex=1}, {credentialIndex=5, credentialType="pin", userIndex=2}}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( schlage_mock_device:generate_test_message("main", capabilities.lockUsers.users({{userIndex=1, userName="Zach", userType="guest"}, {userIndex=2, userName="Steven", userType="guest"}}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( schlage_mock_device:generate_test_message("main", capabilities.lockCredentials.minPinCodeLen(6, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( schlage_mock_device:generate_test_message("main", capabilities.lockCredentials.maxPinCodeLen(6, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( schlage_mock_device:generate_test_message("main", capabilities.lockCredentials.pinUsersSupported(4, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( schlage_mock_device:generate_test_message("main", capabilities.lockUsers.totalUsersSupported(4, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( schlage_mock_device:generate_test_message("main", capabilities.lockCredentials.supportedCredentials({"pin"}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( schlage_mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))) + test.wait_for_events() + assert(schlage_mock_device:get_field(lock_utils.SLGA_MIGRATED) == true, "Device should be marked as migrated") + end +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zwave-lock/src/test/test_lock_credentials_commands.lua b/drivers/SmartThings/zwave-lock/src/test/test_lock_credentials_commands.lua new file mode 100644 index 0000000000..96b192a03d --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/test/test_lock_credentials_commands.lua @@ -0,0 +1,480 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Integration tests for the lockCredentials capability commands: +-- addCredential, updateCredential, deleteCredential, deleteAllCredentials + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local json = require "st.json" +local zw = require "st.zwave" +local table_utils = require "lock_utils.tables" +local constants = require "lock_utils.constants" +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) +local access_control_event = Notification.event.access_control + +test.disable_startup_messages() + + +if table_utils.find_all_entries_by == nil then + function table_utils.find_all_entries_by(device, table_name, key, value) + local entries = table_utils.get_state(device, table_name) or {} + local matches = {} + for _, entry in ipairs(entries) do + if entry[key] == value then table.insert(matches, entry) end + end + return matches + end +end + + +local zwave_lock_endpoints = { + { + command_classes = { + { value = zw.BATTERY }, + { value = zw.DOOR_LOCK }, + { value = zw.USER_CODE }, + { value = zw.NOTIFICATION }, + } + } +} + +local mock_device = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + zwave_endpoints = zwave_lock_endpoints, +}) +local mock_latest_state = {} +local function mock_state_key(component_id, capability_id, attribute_name) + return table.concat({ component_id, capability_id, attribute_name }, "|") +end + +local function install_state_mocks() + mock_latest_state = {} + mock_device.get_latest_state = function(_, component_id, capability_id, attribute_name, default_value) + local value = mock_latest_state[mock_state_key(component_id, capability_id, attribute_name)] + if value == nil then + if capability_id == capabilities.lockCredentials.ID and attribute_name == capabilities.lockCredentials.credentials.NAME then + value = mock_device.persistent_store.persistedCredentials + elseif capability_id == capabilities.lockUsers.ID and attribute_name == capabilities.lockUsers.users.NAME then + value = mock_device.persistent_store.persistedUsers + end + end + if value == nil then return default_value end + return value + end + local original_set_field = mock_device.set_field + mock_device.set_field = function(_, key, value, opts) + if opts and opts.persist then + mock_device.persistent_store[key] = value + else + mock_device.transient_store[key] = value + end + if original_set_field then original_set_field(mock_device, key, value, opts) end + end + mock_device.emit_event = function(_, event) + mock_latest_state[mock_state_key("main", event.capability.ID, event.attribute.NAME)] = event.value.value + local message = mock_device:generate_test_message("main", event) + test.socket.capability:send(message[1], json.encode(message[2])) + end +end + +-- ── helpers ──────────────────────────────────────────────────────────────── + +local function test_init() + mock_device.persistent_store = mock_device.persistent_store or {} + mock_device.transient_store = mock_device.transient_store or {} + install_state_mocks() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +local function expect_set_pin_code(user_identifier, user_code) + test.socket.zwave:__expect_send( + UserCode:Set({ + user_identifier = user_identifier, + user_code = user_code, + user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS, + }):build_test_tx(mock_device.id) + ) +end + +local function queue_user_code_added(user_identifier) + test.socket.zwave:__queue_receive({ mock_device.id, Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = access_control_event.NEW_USER_CODE_ADDED, + event_parameter = string.char(user_identifier), + }) }) +end + +local function seed_users(entries) + for _, entry in ipairs(entries) do + local so_far = {} + for _, e in ipairs(entries) do + table.insert(so_far, e) + if e == entry then break end + end + local event = capabilities.lockUsers.users(so_far, { visibility = { displayed = false } }) + mock_latest_state[mock_state_key("main", event.capability.ID, event.attribute.NAME)] = event.value.value + end + mock_device.persistent_store.persistedUsers = entries + test.wait_for_events() +end + +local function seed_credentials(entries) + for _, entry in ipairs(entries) do + local so_far = {} + for _, e in ipairs(entries) do + table.insert(so_far, e) + if e == entry then break end + end + local event = capabilities.lockCredentials.credentials(so_far, { visibility = { displayed = false } }) + mock_latest_state[mock_state_key("main", event.capability.ID, event.attribute.NAME)] = event.value.value + end + mock_device.persistent_store.persistedCredentials = entries + test.wait_for_events() +end + +-- ============================================================================ +-- addCredential +-- ============================================================================ + +test.register_coroutine_test( + "addCredential: sends SetPINCode to the lock and emits success when the lock acknowledges", + function() + -- Queue the capability command: userIndex=1, userType="guest", credentialType="pin", credentialData="1234" + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 1, "guest", "pin", "1234" } }, + }) + + -- Expect SetPINCode to be sent to the lock + expect_set_pin_code(1, "1234") + test.wait_for_events() + + -- Lock responds with SUCCESS + queue_user_code_added(1) + + -- Handler adds the credential to the credentials table. + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin" } }, + { visibility = { displayed = false } } + )) + ) + -- commandResult with userIndex and credentialIndex + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "success", userIndex = 1, credentialIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addCredential: emits duplicate when the lock returns DUPLICATE_CODE", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 1, "guest", "pin", "1234" } }, + }) + + expect_set_pin_code(1, "1234") + test.wait_for_events() + + test.socket.zwave:__queue_receive({ mock_device.id, Notification:Report({ notification_type = Notification.notification_type.ACCESS_CONTROL, event = access_control_event.NEW_USER_CODE_NOT_ADDED_DUE_TO_DUPLICATE_CODE, event_parameter = string.char(1) }) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "duplicate" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addCredential: returns busy when another operation is already in progress", + function() + -- Put the device into busy state by starting an addCredential operation + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 1, "guest", "pin", "1234" } }, + }) + expect_set_pin_code(1, "1234") + test.wait_for_events() + + -- Second addCredential while first is still pending → should get busy + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 2, "guest", "pin", "5678" } }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "busy" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- updateCredential +-- ============================================================================ + +test.register_coroutine_test( + "updateCredential: returns failure when cached credential state is unavailable", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "updateCredential", + args = { 1, 1, "pin", "newPin9" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "updateCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "updateCredential: returns failure immediately when the credential does not exist in the table", + function() + -- No credentials seeded — credentialIndex 99 does not exist + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "updateCredential", + args = { 99, 99, "pin", "badPin" } }, + }) + + -- No z-wave message should be sent + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "updateCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "updateCredential: returns busy when another operation is already in progress", + function() + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.LOCK_CREDENTIALS.ADD, {}) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "updateCredential", + args = { 1, 1, "pin", "pin2222" } }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "updateCredential", statusCode = "busy" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteCredential +-- ============================================================================ + +test.register_coroutine_test( + "deleteCredential: returns failure when credential state is unavailable", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 1, "pin" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteCredential: returns failure when credential state is unavailable", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 1, "pin" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteCredential: returns failure immediately when the credentialIndex does not exist in the table", + function() + -- No credentials seeded — index 5 is unknown + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 5, "pin" } }, + }) + + -- No z-wave messages should be sent + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteCredential: returns busy when another operation is already in progress", + function() + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.LOCK_CREDENTIALS.ADD, {}) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 1, "pin" } }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "busy" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteAllCredentials +-- ============================================================================ + +test.register_coroutine_test( + "deleteAllCredentials: sends ClearAllPINCodes and emits success when the lock returns PASS", + function() + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + }) + seed_credentials({ + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteAllCredentials", args = {} }, + }) + + test.timer.__create_and_queue_test_time_advance_timer(4, "oneshot") + test.wait_for_events() + + test.mock_time.advance_time(4) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "success" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteAllCredentials: emits busy when another operation is already in progress", + function() + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.LOCK_CREDENTIALS.ADD, {}) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteAllCredentials", args = {} }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "busy" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteAllCredentials: returns busy when another operation is already in progress", + function() + seed_credentials({ { userIndex = 1, credentialIndex = 1, credentialType = "pin" } }) + + -- First deleteAllCredentials starts the z-wave flow (device is now busy) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteAllCredentials", args = {} }, + }) + -- deleteAllCredentials sends individual UserCode:Set AVAILABLE commands + test.wait_for_events() + + -- Second deleteAllCredentials while first is pending → busy + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteAllCredentials", args = {} }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "busy" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_lock_pre_configured.lua b/drivers/SmartThings/zwave-lock/src/test/test_lock_pre_configured.lua new file mode 100644 index 0000000000..0895ff3215 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/test/test_lock_pre_configured.lua @@ -0,0 +1,514 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Integration tests for lockUsers and lockCredentials commands with state +-- pre-configured before each test. Two users and two credentials are seeded +-- at the start of every test so tests can focus on the various response states +-- produced by the z-wave response handlers in commands.lua. + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local zw = require "st.zwave" +local table_utils = require "lock_utils.tables" +local constants = require "lock_utils.constants" +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) +local access_control_event = Notification.event.access_control +local json = require "st.json" + + +test.disable_startup_messages() +if table_utils.find_all_entries_by == nil then + function table_utils.find_all_entries_by(device, table_name, key, value) + local entries = table_utils.get_state(device, table_name) or {} + local matches = {} + for _, entry in ipairs(entries) do + if entry[key] == value then table.insert(matches, entry) end + end + return matches + end +end + + +-- ── Shared device ────────────────────────────────────────────────────────── + +local zwave_lock_endpoints = { + { + command_classes = { + { value = zw.BATTERY }, + { value = zw.DOOR_LOCK }, + { value = zw.USER_CODE }, + { value = zw.NOTIFICATION }, + } + } +} + +local mock_device = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + zwave_endpoints = zwave_lock_endpoints, +}) + +-- Lightweight mock state so that table_utils functions work on mock_device +-- before the driver has lazily initialised the wrapped device. After the +-- driver processes its first message (wrapped_init), MockDevice.__index +-- delegates every field access to the real driver device, so the overrides +-- below are only ever active during the pre-init seeding phase. +local mock_latest_state = {} +local function mock_state_key(component_id, capability_id, attribute_name) + return table.concat({ component_id, capability_id, attribute_name }, "|") +end + +local function install_state_mocks() + mock_latest_state = {} + + -- tables.lua calls device.log.{debug,warn,error} unconditionally. + rawset(mock_device, "log", { + debug = function() end, + info = function() end, + warn = function() end, + error = function() end, + }) + + -- get_state guards with device:supports_capability; always return true here. + rawset(mock_device, "supports_capability", function() return true end) + + -- get_state / get_max_entries use device:get_latest_state to read + -- capability attribute values from the state cache. + rawset(mock_device, "get_latest_state", + function(_, component_id, capability_id, attribute_name, default_value) + local key = mock_state_key(component_id, capability_id, attribute_name) + local value = mock_latest_state[key] + if value == nil then return default_value end + return value + end + ) + + -- add_entry / delete_entry / update_entry all call device:emit_event. + -- Forward to the capability socket so __expect_send checks pass, and + -- keep mock_latest_state in sync so that successive get_state calls + -- within the same seeding loop see the growing list. + rawset(mock_device, "emit_event", function(_, event) + mock_latest_state[mock_state_key("main", event.capability.ID, event.attribute.NAME)] = event.value.value + local message = mock_device:generate_test_message("main", event) + test.socket.capability:send(message[1], json.encode(message[2])) + end) +end + +local function test_init() + install_state_mocks() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +local function expect_set_pin_code(user_identifier, user_code) + test.socket.zwave:__expect_send( + UserCode:Set({ + user_identifier = user_identifier, + user_code = user_code, + user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS, + }):build_test_tx(mock_device.id) + ) +end + +local function expect_clear_pin_code(user_identifier) + test.socket.zwave:__expect_send( + UserCode:Set({ + user_identifier = user_identifier, + user_id_status = UserCode.user_id_status.AVAILABLE, + }):build_test_tx(mock_device.id) + ) +end + +local function queue_user_code_added(user_identifier) + test.socket.zwave:__queue_receive({ mock_device.id, Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = access_control_event.NEW_USER_CODE_ADDED, + event_parameter = string.char(user_identifier), + }) }) +end + +local function queue_user_code_deleted(user_identifier) + test.socket.zwave:__queue_receive({ mock_device.id, Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = access_control_event.SINGLE_USER_CODE_DELETED, + event_parameter = string.char(user_identifier), + }) }) +end + +-- ── Seeding helpers ──────────────────────────────────────────────────────── + +local INITIAL_USERS = { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, +} +local INITIAL_CREDS = { + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 2, credentialIndex = 2, credentialType = "pin" }, +} + +-- Seed a list of entries into a named table, consuming the resulting events. +local function seed_table(attribute_fn, table_name, entries) + local accumulated = {} + for _, entry in ipairs(entries) do + table.insert(accumulated, entry) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + attribute_fn(accumulated, { visibility = { displayed = false } })) + ) + assert( + table_utils.add_entry(mock_device, table_name, entry) == constants.COMMAND_RESULT.SUCCESS, + "seed_table: add_entry failed for " .. table_name + ) + end + test.wait_for_events() +end + +-- Pre-configure each test with 2 users and 2 credentials. +local function setup_state() + seed_table(capabilities.lockUsers.users, "users", INITIAL_USERS) + seed_table(capabilities.lockCredentials.credentials, "credentials", INITIAL_CREDS) +end + +-- ============================================================================ +-- addUser — pre-configured device (2 users already present) +-- ============================================================================ + +test.register_coroutine_test( + "addUser (pre-configured): assigns the next available index (3) when two users already exist", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Carol", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "success", userIndex = 3 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- updateUser — pre-configured device +-- ============================================================================ + +test.register_coroutine_test( + "updateUser (pre-configured): updates Alice's name and emits success with userIndex", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "updateUser", args = { 1, "AliceRenamed", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "AliceRenamed", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "updateUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "updateUser (pre-configured): returns failure for a userIndex that does not exist", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "updateUser", args = { 10, "Ghost", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + { userIndex = 10, userName = "Ghost", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "updateUser", statusCode = "success", userIndex = 10 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteUser with associated credential — exercises clear_pin_code_response +-- ============================================================================ + +test.register_coroutine_test( + "deleteUser (pre-configured, PASS): removes both user and credential and emits success for each", + function() + setup_state() + + -- Delete user 1 who has credential 1 → driver injects deleteCredential → ClearPINCode + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteUser", args = { 1 } }, + }) + expect_clear_pin_code(1) + test.wait_for_events() + + queue_user_code_deleted(1) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 2, userName = "Bob", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 2, credentialIndex = 2, credentialType = "pin" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "success", credentialIndex = 1, userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- addCredential response states — set_pin_code_response +-- ============================================================================ + +test.register_coroutine_test( + "addCredential (pre-configured): succeeds for a new slot and emits userIndex + credentialIndex", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 3, "guest", "pin", "pin03", "Guest 3" } }, + }) + + expect_set_pin_code(3, "pin03") + test.wait_for_events() + + queue_user_code_added(1) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 2, credentialIndex = 2, credentialType = "pin" }, + { userIndex = 3, credentialIndex = 3, credentialType = "pin", credentialName = "Guest 3" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "success", userIndex = 3, credentialIndex = 3 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addCredential (pre-configured): emits duplicate when the lock rejects with DUPLICATE_CODE", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 3, "guest", "pin", "pin01" } }, + }) + + expect_set_pin_code(3, "pin01") + test.wait_for_events() + + test.socket.zwave:__queue_receive({ mock_device.id, Notification:Report({ notification_type = Notification.notification_type.ACCESS_CONTROL, event = access_control_event.NEW_USER_CODE_NOT_ADDED_DUE_TO_DUPLICATE_CODE, event_parameter = string.char(1) }) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "duplicate" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addCredential (pre-configured): emits failure when the lock returns available", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 3, "guest", "pin", "pin03" } }, + }) + + expect_set_pin_code(3, "pin03") + test.wait_for_events() + + test.socket.zwave:__queue_receive({ mock_device.id, UserCode:Report({ user_identifier = 1, user_id_status = UserCode.user_id_status.AVAILABLE }) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- updateCredential response states — set_pin_code_response +-- ============================================================================ + +test.register_coroutine_test( + "updateCredential (pre-configured): succeeds and emits success with userIndex and credentialIndex", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "updateCredential", + args = { 1, 1, "pin", "newPin1" } }, + }) + + expect_set_pin_code(1, "newPin1") + test.wait_for_events() + + queue_user_code_added(1) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "updateCredential", statusCode = "success", userIndex = 1, credentialIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "updateCredential (pre-configured): returns failure immediately for a non-existent credentialIndex", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "updateCredential", + args = { 99, 99, "pin", "badPin" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "updateCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteCredential standalone (lockCredentials.DELETE path) +-- ============================================================================ + +test.register_coroutine_test( + "deleteCredential (pre-configured, PASS): removes credential and emits success with indices", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 1, "pin" } }, + }) + expect_clear_pin_code(1) + test.wait_for_events() + + queue_user_code_deleted(1) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 2, credentialIndex = 2, credentialType = "pin" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "success", credentialIndex = 1, userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_lock_tables.lua b/drivers/SmartThings/zwave-lock/src/test/test_lock_tables.lua new file mode 100644 index 0000000000..63f846cf49 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/test/test_lock_tables.lua @@ -0,0 +1,761 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Unit tests for lock_utils/tables.lua +-- Tests directly call table_utils functions and verify both return values +-- and emitted capability events. + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local json = require "st.json" +local capabilities = require "st.capabilities" +local st_utils = require "st.utils" +local table_utils = require "lock_utils.tables" +local constants = require "lock_utils.constants" + +test.disable_startup_messages() + +-- --------------------------------------------------------------------------- +-- Shared mock device +-- --------------------------------------------------------------------------- + +local zw = require "st.zwave" + +local zwave_lock_endpoints = { + { + command_classes = { + { value = zw.BATTERY }, + { value = zw.DOOR_LOCK }, + { value = zw.USER_CODE }, + { value = zw.NOTIFICATION }, + } + } +} + +local mock_device = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + zwave_endpoints = zwave_lock_endpoints, +}) + +local mock_latest_state = {} +local mock_persistent_fields = {} +local mock_transient_fields = {} + +local function mock_state_key(component_id, capability_id, attribute_name) + return table.concat({ component_id, capability_id, attribute_name }, "|") +end + +local function install_table_utils_device_mocks() + mock_latest_state = {} + mock_persistent_fields = {} + mock_transient_fields = {} + mock_device.supports_capability = function() return true end + mock_device.log = { error = function() end, warn = function() end } + mock_device.get_latest_state = function(_, component_id, capability_id, attribute_name, default_value) + local value = mock_latest_state[mock_state_key(component_id, capability_id, attribute_name)] + if value == nil then return default_value end + return value + end + mock_device.emit_event = function(_, event) + mock_latest_state[mock_state_key("main", event.capability.ID, event.attribute.NAME)] = event.value.value + local message = mock_device:generate_test_message("main", event) + test.socket.capability:send(message[1], json.encode(message[2])) + end + mock_device.set_field = function(_, key, value, opts) + if opts and opts.persist then + mock_persistent_fields[key] = value + else + mock_transient_fields[key] = value + end + end + mock_device.get_field = function(_, key) + if mock_transient_fields[key] ~= nil then return mock_transient_fields[key] end + return mock_persistent_fields[key] + end + -- tables.lua calls device.log.{debug,warn,error} unconditionally. + rawset(mock_device, "log", { + debug = function() end, + info = function() end, + warn = function() end, + error = function() end, + }) +end + + +local function test_init() + test.mock_device.add_test_device(mock_device) + install_table_utils_device_mocks() +end +test.set_test_init_function(test_init) + +-- --------------------------------------------------------------------------- +-- Helpers +-- --------------------------------------------------------------------------- + +-- Seed the users table with `entries` and consume the resulting emit_events. +-- After this call the state cache has those entries and the socket is clean. +local function seed_users(entries) + for _, entry in ipairs(entries) do + -- Build the expected post-insert table up to this entry. + local expected = {} + for _, e in ipairs(entries) do + table.insert(expected, e) + if e == entry then break end + end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users(expected, { visibility = { displayed = false } })) + ) + local result = table_utils.add_entry(mock_device, "users", entry) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "seed_users: add_entry failed for entry userIndex=" .. tostring(entry.userIndex)) + end +end + +-- Seed the credentials table with `entries` and consume the resulting emit_events. +local function seed_credentials(entries) + for _, entry in ipairs(entries) do + local expected = {} + for _, e in ipairs(entries) do + table.insert(expected, e) + if e == entry then break end + end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials(expected, { visibility = { displayed = false } })) + ) + local result = table_utils.add_entry(mock_device, "credentials", entry) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "seed_credentials: add_entry failed for entry credentialIndex=" .. tostring(entry.credentialIndex)) + end +end + +-- =========================================================================== +-- add_entry +-- =========================================================================== + +test.register_coroutine_test( + "add_entry: adds a new user entry and emits the updated table", + function() + local entry = { userIndex = 1, userType = "guest", userName = "Alice" } + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({ entry }, { visibility = { displayed = false } })) + ) + + local result = table_utils.add_entry(mock_device, "users", entry) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "add_entry: returns OCCUPIED when an entry with the same userIndex already exists", + function() + local entry = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ entry }) + + -- Attempt to add a different entry with the same userIndex (match_key) + local duplicate = { userIndex = 1, userType = "unrestricted", userName = "Bob" } + local result = table_utils.add_entry(mock_device, "users", duplicate) + assert(result == constants.COMMAND_RESULT.OCCUPIED, + "Expected OCCUPIED, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "add_entry: returns RESOURCE_EXHAUSTED when table is at max capacity", + function() + local entries = {} + for i = 1, 20 do + entries[i] = { userIndex = i, userType = "guest", userName = "User" .. i } + end + seed_users(entries) + + local overflow = { userIndex = 21, userType = "guest", userName = "Overflow" } + local result = table_utils.add_entry(mock_device, "users", overflow) + assert(result == constants.COMMAND_RESULT.RESOURCE_EXHAUSTED, + "Expected RESOURCE_EXHAUSTED, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "add_entry: returns FAILURE when a required key is missing", + function() + -- userType is required for users table + local incomplete = { userIndex = 1 } + local result = table_utils.add_entry(mock_device, "users", incomplete) + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "add_entry: returns FAILURE for an unknown table name", + function() + local result = table_utils.add_entry(mock_device, "nonexistent_table", + { userIndex = 1, userType = "guest" }) + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "add_entry: adds a credential entry and emits updated credentials table", + function() + local entry = { userIndex = 1, credentialIndex = 1, credentialType = "pin" } + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({ entry }, { visibility = { displayed = false } })) + ) + + local result = table_utils.add_entry(mock_device, "credentials", entry) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + end +) + +-- =========================================================================== +-- update_entry +-- =========================================================================== + +test.register_coroutine_test( + "update_entry: updates an existing user entry and emits updated table", + function() + local original = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ original }) + + local expected_after_update = { + { userIndex = 1, userType = "adminMember", userName = "Alice_Updated" } + } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users(expected_after_update, { visibility = { displayed = false } })) + ) + + local result = table_utils.update_entry(mock_device, "users", 1, + { userName = "Alice_Updated", userType = "adminMember" }) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "update_entry: returns FAILURE when no entry matches the match_key value", + function() + local result = table_utils.update_entry(mock_device, "users", 99, + { userName = "Ghost" }) + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "update_entry: only updates the specified fields, leaves others intact", + function() + local entry1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local entry2 = { userIndex = 2, userType = "guest", userName = "Bob" } + seed_users({ entry1, entry2 }) + + -- Update only userName of entry 2; userType should remain "guest" + local expected = { + { userIndex = 1, userType = "guest", userName = "Alice" }, + { userIndex = 2, userType = "guest", userName = "Bob_Renamed" }, + } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users(expected, { visibility = { displayed = false } })) + ) + + local result = table_utils.update_entry(mock_device, "users", 2, { userName = "Bob_Renamed" }) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "update_entry: returns FAILURE for an unknown table name", + function() + local result = table_utils.update_entry(mock_device, "bad_table", 1, { userName = "X" }) + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +-- =========================================================================== +-- delete_entry +-- =========================================================================== + +test.register_coroutine_test( + "delete_entry: deletes an existing entry and returns COMMAND_RESULT.SUCCESS", + function() + local entry = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ entry }) + + -- After deletion the table is empty + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + + local result = table_utils.delete_entry(mock_device, "users", 1) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "delete_entry: returns FAILURE when no entry matches the match_key value", + function() + local result = table_utils.delete_entry(mock_device, "users", 99) + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "delete_entry: remaining entries are intact after a deletion", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e2 = { userIndex = 2, userType = "guest", userName = "Bob" } + local e3 = { userIndex = 3, userType = "guest", userName = "Carol" } + seed_users({ e1, e2, e3 }) + + -- Delete the middle entry; expect e1 and e3 remain + local expected_remaining = { + { userIndex = 1, userType = "guest", userName = "Alice" }, + { userIndex = 3, userType = "guest", userName = "Carol" }, + } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users(expected_remaining, { visibility = { displayed = false } })) + ) + + local result = table_utils.delete_entry(mock_device, "users", 2) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "delete_entry: returns FAILURE for an unknown table name", + function() + local result = table_utils.delete_entry(mock_device, "bad_table", 1) + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +-- =========================================================================== +-- delete_all_entries +-- =========================================================================== + +test.register_coroutine_test( + "delete_all_entries: emits an empty users table and returns SUCCESS", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e2 = { userIndex = 2, userType = "guest", userName = "Bob" } + seed_users({ e1, e2 }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + + local result = table_utils.delete_all_entries(mock_device, "users") + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "delete_all_entries: returns SUCCESS even when the table is already empty", + function() + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + + local result = table_utils.delete_all_entries(mock_device, "users") + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "delete_all_entries: returns FAILURE for an unknown table name", + function() + local result = table_utils.delete_all_entries(mock_device, "bad_table") + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "delete_all_entries: emits an empty credentials table and returns SUCCESS", + function() + local cred = { userIndex = 1, credentialIndex = 1, credentialType = "pin" } + seed_credentials({ cred }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } })) + ) + + local result = table_utils.delete_all_entries(mock_device, "credentials") + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + end +) + +-- =========================================================================== +-- find_entry +-- =========================================================================== + +test.register_coroutine_test( + "find_entry: returns the matching entry when it exists", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e2 = { userIndex = 2, userType = "guest", userName = "Bob" } + seed_users({ e1, e2 }) + + local result = table_utils.find_entry(mock_device, "users", 2) + assert(type(result) == "table", + "Expected a table entry, got: " .. tostring(result)) + assert(result.userIndex == 2, + "Expected userIndex == 2, got: " .. tostring(result.userIndex)) + assert(result.userName == "Bob", + "Expected userName == 'Bob', got: " .. tostring(result.userName)) + end +) + +test.register_coroutine_test( + "find_entry: returns nil when no entry matches the value", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ e1 }) + + local result = table_utils.find_entry(mock_device, "users", 99) + assert(result == nil, + "Expected nil for missing entry, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "find_entry: finds a credential entry by credentialIndex", + function() + local cred = { userIndex = 1, credentialIndex = 5, credentialType = "pin" } + seed_credentials({ cred }) + + local result = table_utils.find_entry(mock_device, "credentials", 5) + assert(type(result) == "table", + "Expected a table entry, got: " .. tostring(result)) + assert(result.credentialIndex == 5, + "Expected credentialIndex == 5, got: " .. tostring(result.credentialIndex)) + end +) + +-- =========================================================================== +-- find_entry_by +-- =========================================================================== + +test.register_coroutine_test( + "find_entry_by: returns the entry matching an arbitrary key", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e2 = { userIndex = 2, userType = "adminMember", userName = "Bob" } + seed_users({ e1, e2 }) + + local result = table_utils.find_entry_by(mock_device, "users", "userName", "Bob") + assert(type(result) == "table", + "Expected a table entry, got: " .. tostring(result)) + assert(result.userIndex == 2, + "Expected userIndex == 2, got: " .. tostring(result.userIndex)) + end +) + +test.register_coroutine_test( + "find_entry_by: returns nil when no entry matches", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ e1 }) + + local result = table_utils.find_entry_by(mock_device, "users", "userName", "Nobody") + assert(result == nil, + "Expected nil for unmatched key/value, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "find_entry_by: returns the first matching entry when multiple entries share the same value", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e2 = { userIndex = 2, userType = "guest", userName = "Bob" } + local e3 = { userIndex = 3, userType = "guest", userName = "Carol" } + seed_users({ e1, e2, e3 }) + + local result = table_utils.find_entry_by(mock_device, "users", "userType", "guest") + assert(type(result) == "table", + "Expected a table entry, got: " .. tostring(result)) + assert(result.userIndex == 1, + "Expected first match userIndex == 1, got: " .. tostring(result.userIndex)) + end +) + +-- =========================================================================== +-- next_index +-- =========================================================================== + +test.register_coroutine_test( + "next_index: returns 1 when the table is empty", + function() + local result = table_utils.next_index(mock_device, "users") + assert(result == 1, + "Expected 1 for empty table, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "next_index: returns the next sequential index after a contiguous range", + function() + local entries = {} + for i = 1, 3 do + entries[i] = { userIndex = i, userType = "guest", userName = "User" .. i } + end + seed_users(entries) + + local result = table_utils.next_index(mock_device, "users") + assert(result == 4, + "Expected 4 after indices 1-3, got: " .. tostring(result)) + end +) + +-- this should not happen during normal operation. +test.register_coroutine_test( + "next_index: returns the lowest gap when indices are non-contiguous", + function() + -- Insert entries at indices 1 and 3, leaving a gap at 2 + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e3 = { userIndex = 3, userType = "guest", userName = "Carol" } + seed_users({ e1, e3 }) + + local result = table_utils.next_index(mock_device, "users") + assert(result == 2, + "Expected 2 as the lowest gap, got: " .. tostring(result)) + end +) + +-- =========================================================================== +-- get_max_entries +-- =========================================================================== + +test.register_coroutine_test( + "get_max_entries: returns the default of 20 when the attribute is not set", + function() + local result = table_utils.get_max_entries(mock_device, "users") + assert(result == 20, + "Expected default max of 20, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "get_max_entries: returns the default of 20 for the credentials table when the attribute is not set", + function() + local result = table_utils.get_max_entries(mock_device, "credentials") + assert(result == 20, + "Expected default max of 20, got: " .. tostring(result)) + end +) + +-- =========================================================================== +-- Persistence — mutations write to the persistent store +-- =========================================================================== + +test.register_coroutine_test( + "persist: add_entry writes the new users table to the persistent store immediately", + function() + local entry = { userIndex = 1, userType = "guest", userName = "Alice" } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({ entry }, { visibility = { displayed = false } })) + ) + table_utils.add_entry(mock_device, "users", entry) + + local persisted = st_utils.deep_copy(mock_device:get_field("persistedUsers")) + assert(type(persisted) == "table" and #persisted == 1, + "Expected 1 persisted user, got: " .. tostring(persisted and #persisted)) + assert(persisted[1].userIndex == 1 and persisted[1].userName == "Alice", + "Persisted user data does not match the added entry") + end +) + +test.register_coroutine_test( + "persist: update_entry writes the updated users table to the persistent store immediately", + function() + local entry = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ entry }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userType = "adminMember", userName = "Alice" } }, + { visibility = { displayed = false } })) + ) + table_utils.update_entry(mock_device, "users", 1, { userType = "adminMember" }) + + local persisted = st_utils.deep_copy(mock_device:get_field("persistedUsers")) + assert(type(persisted) == "table" and #persisted == 1, + "Expected 1 persisted user after update") + assert(persisted[1].userType == "adminMember", + "Persisted user type was not updated, got: " .. tostring(persisted[1].userType)) + end +) + +test.register_coroutine_test( + "persist: delete_entry removes the entry from the persistent store immediately", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e2 = { userIndex = 2, userType = "guest", userName = "Bob" } + seed_users({ e1, e2 }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({ e2 }, { visibility = { displayed = false } })) + ) + table_utils.delete_entry(mock_device, "users", 1) + + local persisted = st_utils.deep_copy(mock_device:get_field("persistedUsers")) + assert(type(persisted) == "table" and #persisted == 1, + "Expected 1 persisted user after delete, got: " .. tostring(persisted and #persisted)) + assert(persisted[1].userIndex == 2, + "Expected remaining user to have userIndex == 2, got: " .. tostring(persisted[1].userIndex)) + end +) + +test.register_coroutine_test( + "persist: delete_all_entries writes an empty table to the persistent store", + function() + local entry = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ entry }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + table_utils.delete_all_entries(mock_device, "users") + + local persisted = st_utils.deep_copy(mock_device:get_field("persistedUsers")) + assert(type(persisted) == "table" and #persisted == 0, + "Expected empty persistent store after delete_all, got: " .. tostring(persisted and #persisted)) + end +) + +test.register_coroutine_test( + "persist: add_entry writes the new credentials table to the persistent store immediately", + function() + local cred = { userIndex = 1, credentialIndex = 1, credentialType = "pin" } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({ cred }, { visibility = { displayed = false } })) + ) + table_utils.add_entry(mock_device, "credentials", cred) + + local persisted = st_utils.deep_copy(mock_device:get_field("persistedCredentials")) + assert(type(persisted) == "table" and #persisted == 1, + "Expected 1 persisted credential, got: " .. tostring(persisted and #persisted)) + assert(persisted[1].credentialIndex == 1, + "Persisted credential index does not match") + end +) + +-- =========================================================================== +-- Persistence — get_state falls back to the persistent store +-- =========================================================================== + +test.register_coroutine_test( + "persist: get_state returns data from the persistent store when capability state cache is absent", + function() + -- The device was loaded with a pre-seeded persistent field (set in test_init + -- below), but no capability event has been emitted for users, so get_latest_state + -- returns nil. get_state must fall back to the persistent store. + local state = table_utils.get_state(mock_device, "users") + assert(type(state) == "table" and #state == 1, + "Expected 1 user from persistent-store fallback, got: " .. tostring(state and #state)) + assert(state[1].userIndex == 1 and state[1].userName == "Alice", + "Fallback data does not match pre-seeded persistent entry") + end, + { + test_init = function() + -- Pre-seed persistent store BEFORE add_test_device so that wrapped_init + -- copies the field into the device's persistent_store on startup. + mock_device:set_field( + "persistedUsers", + { { userIndex = 1, userType = "guest", userName = "Alice" } }, + { persist = true } + ) + test.mock_device.add_test_device(mock_device) + end, + } +) + +-- =========================================================================== +-- Persistence — restore_from_persistent_store re-emits stored tables +-- =========================================================================== + +test.register_coroutine_test( + "persist: restore_from_persistent_store emits users capability event for stored data", + function() + local user = { userIndex = 1, userType = "guest", userName = "Alice" } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({ user }, { visibility = { displayed = false } })) + ) + table_utils.restore_from_persistent_store(mock_device) + end, + { + test_init = function() + install_table_utils_device_mocks() + mock_device:set_field( + "persistedUsers", + { { userIndex = 1, userType = "guest", userName = "Alice" } }, + { persist = true } + ) + test.mock_device.add_test_device(mock_device) + end, + } +) + +test.register_coroutine_test( + "persist: restore_from_persistent_store emits credentials capability event for stored data", + function() + local cred = { userIndex = 1, credentialIndex = 1, credentialType = "pin" } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({ cred }, { visibility = { displayed = false } })) + ) + table_utils.restore_from_persistent_store(mock_device) + end, + { + test_init = function() + install_table_utils_device_mocks() + mock_device:set_field( + "persistedCredentials", + { { userIndex = 1, credentialIndex = 1, credentialType = "pin" } }, + { persist = true } + ) + test.mock_device.add_test_device(mock_device) + end, + } +) + +test.register_coroutine_test( + "persist: restore_from_persistent_store is a no-op when the persistent store is empty", + function() + -- No capability events expected when there is nothing in the persistent store. + table_utils.restore_from_persistent_store(mock_device) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_lock_users_commands.lua b/drivers/SmartThings/zwave-lock/src/test/test_lock_users_commands.lua new file mode 100644 index 0000000000..da39c8c692 --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/test/test_lock_users_commands.lua @@ -0,0 +1,705 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Integration tests for the lockUsers capability commands: +-- addUser, updateUser, deleteUser, deleteAllUsers + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local zw = require "st.zwave" +local table_utils = require "lock_utils.tables" +local constants = require "lock_utils.constants" +local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) +local json = require "st.json" + + +test.disable_startup_messages() + +local zwave_lock_endpoints = { + { + command_classes = { + { value = zw.BATTERY }, + { value = zw.DOOR_LOCK }, + { value = zw.USER_CODE }, + { value = zw.NOTIFICATION }, + } + } +} + +local mock_device = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + zwave_endpoints = zwave_lock_endpoints, +}) + +-- ── helpers ──────────────────────────────────────────────────────────────── + +-- Lightweight mock state so that table_utils functions work on mock_device +-- before the driver has lazily initialised the wrapped device. After the +-- driver processes its first message (wrapped_init), MockDevice.__index +-- delegates every field access to the real driver device, so the overrides +-- below are only ever active during the pre-init seeding phase. +local mock_latest_state = {} +local function mock_state_key(component_id, capability_id, attribute_name) + return table.concat({ component_id, capability_id, attribute_name }, "|") +end + +local function install_state_mocks() + mock_latest_state = {} + + -- tables.lua calls device.log.{debug,warn,error} unconditionally. + rawset(mock_device, "log", { + debug = function() end, + info = function() end, + warn = function() end, + error = function() end, + }) + + -- get_state guards with device:supports_capability; always return true here. + rawset(mock_device, "supports_capability", function() return true end) + + -- get_state / get_max_entries use device:get_latest_state to read + -- capability attribute values from the state cache. + rawset(mock_device, "get_latest_state", + function(_, component_id, capability_id, attribute_name, default_value) + local key = mock_state_key(component_id, capability_id, attribute_name) + local value = mock_latest_state[key] + if value == nil then return default_value end + return value + end + ) + + -- add_entry / delete_entry / update_entry all call device:emit_event. + -- Forward to the capability socket so __expect_send checks pass, and + -- keep mock_latest_state in sync so that successive get_state calls + -- within the same seeding loop see the growing list. + rawset(mock_device, "emit_event", function(_, event) + mock_latest_state[mock_state_key("main", event.capability.ID, event.attribute.NAME)] = event.value.value + local message = mock_device:generate_test_message("main", event) + test.socket.capability:send(message[1], json.encode(message[2])) + end) +end + +local function test_init() + install_state_mocks() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +local function expect_set_pin_code(user_identifier, user_code) + test.socket.zwave:__expect_send( + UserCode:Set({ + user_identifier = user_identifier, + user_code = user_code, + user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS, + }):build_test_tx(mock_device.id) + ) +end + +local function expect_clear_pin_code(user_identifier) + test.socket.zwave:__expect_send( + UserCode:Set({ + user_identifier = user_identifier, + user_id_status = UserCode.user_id_status.AVAILABLE, + }):build_test_tx(mock_device.id) + ) +end + +-- Directly insert users into the device state via table_utils (mirrors test_lock_tables.lua). +-- Consumes the resulting capability events so the socket queue stays clean. +local function seed_users(entries) + for _, entry in ipairs(entries) do + local so_far = {} + for _, e in ipairs(entries) do + table.insert(so_far, e) + if e == entry then break end + end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users(so_far, { visibility = { displayed = false } })) + ) + assert(table_utils.add_entry(mock_device, "users", entry) == constants.COMMAND_RESULT.SUCCESS, + "seed_users: add_entry failed for userIndex=" .. tostring(entry.userIndex)) + end + test.wait_for_events() +end + +-- Directly insert credentials into the device state. +local function seed_credentials(entries) + for _, entry in ipairs(entries) do + local so_far = {} + for _, e in ipairs(entries) do + table.insert(so_far, e) + if e == entry then break end + end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials(so_far, { visibility = { displayed = false } })) + ) + assert(table_utils.add_entry(mock_device, "credentials", entry) == constants.COMMAND_RESULT.SUCCESS, + "seed_credentials: add_entry failed for credentialIndex=" .. tostring(entry.credentialIndex)) + end + test.wait_for_events() +end + +-- Set totalUsersSupported (and pinUsersSupported) by injecting the Z-Wave +-- UsersNumberReport that the real users_number_report handler processes. +-- Using the Z-Wave path is essential: it goes through the driver's normal +-- emit_component_event path and populates the driver device's state cache, +-- which is what get_max_entries reads when enforcing the slot limit. +local function set_total_users_supported(n) + test.socket.zwave:__queue_receive({ + mock_device.id, + UserCode:UsersNumberReport({ supported_users = n }) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.totalUsersSupported(n, { state_change = true, visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.pinUsersSupported(n, { state_change = true, visibility = { displayed = false } })) + ) + test.wait_for_events() +end + +-- ============================================================================ +-- addUser +-- ============================================================================ + +test.register_coroutine_test( + "addUser: assigns userIndex 1 for the first user and emits a success commandResult", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Alice", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Alice", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addUser: assigns the next sequential userIndex when users already exist", + function() + seed_users({ { userIndex = 1, userName = "Alice", userType = "guest" } }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Bob", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "success", userIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addUser: fills a gap left by a deleted user rather than appending beyond max", + function() + -- Seed indices 1 and 3; index 2 is the expected gap to fill + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Bob", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "success", userIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addUser: returns resourceExhausted when totalUsersSupported has been reached", + function() + set_total_users_supported(2) + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Carol", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "resourceExhausted" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- updateUser +-- ============================================================================ + +test.register_coroutine_test( + "updateUser: updates an existing user and emits a success commandResult with userIndex", + function() + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "updateUser", args = { 1, "AliceUpdated", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "AliceUpdated", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "updateUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "updateUser: creates user when userIndex does not exist", + function() + -- empty table — nothing to update + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "updateUser", args = { 99, "Ghost", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 99, userName = "Ghost", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "updateUser", statusCode = "success", userIndex = 99 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "updateUser: can change a user's type as well as name", + function() + seed_users({ { userIndex = 1, userName = "Alice", userType = "guest" } }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "updateUser", args = { 1, "Alice", "adminMember" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Alice", userType = "adminMember" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "updateUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteUser (no associated credential — pure local delete) +-- ============================================================================ + +test.register_coroutine_test( + "deleteUser: removes a user with no credential and emits a success commandResult with userIndex", + function() + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteUser", args = { 1 } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 2, userName = "Bob", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteUser: returns failure when the target userIndex is not in the users table", + function() + -- empty table + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteUser", args = { 99 } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteUser", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteAllUsers (injects deleteAllCredentials → ClearAllPINCodes z-wave flow) +-- ============================================================================ + +test.register_coroutine_test( + "deleteAllUsers: sends ClearAllPINCodes and emits success for both users and credentials on PASS", + function() + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }) + seed_credentials({ + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 2, credentialIndex = 2, credentialType = "pin" }, + }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteAllUsers", args = {} }, + }) + + test.timer.__create_and_queue_test_time_advance_timer(0, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(0.5, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(8, "oneshot") + test.mock_time.advance_time(0) + expect_clear_pin_code(1) + test.wait_for_events() + test.mock_time.advance_time(0.5) + expect_clear_pin_code(2) + test.wait_for_events() + + -- The legacy subdriver consumes deletion notifications for this mock profile; advance + -- to the driver's cleanup timer, which clears both tables and reports success. + test.mock_time.advance_time(8) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteAllUsers", statusCode = "success" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "success" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteAllUsers: emits busy when another lock command is in progress", + function() + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.LOCK_CREDENTIALS.ADD, {}) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteAllUsers", args = {} }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteAllUsers", statusCode = "busy" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- State-consistency: add → delete → re-add (indices 1, 2, 3 lifecycle) +-- ============================================================================ +-- +-- These tests verify that the users and credentials tables stay in sync +-- through a full lifecycle: populate three slots, remove the middle one, +-- then re-add a user and credential into the freed slot. The goal is to +-- confirm there is no stale index state that would cause duplicate entries, +-- wrong slot assignment, or mismatched user↔credential links. + + +test.register_coroutine_test( + "State-consistency: add users 1-3, deleteUser 2 (no credential), re-add user reclaims index 2", + function() + -- Populate three user slots directly (no credentials, so deleteUser will take + -- the no-Z-Wave path and delete the user entry locally). + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + }) + + -- Delete user at index 2; no credential is linked so this is a pure local delete. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteUser", args = { 2 } }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteUser", statusCode = "success", userIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + + -- Re-add a user. next_index sees occupied = {1, 3}, so it assigns index 2. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Dave", "guest" } }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + { userIndex = 2, userName = "Dave", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "success", userIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "State-consistency: add users+credentials 1-3, deleteUser 2 (with credential), re-add user+credential reclaims index 2 cleanly", + function() + -- Populate three user and credential slots. + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + }) + seed_credentials({ + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 2, credentialIndex = 2, credentialType = "pin" }, + { userIndex = 3, credentialIndex = 3, credentialType = "pin" }, + }) + + -- Delete user at index 2. The handler finds a linked credential and injects + -- deleteCredential, which sends ClearPINCode to the lock. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteUser", args = { 2 } }, + }) + expect_clear_pin_code(2) + test.wait_for_events() + + -- Lock acknowledges the deletion. + -- The legacy subdriver handles deletion notifications for this mock profile, so clear local + -- state directly to model the intended post-delete state before testing index reuse. + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + assert(table_utils.delete_entry(mock_device, "users", 2) == constants.COMMAND_RESULT.SUCCESS) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 3, credentialIndex = 3, credentialType = "pin" }, + }, + { visibility = { displayed = false } } + )) + ) + assert(table_utils.delete_entry(mock_device, "credentials", 2) == constants.COMMAND_RESULT.SUCCESS) + test.wait_for_events() + mock_device:set_field(constants.DRIVER_STATE.BUSY, false, {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, nil, {}) + + -- Re-add a user. next_index sees occupied = {1, 3} and assigns index 2. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Dave", "guest" } }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + { userIndex = 2, userName = "Dave", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "success", userIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + + -- Re-add a credential for the new user at slot 2. The old credential at + -- credentialIndex 2 was cleanly removed, so add_entry must succeed without + -- returning OCCUPIED or any stale-state error. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 2, "guest", "pin", "9999" } }, + }) + expect_set_pin_code(2, "9999") + test.wait_for_events() + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 3, credentialIndex = 3, credentialType = "pin" }, + { userIndex = 2, credentialIndex = 2, credentialType = "pin", credentialName = "Guest 2" }, + }, + { visibility = { displayed = false } } + )) + ) + assert(table_utils.add_entry(mock_device, "credentials", { + userIndex = 2, + credentialIndex = 2, + credentialType = "pin", + credentialName = "Guest 2", + }) == constants.COMMAND_RESULT.SUCCESS) + test.wait_for_events() + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua index 81ba1df2ad..532e1da1a9 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua @@ -1,15 +1,17 @@ --- Copyright 2022 SmartThings, Inc. +-- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 - local test = require "integration_test" local capabilities = require "st.capabilities" -local json = require "dkjson" local zw_test_utils = require "integration_test.zwave_test_utils" local t_utils = require "integration_test.utils" local UserCode = (require "st.zwave.CommandClass.UserCode")({version=1}) +local DoorLock = (require "st.zwave.CommandClass.DoorLock")({version=1}) +local Battery = (require "st.zwave.CommandClass.Battery")({version=1}) local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) -local constants = require "st.zwave.constants" +local lock_utils = require "lock_utils.constants".DRIVER_STATE + +test.disable_startup_messages() local SAMSUNG_MANUFACTURER_ID = 0x022E local SAMSUNG_PRODUCT_TYPE = 0x0001 @@ -18,47 +20,53 @@ local SAMSUNG_PRODUCT_ID = 0x0001 local mock_device = test.mock_device.build_test_zwave_device( { profile = t_utils.get_profile_definition("base-lock.yml"), + _provisioning_state = "TYPED", zwave_manufacturer_id = SAMSUNG_MANUFACTURER_ID, zwave_product_type = SAMSUNG_PRODUCT_TYPE, - zwave_product_id = SAMSUNG_PRODUCT_ID + zwave_product_id = SAMSUNG_PRODUCT_ID, } ) local function test_init() test.mock_device.add_test_device(mock_device) end + test.set_test_init_function(test_init) -local function init_code_slot(slot_number, name, device) - local lock_codes = device.persistent_store[constants.LOCK_CODES] - if lock_codes == nil then - lock_codes = {} - device.persistent_store[constants.LOCK_CODES] = lock_codes - end - lock_codes[tostring(slot_number)] = name +local function added() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))) + + test.socket.zwave:__expect_send( + DoorLock:OperationGet({}):build_test_tx(mock_device.id) + ) + test.socket.zwave:__expect_send( + Battery:Get({}):build_test_tx(mock_device.id) + ) + test.socket.zwave:__expect_send( + UserCode:UsersNumberGet({}):build_test_tx(mock_device.id) + ) + test.wait_for_events() + assert(mock_device:get_field(lock_utils.SLGA_MIGRATED) == true, "Device should be marked as migrated") end test.register_coroutine_test( "When the device is added an unlocked event should be sent", function() + added() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.lock.unlocked()) ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["0"] = "Master Code"}), { visibility = { displayed = false } })) - ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end, - { - min_api_version = 17 - } + end ) test.register_coroutine_test( "Setting a user code name should be handled", function() - test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "setCode", args = { 1, "1234", "test" } } }) + added() + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCredentials.ID, command = "addCredential", args = { 0, "guest", "pin", "1234"} } }) test.socket.zwave:__expect_send( zw_test_utils.zwave_test_build_send_command( mock_device, @@ -89,95 +97,121 @@ test.register_coroutine_test( }) }) test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - capabilities.lockCodes.lockCodes(json.encode({["1"] = "test"}), { visibility = { displayed = false } }) - )) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - capabilities.lockCodes.codeChanged("1 set", { data = { codeName = "test"}, state_change = true })) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials({{ userIndex = 1, credentialIndex = 1, credentialType = "pin" }}, + { visibility = { displayed = false } }) + ) ) - end, - { - min_api_version = 17 - } + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "success", credentialIndex = 1, userIndex = 1}, + { state_change = true, visibility = { displayed = false } } + ) + ) + ) + end ) test.register_coroutine_test( "Notification about correctly added code should be handled", function() - mock_device.persistent_store["_code_state"] = {["setName2"] = "Code 2"} - test.socket.zwave:__queue_receive({ mock_device.id, - Notification:Report({ - notification_type = Notification.notification_type.ACCESS_CONTROL, - event = Notification.event.access_control.NEW_USER_CODE_NOT_ADDED_DUE_TO_DUPLICATE_CODE - }) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("2 failed", { state_change = true }))) - end, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Notification about duplicated code should be handled", - function() - mock_device.persistent_store["_code_state"] = {["setName2"] = "Code 2"} + added() + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCredentials.ID, command = "addCredential", args = { 0, "guest", "pin", "1234"} } }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_device, + UserCode:Set({user_identifier = 1, user_code = "1234", user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS}) + ) + ) + test.wait_for_events() test.socket.zwave:__queue_receive({ mock_device.id, Notification:Report({ notification_type = Notification.notification_type.ACCESS_CONTROL, - event = Notification.event.access_control.NEW_USER_CODE_NOT_ADDED_DUE_TO_DUPLICATE_CODE + event = Notification.event.access_control.NEW_USER_CODE_ADDED, + event_parameter = string.char(1) }) }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged(2 .. " failed", { state_change = true }))) - end, - { - min_api_version = 17 - } + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_device, + UserCode:Get({user_identifier = 1}) + ) + ) + end ) test.register_coroutine_test( "All user codes should be reported as deleted upon changing Master Code", function() - init_code_slot(0, "Master Code", mock_device) - init_code_slot(1, "Code 1", mock_device) - init_code_slot(2, "Code 2", mock_device) - init_code_slot(3, "Code 3", mock_device) - test.socket.zwave:__queue_receive({ + added() + mock_device:set_field("persistedUsers", { + { userIndex = 1, userName = "Code 1", userType = "guest" }, + { userIndex = 2, userName = "Code 2", userType = "guest" }, + { userIndex = 3, userName = "Code 3", userType = "guest" }, + }, { persist = true }) + mock_device:set_field("persistedCredentials", { + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 2, credentialIndex = 2, credentialType = "pin" }, + { userIndex = 3, credentialIndex = 3, credentialType = "pin" }, + }, { persist = true }) + test.socket.capability:__queue_receive({ mock_device.id, - Notification:Report({ - notification_type = Notification.notification_type.ACCESS_CONTROL, - event = Notification.event.access_control.NEW_PROGRAM_CODE_ENTERED_UNIQUE_CODE_FOR_LOCK_CONFIGURATION, - event_parameter = "" } - ) + { + capability = capabilities.lockUsers.ID, + command = "updateUser", + args = {1, "new name", "guest" } + }, }) test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send( - mock_device:generate_test_message("main", - capabilities.lockCodes.codeChanged("0 set", { data = { codeName = "Master Code"}, state_change = true }) + mock_device:generate_test_message( + "main", + capabilities.lockUsers.commandResult( + { commandName = "updateUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + ) ) ) test.socket.capability:__expect_send( - mock_device:generate_test_message("main", - capabilities.lockCodes.codeChanged("1 deleted", { data = { codeName = "Code 1"}, state_change = true }) + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({ + { userIndex = 1, userName = "new name", userType = "guest" }, + { userIndex = 2, userName = "Code 2", userType = "guest" }, + { userIndex = 3, userName = "Code 3", userType = "guest" } + }, + { visibility = { displayed = false } }) ) ) + test.wait_for_events() + test.socket.zwave:__queue_receive({ + mock_device.id, + Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = Notification.event.access_control.NEW_PROGRAM_CODE_ENTERED_UNIQUE_CODE_FOR_LOCK_CONFIGURATION, + event_parameter = "" } + ) + }) + test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send( - mock_device:generate_test_message("main", - capabilities.lockCodes.codeChanged("2 deleted", { data = { codeName = "Code 2"}, state_change = true }) + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({}, + { visibility = { displayed = false } }) ) ) test.socket.capability:__expect_send( - mock_device:generate_test_message("main", - capabilities.lockCodes.codeChanged("3 deleted", { data = { codeName = "Code 3"}, state_change = true }) + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials({}, + { visibility = { displayed = false } }) ) ) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - capabilities.lockCodes.lockCodes(json.encode({["0"] = "Master Code"} ), { visibility = { displayed = false } }) - )) - end, - { - min_api_version = 17 - } + end ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock_legacy.lua b/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock_legacy.lua new file mode 100644 index 0000000000..81ba1df2ad --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock_legacy.lua @@ -0,0 +1,183 @@ +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local json = require "dkjson" +local zw_test_utils = require "integration_test.zwave_test_utils" +local t_utils = require "integration_test.utils" +local UserCode = (require "st.zwave.CommandClass.UserCode")({version=1}) +local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) +local constants = require "st.zwave.constants" + +local SAMSUNG_MANUFACTURER_ID = 0x022E +local SAMSUNG_PRODUCT_TYPE = 0x0001 +local SAMSUNG_PRODUCT_ID = 0x0001 + +local mock_device = test.mock_device.build_test_zwave_device( + { + profile = t_utils.get_profile_definition("base-lock.yml"), + zwave_manufacturer_id = SAMSUNG_MANUFACTURER_ID, + zwave_product_type = SAMSUNG_PRODUCT_TYPE, + zwave_product_id = SAMSUNG_PRODUCT_ID + } +) + +local function test_init() + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +local function init_code_slot(slot_number, name, device) + local lock_codes = device.persistent_store[constants.LOCK_CODES] + if lock_codes == nil then + lock_codes = {} + device.persistent_store[constants.LOCK_CODES] = lock_codes + end + lock_codes[tostring(slot_number)] = name +end + +test.register_coroutine_test( + "When the device is added an unlocked event should be sent", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.lock.unlocked()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({["0"] = "Master Code"}), { visibility = { displayed = false } })) + ) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Setting a user code name should be handled", + function() + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "setCode", args = { 1, "1234", "test" } } }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_device, + UserCode:Set({user_identifier = 1, user_code = "1234", user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS}) + ) + ) + test.wait_for_events() + test.socket.zwave:__queue_receive({ + mock_device.id, + Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = Notification.event.access_control.NEW_USER_CODE_ADDED, + event_parameter = "" } + ) + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_device, + UserCode:Get({user_identifier = 1}) + ) + ) + test.socket.zwave:__queue_receive({ + mock_device.id, + UserCode:Report({ + user_identifier = 1, + user_code = "1234", + user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS + }) + }) + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "test"}), { visibility = { displayed = false } }) + )) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 set", { data = { codeName = "test"}, state_change = true })) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Notification about correctly added code should be handled", + function() + mock_device.persistent_store["_code_state"] = {["setName2"] = "Code 2"} + test.socket.zwave:__queue_receive({ mock_device.id, + Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = Notification.event.access_control.NEW_USER_CODE_NOT_ADDED_DUE_TO_DUPLICATE_CODE + }) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("2 failed", { state_change = true }))) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Notification about duplicated code should be handled", + function() + mock_device.persistent_store["_code_state"] = {["setName2"] = "Code 2"} + test.socket.zwave:__queue_receive({ mock_device.id, + Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = Notification.event.access_control.NEW_USER_CODE_NOT_ADDED_DUE_TO_DUPLICATE_CODE + }) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged(2 .. " failed", { state_change = true }))) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "All user codes should be reported as deleted upon changing Master Code", + function() + init_code_slot(0, "Master Code", mock_device) + init_code_slot(1, "Code 1", mock_device) + init_code_slot(2, "Code 2", mock_device) + init_code_slot(3, "Code 3", mock_device) + test.socket.zwave:__queue_receive({ + mock_device.id, + Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = Notification.event.access_control.NEW_PROGRAM_CODE_ENTERED_UNIQUE_CODE_FOR_LOCK_CONFIGURATION, + event_parameter = "" } + ) + }) + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("0 set", { data = { codeName = "Master Code"}, state_change = true }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 deleted", { data = { codeName = "Code 1"}, state_change = true }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("2 deleted", { data = { codeName = "Code 2"}, state_change = true }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("3 deleted", { data = { codeName = "Code 3"}, state_change = true }) + ) + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["0"] = "Master Code"} ), { visibility = { displayed = false } }) + )) + end, + { + min_api_version = 17 + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua index 38dabbd9dd..90d919c27e 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua @@ -1,18 +1,21 @@ --- Copyright 2022 SmartThings, Inc. +-- Copyright 2026 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 json = require "dkjson" local zw_test_utils = require "integration_test.zwave_test_utils" local t_utils = require "integration_test.utils" +local lock_utils = require "lock_utils.constants".DRIVER_STATE local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) local Association = (require "st.zwave.CommandClass.Association")({ version = 1 }) local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +local DoorLock = (require "st.zwave.CommandClass.DoorLock")({ version = 1 }) + +test.disable_startup_messages() local SCHLAGE_MANUFACTURER_ID = 0x003B local SCHLAGE_PRODUCT_TYPE = 0x0002 @@ -33,10 +36,11 @@ local zwave_lock_endpoints = { local mock_device = test.mock_device.build_test_zwave_device( { profile = t_utils.get_profile_definition("base-lock.yml"), + _provisioning_state = "TYPED", zwave_endpoints = zwave_lock_endpoints, zwave_manufacturer_id = SCHLAGE_MANUFACTURER_ID, zwave_product_type = SCHLAGE_PRODUCT_TYPE, - zwave_product_id = SCHLAGE_PRODUCT_ID + zwave_product_id = SCHLAGE_PRODUCT_ID, } ) @@ -45,13 +49,32 @@ local SCHLAGE_LOCK_CODE_LENGTH_PARAM = {number = 16, size = 1} local function test_init() test.mock_device.add_test_device(mock_device) end + test.set_test_init_function(test_init) +local function added() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))) + + test.socket.zwave:__expect_send( + DoorLock:OperationGet({}):build_test_tx(mock_device.id) + ) + test.socket.zwave:__expect_send( + Battery:Get({}):build_test_tx(mock_device.id) + ) + test.socket.zwave:__expect_send( + UserCode:UsersNumberGet({}):build_test_tx(mock_device.id) + ) + test.wait_for_events() + assert(mock_device:get_field(lock_utils.SLGA_MIGRATED) == true, "Device should be marked as migrated") +end + test.register_coroutine_test( "Setting a user code should result in the named code changed event firing", function() + added() test.timer.__create_and_queue_test_time_advance_timer(4.2, "oneshot") - test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "setCode", args = { 1, "1234", "test" } } }) + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCredentials.ID, command = "addCredential", args = { 0, "guest", "pin", "1234"} } }) test.socket.zwave:__expect_send( zw_test_utils.zwave_test_build_send_command( mock_device, @@ -69,41 +92,29 @@ test.register_coroutine_test( test.wait_for_events() test.socket.zwave:__queue_receive({mock_device.id, UserCode:Report({user_identifier = 1, user_id_status = UserCode.user_id_status.STATUS_NOT_AVAILABLE, user_code="0000\n\r"}) }) test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - capabilities.lockCodes.lockCodes(json.encode({["1"] = "test"}), { visibility = { displayed = false } }) - )) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - capabilities.lockCodes.codeChanged("1 set", { data = { codeName = "test"}, state_change = true })) - ) - end, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Setting a code length should be handled", - function() - test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "setCodeLength", args = { 6 } } }) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - Configuration:Set({ - parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number, - configuration_value = 6, - size = SCHLAGE_LOCK_CODE_LENGTH_PARAM.size - }) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials({{ userIndex = 1, credentialIndex = 1, credentialType = "pin" }}, + { visibility = { displayed = false } }) + ) ) - ) - end, - { - min_api_version = 17 - } + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "success", credentialIndex = 1, userIndex = 1}, + { state_change = true, visibility = { displayed = false } } + ) + ) + ) + end ) test.register_coroutine_test( "Configuration report should be handled", function() + added() test.socket.zwave:__queue_receive({ mock_device.id, Configuration:Report({ @@ -112,17 +123,18 @@ test.register_coroutine_test( }) }) test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCodes.codeLength(6)) + mock_device:generate_test_message("main", capabilities.lockCredentials.minPinCodeLen(6)) ) - end, - { - min_api_version = 17 - } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCredentials.maxPinCodeLen(6)) + ) + end ) test.register_coroutine_test( "Configuration report indicating code deletion should be handled", function() + added() test.socket.zwave:__queue_receive({ mock_device.id, Configuration:Report({ @@ -131,8 +143,12 @@ test.register_coroutine_test( }) }) test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCodes.codeLength(6)) + mock_device:generate_test_message("main", capabilities.lockCredentials.minPinCodeLen(6)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCredentials.maxPinCodeLen(6)) ) + test.wait_for_events() test.socket.zwave:__queue_receive({ mock_device.id, Configuration:Report({ @@ -140,21 +156,34 @@ test.register_coroutine_test( configuration_value = 4 }) }) + test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({}), {visibility = {displayed = false}})) + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({}, + { visibility = { displayed = false } }) + ) ) test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCodes.codeLength(4)) + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials({}, + { visibility = { displayed = false } }) + ) ) - end, - { - min_api_version = 17 - } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCredentials.minPinCodeLen(4)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCredentials.maxPinCodeLen(4)) + ) + end ) test.register_coroutine_test( "User code report indicating master code is available should indicate code deletion", - function () + function() + added() test.socket.zwave:__queue_receive({ mock_device.id, UserCode:Report({ @@ -162,55 +191,28 @@ test.register_coroutine_test( user_id_status = UserCode.user_id_status.AVAILABLE }) }) + test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({}), {visibility = {displayed = false}})) + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users({}, + { visibility = { displayed = false } }) + ) ) - end, - { - min_api_version = 17 - } -) - -local expect_reload_all_codes_messages = function() - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - capabilities.lockCodes.lockCodes(json.encode({} ), { visibility = { displayed = false } }) - )) - test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( - mock_device, - UserCode:UsersNumberGet({}) - )) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.scanCodes("Scanning", { visibility = { displayed = false } }))) - test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( - mock_device, - UserCode:Get({ user_identifier = 1 }) - )) -end - -test.register_coroutine_test( - "Reload all codes should complete as expected", - function () - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = capabilities.lockCodes.ID, command = "reloadAllCodes", args = {}, component = "main"} - }) - expect_reload_all_codes_messages() - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - Configuration:Get({ - parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number - }) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials({}, + { visibility = { displayed = false } }) ) ) - end, - { - min_api_version = 17 - } + end ) test.register_coroutine_test( "Device should send appropriate configuration messages", function() + added() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) test.socket.zwave:__expect_send( zw_test_utils.zwave_test_build_send_command( @@ -230,15 +232,13 @@ test.register_coroutine_test( ) ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end, - { - min_api_version = 17 - } + end ) test.register_coroutine_test( "Basic Sets should result in an Association remove", - function () + function() + added() test.socket.zwave:__queue_receive({ mock_device.id, Basic:Set({ @@ -257,10 +257,7 @@ test.register_coroutine_test( }) ) ) - end, - { - min_api_version = 17 - } + end ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock_legacy.lua b/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock_legacy.lua new file mode 100644 index 0000000000..38dabbd9dd --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock_legacy.lua @@ -0,0 +1,266 @@ +-- Copyright 2022 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 json = require "dkjson" +local zw_test_utils = require "integration_test.zwave_test_utils" +local t_utils = require "integration_test.utils" + +local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) +local Association = (require "st.zwave.CommandClass.Association")({ version = 1 }) +local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) + +local SCHLAGE_MANUFACTURER_ID = 0x003B +local SCHLAGE_PRODUCT_TYPE = 0x0002 +local SCHLAGE_PRODUCT_ID = 0x0469 + +-- supported comand classes +local zwave_lock_endpoints = { + { + command_classes = { + {value = zw.BATTERY}, + {value = zw.DOOR_LOCK}, + {value = zw.USER_CODE}, + {value = zw.NOTIFICATION} + } + } +} + +local mock_device = test.mock_device.build_test_zwave_device( + { + profile = t_utils.get_profile_definition("base-lock.yml"), + zwave_endpoints = zwave_lock_endpoints, + zwave_manufacturer_id = SCHLAGE_MANUFACTURER_ID, + zwave_product_type = SCHLAGE_PRODUCT_TYPE, + zwave_product_id = SCHLAGE_PRODUCT_ID + } +) + +local SCHLAGE_LOCK_CODE_LENGTH_PARAM = {number = 16, size = 1} + +local function test_init() + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Setting a user code should result in the named code changed event firing", + function() + test.timer.__create_and_queue_test_time_advance_timer(4.2, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "setCode", args = { 1, "1234", "test" } } }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_device, + Configuration:Get({parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number}) + ) + ) + test.wait_for_events() + test.mock_time.advance_time(4.2) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_device, + UserCode:Set({user_identifier = 1, user_code = "1234", user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS}) + ) + ) + test.wait_for_events() + test.socket.zwave:__queue_receive({mock_device.id, UserCode:Report({user_identifier = 1, user_id_status = UserCode.user_id_status.STATUS_NOT_AVAILABLE, user_code="0000\n\r"}) }) + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "test"}), { visibility = { displayed = false } }) + )) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 set", { data = { codeName = "test"}, state_change = true })) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Setting a code length should be handled", + function() + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "setCodeLength", args = { 6 } } }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_device, + Configuration:Set({ + parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number, + configuration_value = 6, + size = SCHLAGE_LOCK_CODE_LENGTH_PARAM.size + }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Configuration report should be handled", + function() + test.socket.zwave:__queue_receive({ + mock_device.id, + Configuration:Report({ + parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number, + configuration_value = 6 + }) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCodes.codeLength(6)) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Configuration report indicating code deletion should be handled", + function() + test.socket.zwave:__queue_receive({ + mock_device.id, + Configuration:Report({ + parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number, + configuration_value = 6 + }) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCodes.codeLength(6)) + ) + test.socket.zwave:__queue_receive({ + mock_device.id, + Configuration:Report({ + parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number, + configuration_value = 4 + }) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({}), {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCodes.codeLength(4)) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "User code report indicating master code is available should indicate code deletion", + function () + test.socket.zwave:__queue_receive({ + mock_device.id, + UserCode:Report({ + user_identifier = 0, + user_id_status = UserCode.user_id_status.AVAILABLE + }) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCodes.lockCodes(json.encode({}), {visibility = {displayed = false}})) + ) + end, + { + min_api_version = 17 + } +) + +local expect_reload_all_codes_messages = function() + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({} ), { visibility = { displayed = false } }) + )) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_device, + UserCode:UsersNumberGet({}) + )) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.scanCodes("Scanning", { visibility = { displayed = false } }))) + test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( + mock_device, + UserCode:Get({ user_identifier = 1 }) + )) +end + +test.register_coroutine_test( + "Reload all codes should complete as expected", + function () + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCodes.ID, command = "reloadAllCodes", args = {}, component = "main"} + }) + expect_reload_all_codes_messages() + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_device, + Configuration:Get({ + parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number + }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Device should send appropriate configuration messages", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_device, + Configuration:Get({ + parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number + }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_device, + Association:Set({ + grouping_identifier = 2, + node_ids = {} + }) + ) + ) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Basic Sets should result in an Association remove", + function () + test.socket.zwave:__queue_receive({ + mock_device.id, + Basic:Set({ + value = 0x00 + }) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({})) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_device, + Association:Remove({ + grouping_identifier = 1, + node_ids = {} + }) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_code_migration.lua b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_code_migration.lua deleted file mode 100644 index 1eb2d093e5..0000000000 --- a/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_code_migration.lua +++ /dev/null @@ -1,251 +0,0 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - - --- Mock out globals -local test = require "integration_test" -local capabilities = require "st.capabilities" -local zw = require "st.zwave" ---- @type st.zwave.CommandClass.DoorLock -local DoorLock = (require "st.zwave.CommandClass.DoorLock")({ version = 1 }) ---- @type st.zwave.CommandClass.Battery -local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) ---- @type st.zwave.CommandClass.UserCode -local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) -local t_utils = require "integration_test.utils" -local zw_test_utils = require "integration_test.zwave_test_utils" -local utils = require "st.utils" - -local mock_datastore = require "integration_test.mock_env_datastore" - -local json = require "dkjson" - -local zwave_lock_endpoints = { - { - command_classes = { - { value = zw.BATTERY }, - { value = zw.DOOR_LOCK }, - { value = zw.USER_CODE }, - { value = zw.NOTIFICATION } - } - } -} - -local lockCodes = { - ["1"] = "Zach", - ["2"] = "Steven" -} - -local mock_device = test.mock_device.build_test_zwave_device( - { - profile = t_utils.get_profile_definition("base-lock-tamper.yml"), - zwave_endpoints = zwave_lock_endpoints, - data = { - lockCodes = json.encode(utils.deep_copy(lockCodes)) - } - } -) - -local mock_device_no_data = test.mock_device.build_test_zwave_device( - { - profile = t_utils.get_profile_definition("base-lock-tamper.yml"), - data = {} - } -) - -local expect_reload_all_codes_messages = function(dev, lc) - test.socket.capability:__expect_send(dev:generate_test_message("main", - capabilities.lockCodes.lockCodes(json.encode(lc), { visibility = { displayed = false } }) - )) - test.socket.zwave:__expect_send( UserCode:UsersNumberGet({}):build_test_tx(dev.id) ) - test.socket.capability:__expect_send(dev:generate_test_message("main", capabilities.lockCodes.scanCodes("Scanning", { visibility = { displayed = false } }))) - test.socket.zwave:__expect_send( UserCode:Get({ user_identifier = 1 }):build_test_tx(dev.id) ) -end - -test.register_coroutine_test( - "Device added data lock codes population", - function() - test.mock_device.add_test_device(mock_device) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - DoorLock:OperationGet({}) - ) - ) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - Battery:Get({}) - ) - ) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device.id, "_lock_codes", { ["1"] = "Zach", ["2"] = "Steven" }) - -- Validate state cache - assert(mock_device.state_cache.main.lockCodes.lockCodes.value == json.encode(utils.deep_copy(lockCodes))) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) - end, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Device added without data should function", - function() - test.mock_device.add_test_device(mock_device_no_data) - test.socket.device_lifecycle:__queue_receive({ mock_device_no_data.id, "added" }) - expect_reload_all_codes_messages(mock_device_no_data,{}) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device_no_data, - DoorLock:OperationGet({}) - ) - ) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device_no_data, - Battery:Get({}) - ) - ) - test.socket.capability:__expect_send(mock_device_no_data:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "_lock_codes", nil) - -- Validate state cache - assert(mock_device_no_data.state_cache.main.lockCodes.lockCodes.value == json.encode({})) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "migrationComplete", nil) - end, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Device init after added shouldn't change the datastores", - function() - test.mock_device.add_test_device(mock_device) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - DoorLock:OperationGet({}) - ) - ) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - Battery:Get({}) - ) - ) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device.id, "_lock_codes", { ["1"] = "Zach", ["2"] = "Steven" }) - -- Validate state cache - assert(mock_device.state_cache.main.lockCodes.lockCodes.value == json.encode(utils.deep_copy(lockCodes))) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device.id, "_lock_codes", { ["1"] = "Zach", ["2"] = "Steven" }) - -- Validate state cache - assert(mock_device.state_cache.main.lockCodes.lockCodes.value == json.encode(utils.deep_copy(lockCodes))) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) - end, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Device init after added with no data should update the datastores", - function() - test.mock_device.add_test_device(mock_device_no_data) - test.socket.device_lifecycle:__queue_receive({ mock_device_no_data.id, "added" }) - -- This should happen as the data is empty at this point - expect_reload_all_codes_messages(mock_device_no_data, {}) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device_no_data, - DoorLock:OperationGet({}) - ) - ) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device_no_data, - Battery:Get({}) - ) - ) - test.socket.capability:__expect_send(mock_device_no_data:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "_lock_codes", nil) - -- Validate state cache - assert(mock_device_no_data.state_cache.main.lockCodes.lockCodes.value == json.encode({})) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "migrationComplete", nil) - test.socket.device_lifecycle():__queue_receive(mock_device_no_data:generate_info_changed( - { - data = { - lockCodes = json.encode(utils.deep_copy(lockCodes)) - } - } - )) - test.socket.device_lifecycle:__queue_receive({ mock_device_no_data.id, "init" }) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "_lock_codes", { ["1"] = "Zach", ["2"] = "Steven" }) - -- Validate state cache - assert(mock_device_no_data.state_cache.main.lockCodes.lockCodes.value == json.encode(utils.deep_copy(lockCodes))) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "migrationComplete", true) - end, - { - min_api_version = 17 - } -) - - -test.register_coroutine_test( - "Device added data lock codes population, should not reload all codes", - function() - test.timer.__create_and_queue_test_time_advance_timer(31, "oneshot") - test.mock_device.add_test_device(mock_device) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - DoorLock:OperationGet({}) - ) - ) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - Battery:Get({}) - ) - ) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device.id, "_lock_codes", { ["1"] = "Zach", ["2"] = "Steven" }) - -- Validate state cache - assert(mock_device.state_cache.main.lockCodes.lockCodes.value == json.encode(utils.deep_copy(lockCodes))) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) - test.wait_for_events() - test.mock_time.advance_time(35) - -- Nothing should happen - end, - { - min_api_version = 17 - } -) - -test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_legacy.lua similarity index 97% rename from drivers/SmartThings/zwave-lock/src/test/test_zwave_lock.lua rename to drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_legacy.lua index c798feca08..66038d0760 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_legacy.lua @@ -33,10 +33,10 @@ local zwave_lock_endpoints = { } local mock_device = test.mock_device.build_test_zwave_device( - { - profile = t_utils.get_profile_definition("base-lock-tamper.yml"), - zwave_endpoints = zwave_lock_endpoints - } + { + profile = t_utils.get_profile_definition("base-lock-tamper.yml"), + zwave_endpoints = zwave_lock_endpoints + } ) local function test_init() @@ -53,31 +53,6 @@ local expect_reload_all_codes_messages = function() test.socket.zwave:__expect_send( UserCode:Get({ user_identifier = 1 }):build_test_tx(mock_device.id) ) end -test.register_coroutine_test( - "When the device is added it should be set up and start reading codes", - function() - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - - expect_reload_all_codes_messages() - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - DoorLock:OperationGet({}) - ) - ) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - Battery:Get({}) - ) - ) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) - end, - { - min_api_version = 17 - } -) - test.register_coroutine_test( "Door Lock Operation Reports should be handled", function() @@ -502,7 +477,7 @@ test.register_coroutine_test( Notification:Report({ notification_type = Notification.notification_type.ACCESS_CONTROL, event = Notification.event.access_control.KEYPAD_UNLOCK_OPERATION, - event_parameter = "" + event_parameter = "\x01" }) } ) @@ -824,6 +799,12 @@ test.register_coroutine_test( Battery:Get({}) ) ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_device, + UserCode:UsersNumberGet({}) + ) + ) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { diff --git a/drivers/SmartThings/zwave-lock/src/test/test_zwave_responses.lua b/drivers/SmartThings/zwave-lock/src/test/test_zwave_responses.lua new file mode 100644 index 0000000000..71bf25209d --- /dev/null +++ b/drivers/SmartThings/zwave-lock/src/test/test_zwave_responses.lua @@ -0,0 +1,393 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Tests for lock_handlers/zwave_responses.lua +-- Covers: +-- • user_code_report: sync-codes-from-lock flow (advances slot, completes sync) +-- • user_code_report: out-of-band ENABLED code creates user+credential entries +-- • user_code_event_handler (via Notification:Report): NEW_USER_CODE_ADDED out-of-band +-- • user_code_event_handler: multi-byte event_parameter (byte 3 carries user_id) +-- • door_operation_event_handler: KEYPAD_UNLOCK with user info lookup +-- • door_operation_event_handler: KEYPAD_UNLOCK with unknown credential +-- • door_operation_event_handler: AUTO_LOCK_LOCKED_OPERATION → locked/auto +-- • DoorLock:OperationReport DOOR_SECURED / DOOR_UNSECURED → locked / unlocked +-- • UserCode:UsersNumberReport → pinUsersSupported + totalUsersSupported +-- +-- Removed: alarm tests — z-wave alarm decoding is only in the legacy-handlers sub-driver +-- for non-SLGA devices; not applicable to the main SLGA driver path. +-- Removed: max/min PIN code length tests — no z-wave equivalent attribute. +-- Removed: NOT_FULLY_LOCKED lock_state test — no clean z-wave OperationReport equivalent. +-- Removed: NumberOfPINUsersSupported profile-migration tests — that logic lives in the +-- zigbee legacy sub-driver; users_number_report in z-wave only emits events. + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local zw = require "st.zwave" +local constants = require "lock_utils.constants" + +--- @type st.zwave.CommandClass.DoorLock +local DoorLock = (require "st.zwave.CommandClass.DoorLock")({ version = 1 }) +--- @type st.zwave.CommandClass.UserCode +local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) + +local zwave_lock_endpoints = { + { + command_classes = { + { value = zw.BATTERY }, + { value = zw.DOOR_LOCK }, + { value = zw.USER_CODE }, + { value = zw.NOTIFICATION }, + } + } +} + +local mock_device = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + zwave_endpoints = zwave_lock_endpoints, +}) + +local function test_init() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) +end +test.set_test_init_function(test_init) + +-- ============================================================================ +-- user_code_report: sync codes from lock +-- During sync (CODES_FROM_LOCK busy state), ENABLED slots advance the index +-- without adding table entries; only AVAILABLE slots remove local entries. +-- ============================================================================ + +test.register_coroutine_test( + "user_code_report: sync advances to next code slot when slot is occupied", + function() + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.SYNC.CODES_FROM_LOCK, {}) + mock_device:set_field(constants.SYNC.CODE_INDEX, 1, {}) + + -- Receive UserCode:Report for slot 1 (occupied) + test.socket.zwave:__queue_receive({ + mock_device.id, + UserCode:Report({ + user_identifier = 1, + user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS, + user_code = "1234", + }), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + )) + ) + + -- driver requests the next code slot. + test.socket.zwave:__expect_send( + UserCode:Get({ user_identifier = 2 }):build_test_tx(mock_device.id) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "user_code_report: sync completes when the last slot is reached", + function() + -- get_max_entries defaults to 20 when the attribute is missing + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.SYNC.CODES_FROM_LOCK, {}) + mock_device:set_field(constants.SYNC.CODE_INDEX, 20, {}) + + test.socket.zwave:__queue_receive({ + mock_device.id, + UserCode:Report({ + user_identifier = 20, + user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS, + user_code = "1234", + }), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 20, userName = "Guest 20", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 20, credentialIndex = 20, credentialType = "pin", credentialName = "Guest 20" } }, + { visibility = { displayed = false } } + )) + ) + test.wait_for_events() + + assert(mock_device:get_field(constants.SYNC.CODE_INDEX) == nil, + "CODE_INDEX must be nil after sync completes") + assert(mock_device:get_field(constants.DRIVER_STATE.BUSY) == false, + "BUSY must be false after sync completes") + end +) + +test.register_coroutine_test( + "user_code_report: out-of-band ENABLED code creates user and credential entries", + function() + -- command_in_progress == nil → out-of-band path + test.socket.zwave:__queue_receive({ + mock_device.id, + UserCode:Report({ + user_identifier = 1, + user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS, + user_code = "1234", + }), + }) + + -- User entry added + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + -- Credential entry added + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- user_code_event_handler (ACCESS_CONTROL Notification:Report) +-- ============================================================================ + +test.register_coroutine_test( + "user_code_event: NEW_USER_CODE_ADDED out-of-band creates user and credential entries", + function() + -- event_parameter = single byte encodes user_id = 1 + test.socket.zwave:__queue_receive({ + mock_device.id, + Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = Notification.event.access_control.NEW_USER_CODE_ADDED, + event_parameter = string.char(1), + }), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "user_code_event: multi-byte event_parameter uses byte 3 to extract user_id", + function() + -- 3-byte event_parameter: bytes are (0, 0, 2) → byte 3 = 2 → user_id = 2 + test.socket.zwave:__queue_receive({ + mock_device.id, + Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = Notification.event.access_control.NEW_USER_CODE_ADDED, + event_parameter = string.char(0, 0, 2), + }), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 2, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- door_operation_event_handler (ACCESS_CONTROL Notification:Report) +-- ============================================================================ + +test.register_coroutine_test( + "door_operation_event: KEYPAD_UNLOCK_OPERATION includes user info when credential exists", + function() + -- Seed persisted user/credential tables so the driver's lookup succeeds. + mock_device:set_field("persistedUsers", { + { userIndex = 1, userName = "John Doe", userType = "guest" }, + }, { persist = true }) + mock_device:set_field("persistedCredentials", { + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + }, { persist = true }) + + test.socket.zwave:__queue_receive({ + mock_device.id, + Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = Notification.event.access_control.KEYPAD_UNLOCK_OPERATION, + event_parameter = string.char(1), + }), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.unlocked({ + data = { + method = "keypad", + userIndex = 1, + userName = "John Doe", + userType = "guest", + }, + }) + ) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "door_operation_event: KEYPAD_UNLOCK_OPERATION with unknown credential sets userIndex from event_parameter", + function() + -- No credential seeded for slot 99 + test.socket.zwave:__queue_receive({ + mock_device.id, + Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = Notification.event.access_control.KEYPAD_UNLOCK_OPERATION, + event_parameter = string.char(99), + }), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.unlocked({ + data = { + method = "keypad", + userIndex = 99, + }, + }) + ) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "door_operation_event: AUTO_LOCK_LOCKED_OPERATION emits locked with auto method", + function() + test.socket.zwave:__queue_receive({ + mock_device.id, + Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = Notification.event.access_control.AUTO_LOCK_LOCKED_OPERATION, + event_parameter = "", + }), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.locked({ data = { method = "auto" } }) + ) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- DoorLock:OperationReport → lock state +-- Removed: NOT_FULLY_LOCKED — no clean z-wave OperationReport equivalent. +-- ============================================================================ + +test.register_coroutine_test( + "OperationReport: DOOR_SECURED emits locked event", + function() + test.socket.zwave:__queue_receive({ + mock_device.id, + DoorLock:OperationReport({ door_lock_mode = DoorLock.door_lock_mode.DOOR_SECURED }), + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.lock.locked()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "OperationReport: DOOR_UNSECURED emits unlocked event", + function() + test.socket.zwave:__queue_receive({ + mock_device.id, + DoorLock:OperationReport({ door_lock_mode = DoorLock.door_lock_mode.DOOR_UNSECURED }), + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.lock.unlocked()) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- users_number_report: UserCode:UsersNumberReport +-- ============================================================================ + +test.register_coroutine_test( + "users_number_report: emits pinUsersSupported and totalUsersSupported", + function() + test.socket.zwave:__queue_receive({ + mock_device.id, + UserCode:UsersNumberReport({ supported_users = 20 }), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.totalUsersSupported( + 20, { state_change = true, visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.pinUsersSupported( + 20, { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/can_handle.lua b/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/can_handle.lua index 7bb54f23f2..42a80c6f66 100644 --- a/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/can_handle.lua +++ b/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/can_handle.lua @@ -1,11 +1,14 @@ --- Copyright 2025 SmartThings, Inc. +-- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 -local function can_handle_v1_alarm(opts, driver, device, cmd, ...) - if opts.dispatcher_class == "ZwaveDispatcher" and cmd ~= nil and cmd.version ~= nil and cmd.version == 1 then - return true, require("zwave-alarm-v1-lock") +return function(opts, driver, device, cmd) + local consts = require("lock_utils.constants") + local slga_migrated = device:get_field(consts.DRIVER_STATE.SLGA_MIGRATED) or false + if slga_migrated then + if opts.dispatcher_class == "ZwaveDispatcher" and cmd ~= nil and cmd.version ~= nil and cmd.version == 1 then + local subdriver = require("zwave-alarm-v1-lock") + return true, subdriver + end end return false end - -return can_handle_v1_alarm diff --git a/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/init.lua b/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/init.lua index d7c862f22a..fa9836498b 100644 --- a/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/init.lua +++ b/drivers/SmartThings/zwave-lock/src/zwave-alarm-v1-lock/init.lua @@ -1,7 +1,6 @@ --- Copyright 2022 SmartThings, Inc. +-- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 - local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" @@ -9,9 +8,10 @@ local cc = require "st.zwave.CommandClass" local Alarm = (require "st.zwave.CommandClass.Alarm")({ version = 1 }) --- @type st.zwave.CommandClass.Battery local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) ---- @type st.zwave.defaults.lockCodes -local lock_code_defaults = require "st.zwave.defaults.lockCodes" -local json = require "dkjson" + +local consts = require "lock_utils.constants" +local lock_utils = require "lock_utils.utils" +local tables = require "lock_utils.tables" local METHOD = { KEYPAD = "keypad", @@ -20,12 +20,6 @@ local METHOD = { AUTO = "auto" } ---- Determine whether the passed command is a V1 alarm command ---- ---- @param driver st.zwave.Driver ---- @param device st.zwave.Device ---- @return boolean true if the device is smoke co alarm - --- Default handler for alarm command class reports, these were largely OEM-defined --- --- This converts alarm V1 reports to correct lock events @@ -35,84 +29,66 @@ local METHOD = { --- @param cmd st.zwave.CommandClass.Alarm.Report local function alarm_report_handler(driver, device, cmd) local alarm_type = cmd.args.alarm_type - local event = nil - local lock_codes = lock_code_defaults.get_lock_codes(device) - local code_id = nil - if (cmd.args.alarm_level ~= nil) then - code_id = tostring(cmd.args.alarm_level) - end + local credential_index = cmd.args.alarm_level + local event if (alarm_type == 9 or alarm_type == 17) then event = capabilities.lock.lock.unknown() elseif (alarm_type == 16 or alarm_type == 19) then event = capabilities.lock.lock.unlocked() - if (device:supports_capability(capabilities.lockCodes) and code_id ~= nil) then - local code_name = lock_code_defaults.get_code_name(device, code_id) - event.data = {codeId = code_id, codeName = code_name, method = METHOD.KEYPAD} + if (credential_index ~= nil) then + local credential = tables.find_entry(device, "credentials", credential_index) + local user_id = credential and credential.userIndex or nil + event.data = { userIndex = user_id, method = METHOD.KEYPAD} end elseif (alarm_type == 18) then event = capabilities.lock.lock.locked() - if (device:supports_capability(capabilities.lockCodes) and code_id ~= nil) then - local code_name = lock_code_defaults.get_code_name(device, code_id) - event.data = {codeId = code_id, codeName = code_name, method = METHOD.KEYPAD} + if (credential_index ~= nil) then + local credential = tables.find_entry(device, "credentials", credential_index) + local user_id = credential and credential.userIndex or nil + event.data = { userIndex = user_id, method = METHOD.KEYPAD} end elseif (alarm_type == 21) then event = capabilities.lock.lock.locked() - if (cmd.args.alarm_level == 2) then - event["data"] = {method = METHOD.MANUAL} - else - event["data"] = {method = METHOD.KEYPAD} - end + event.data = {method = (cmd.args.alarm_level == 2) and METHOD.MANUAL or METHOD.KEYPAD} elseif (alarm_type == 22) then event = capabilities.lock.lock.unlocked() - event["data"] = {method = METHOD.MANUAL} + event.data = {method = METHOD.MANUAL} elseif (alarm_type == 23) then event = capabilities.lock.lock.unknown() - event["data"] = {method = METHOD.COMMAND} + event.data = {method = METHOD.COMMAND} elseif (alarm_type == 24) then event = capabilities.lock.lock.locked() - event["data"] = {method = METHOD.COMMAND} + event.data = {method = METHOD.COMMAND} elseif (alarm_type == 25) then event = capabilities.lock.lock.unlocked() - event["data"] = {method = METHOD.COMMAND} + event.data = {method = METHOD.COMMAND} elseif (alarm_type == 26) then event = capabilities.lock.lock.unknown() - event["data"] = {method = METHOD.AUTO} + event.data = {method = METHOD.AUTO} elseif (alarm_type == 27) then event = capabilities.lock.lock.locked() - event["data"] = {method = METHOD.AUTO} + event.data = {method = METHOD.AUTO} elseif (alarm_type == 32) then - -- all user codes deleted - for code_id, _ in pairs(lock_codes) do - lock_code_defaults.code_deleted(device, code_id) - end - device:emit_event(capabilities.lockCodes.lockCodes(json.encode(lock_code_defaults.get_lock_codes(device)), { visibility = { displayed = false } })) + -- all credentials have been deleted + tables.delete_all_entries(device, "credentials") + tables.delete_all_entries(device, "users") elseif (alarm_type == 33) then - -- user code deleted - if (code_id ~= nil) then - lock_code_defaults.clear_code_state(device, code_id) - if (lock_codes[code_id] ~= nil) then - lock_code_defaults.code_deleted(device, code_id) - device:emit_event(capabilities.lockCodes.lockCodes(json.encode(lock_code_defaults.get_lock_codes(device)), { visibility = { displayed = false } })) - end - end + -- credential has been deleted + lock_utils.delete_credential_report_helper(device, credential_index) + elseif (alarm_type == 13 or alarm_type == 112) then -- user code changed/set - if (code_id ~= nil) then - local code_name = lock_code_defaults.get_code_name(device, code_id) - local change_type = lock_code_defaults.get_change_type(device, code_id) - local code_changed_event = capabilities.lockCodes.codeChanged(code_id .. change_type, { state_change = true }) - code_changed_event["data"] = { codeName = code_name} - lock_code_defaults.code_set_event(device, code_id, code_name) - lock_code_defaults.clear_code_state(device, code_id) - device:emit_event(code_changed_event) - end + lock_utils.set_credential_report_helper(device, credential_index) + elseif (alarm_type == 34 or alarm_type == 113) then - -- duplicate lock code - if (code_id ~= nil) then - local code_changed_event = capabilities.lockCodes.codeChanged(code_id .. lock_code_defaults.CHANGE_TYPE.FAILED, { state_change = true }) - lock_code_defaults.clear_code_state(device, code_id) - device:emit_event(code_changed_event) + -- duplicate lock code. Log duplicate error + local command_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + if command_in_progress then + lock_utils.emit_command_result(device, capabilities.lockCredentials, + command_in_progress, consts.COMMAND_RESULT.DUPLICATE) + lock_utils.clear_busy_state(device) end + elseif (alarm_type == 130) then -- batteries replaced if (device:is_cc_supported(cc.BATTERY)) then @@ -146,7 +122,7 @@ local zwave_lock = { } }, NAME = "Z-Wave lock alarm V1", - can_handle = require("zwave-alarm-v1-lock.can_handle"), + can_handle = require("zwave-alarm-v1-lock.can_handle") } return zwave_lock From ec88723f065abc3edae835b0c385947513e12b78 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu, 9 Jul 2026 09:52:19 -0500 Subject: [PATCH 433/538] Zigbee Lock: Support SLGA Migration (#2956) --- .../zigbee-lock/profiles/base-lock.yml | 4 + .../src/bad-battery-reporter/can_handle.lua | 13 + .../fingerprints.lua | 4 +- .../init.lua | 8 +- drivers/SmartThings/zigbee-lock/src/init.lua | 515 +++---------- .../src/legacy-handlers/can_handle.lua | 12 + .../zigbee-lock/src/legacy-handlers/init.lua | 495 ++++++++++++ .../src/legacy-handlers/legacy_lock_utils.lua | 89 +++ .../lock-without-codes/can_handle.lua | 13 + .../lock-without-codes/fingerprints.lua | 0 .../lock-without-codes/init.lua | 2 +- .../src/legacy-handlers/sub_drivers.lua | 10 + .../yale-fingerprint-lock/can_handle.lua | 13 + .../yale-fingerprint-lock/init.lua | 27 + .../{ => legacy-handlers}/yale/can_handle.lua | 2 +- .../src/{ => legacy-handlers}/yale/init.lua | 5 +- .../src/lock-without-codes/can_handle.lua | 14 - .../src/lock_handlers/capabilities.lua | 248 ++++++ .../src/lock_handlers/zigbee_responses.lua | 438 +++++++++++ .../zigbee-lock/src/lock_utils.lua | 87 --- .../zigbee-lock/src/lock_utils/constants.lua | 47 ++ .../zigbee-lock/src/lock_utils/tables.lua | 268 +++++++ .../zigbee-lock/src/lock_utils/utils.lua | 66 ++ .../zigbee-lock/src/samsungsds/init.lua | 88 ++- .../zigbee-lock/src/sub_drivers.lua | 4 +- ...rter.lua => test_bad_battery_reporter.lua} | 38 + ...st_generic_fingerprint_profile_update.lua} | 13 + .../src/test/test_init_lifecycle_handlers.lua | 484 ++++++++++++ .../test/test_lock_code_slga_migration.lua | 76 ++ .../test/test_lock_credentials_commands.lua | 578 ++++++++++++++ .../src/test/test_lock_pre_configured.lua | 653 ++++++++++++++++ .../src/test/test_lock_programming_events.lua | 574 ++++++++++++++ .../zigbee-lock/src/test/test_lock_tables.lua | 728 ++++++++++++++++++ .../src/test/test_lock_users_commands.lua | 654 ++++++++++++++++ .../test/test_zigbee_lock_code_migration.lua | 248 ------ ...e_lock.lua => test_zigbee_lock_legacy.lua} | 6 +- .../src/test/test_zigbee_responses.lua | 566 ++++++++++++++ .../src/test/test_zigbee_samsungsds.lua | 15 + .../test_zigbee_yale-bad-battery-reporter.lua | 41 - .../test_zigbee_yale-fingerprint-lock.lua | 41 - .../test_zigbee_yale_fingerprint-lock.lua | 78 ++ ...e_yale.lua => test_zigbee_yale_legacy.lua} | 0 .../src/yale-fingerprint-lock/can_handle.lua | 3 + .../src/yale-fingerprint-lock/init.lua | 8 +- .../zigbee-lock/src/yale/sub_drivers.lua | 8 - .../yale-bad-battery-reporter/can_handle.lua | 14 - 46 files changed, 6421 insertions(+), 877 deletions(-) create mode 100644 drivers/SmartThings/zigbee-lock/src/bad-battery-reporter/can_handle.lua rename drivers/SmartThings/zigbee-lock/src/{yale/yale-bad-battery-reporter => bad-battery-reporter}/fingerprints.lua (79%) rename drivers/SmartThings/zigbee-lock/src/{yale/yale-bad-battery-reporter => bad-battery-reporter}/init.lua (75%) create mode 100644 drivers/SmartThings/zigbee-lock/src/legacy-handlers/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/legacy-handlers/init.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/legacy-handlers/legacy_lock_utils.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/legacy-handlers/lock-without-codes/can_handle.lua rename drivers/SmartThings/zigbee-lock/src/{ => legacy-handlers}/lock-without-codes/fingerprints.lua (100%) rename drivers/SmartThings/zigbee-lock/src/{ => legacy-handlers}/lock-without-codes/init.lua (96%) create mode 100644 drivers/SmartThings/zigbee-lock/src/legacy-handlers/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale-fingerprint-lock/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale-fingerprint-lock/init.lua rename drivers/SmartThings/zigbee-lock/src/{ => legacy-handlers}/yale/can_handle.lua (85%) rename drivers/SmartThings/zigbee-lock/src/{ => legacy-handlers}/yale/init.lua (97%) delete mode 100644 drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/lock_handlers/capabilities.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/lock_handlers/zigbee_responses.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/lock_utils.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/lock_utils/constants.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/lock_utils/tables.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/lock_utils/utils.lua rename drivers/SmartThings/zigbee-lock/src/test/{test_yale_fingerprint_bad_battery_reporter.lua => test_bad_battery_reporter.lua} (59%) rename drivers/SmartThings/zigbee-lock/src/test/{test_generic_lock_migration.lua => test_generic_fingerprint_profile_update.lua} (56%) create mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_init_lifecycle_handlers.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_lock_code_slga_migration.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_lock_credentials_commands.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_lock_pre_configured.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_lock_programming_events.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_lock_tables.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_lock_users_commands.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua rename drivers/SmartThings/zigbee-lock/src/test/{test_zigbee_lock.lua => test_zigbee_lock_legacy.lua} (99%) create mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_zigbee_responses.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua create mode 100644 drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_fingerprint-lock.lua rename drivers/SmartThings/zigbee-lock/src/test/{test_zigbee_yale.lua => test_zigbee_yale_legacy.lua} (100%) delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua delete mode 100644 drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua diff --git a/drivers/SmartThings/zigbee-lock/profiles/base-lock.yml b/drivers/SmartThings/zigbee-lock/profiles/base-lock.yml index 159e6939f3..c9c98898c6 100644 --- a/drivers/SmartThings/zigbee-lock/profiles/base-lock.yml +++ b/drivers/SmartThings/zigbee-lock/profiles/base-lock.yml @@ -6,6 +6,10 @@ components: version: 1 - id: lockCodes version: 1 + - id: lockCredentials + version: 1 + - id: lockUsers + version: 1 - id: battery version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-lock/src/bad-battery-reporter/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/bad-battery-reporter/can_handle.lua new file mode 100644 index 0000000000..7d7d566118 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/bad-battery-reporter/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local fingerprints = require("bad-battery-reporter.fingerprints") + for _, fingerprint in ipairs(fingerprints) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("bad-battery-reporter") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/bad-battery-reporter/fingerprints.lua similarity index 79% rename from drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua rename to drivers/SmartThings/zigbee-lock/src/bad-battery-reporter/fingerprints.lua index cbb7c3404f..3024aeeace 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/fingerprints.lua +++ b/drivers/SmartThings/zigbee-lock/src/bad-battery-reporter/fingerprints.lua @@ -1,7 +1,7 @@ -- Copyright 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 -local BAD_YALE_LOCK_FINGERPRINTS = { +local BAD_BATTERY_REPORTING_LOCK_FINGERPRINTS = { { mfr = "Yale", model = "YRD220/240 TSDB" }, { mfr = "Yale", model = "YRL220 TS LL" }, { mfr = "Yale", model = "YRD210 PB DB" }, @@ -10,4 +10,4 @@ local BAD_YALE_LOCK_FINGERPRINTS = { { mfr = "ASSA ABLOY iRevo", model = "06ffff2027" } } -return BAD_YALE_LOCK_FINGERPRINTS +return BAD_BATTERY_REPORTING_LOCK_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua b/drivers/SmartThings/zigbee-lock/src/bad-battery-reporter/init.lua similarity index 75% rename from drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua rename to drivers/SmartThings/zigbee-lock/src/bad-battery-reporter/init.lua index 3b77f32563..5fbdabd1b7 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/bad-battery-reporter/init.lua @@ -11,8 +11,8 @@ local battery_report_handler = function(driver, device, value) device:emit_event(capabilities.battery.battery(value.value)) end -local bad_yale_driver = { - NAME = "YALE BAD Lock Driver", +local bad_battery_reporter_driver = { + NAME = "Bad Battery Reporter Driver", zigbee_handlers = { attr = { [clusters.PowerConfiguration.ID] = { @@ -20,7 +20,7 @@ local bad_yale_driver = { } } }, - can_handle = require("yale.yale-bad-battery-reporter.can_handle"), + can_handle = require("bad-battery-reporter.can_handle") } -return bad_yale_driver +return bad_battery_reporter_driver diff --git a/drivers/SmartThings/zigbee-lock/src/init.lua b/drivers/SmartThings/zigbee-lock/src/init.lua index 1ac2598e2f..b8b290af4d 100644 --- a/drivers/SmartThings/zigbee-lock/src/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/init.lua @@ -1,450 +1,159 @@ --- Copyright 2022 SmartThings, Inc. +-- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 - --- Zigbee Driver utilities +local ZigbeeDriver = require "st.zigbee" local defaults = require "st.zigbee.defaults" local device_management = require "st.zigbee.device_management" -local ZigbeeDriver = require "st.zigbee" - --- Zigbee Spec Utils -local clusters = require "st.zigbee.zcl.clusters" -local Alarm = clusters.Alarms -local LockCluster = clusters.DoorLock -local PowerConfiguration = clusters.PowerConfiguration - --- Capabilities -local capabilities = require "st.capabilities" -local Battery = capabilities.battery -local Lock = capabilities.lock -local LockCodes = capabilities.lockCodes - --- Enums -local UserStatusEnum = LockCluster.types.DrlkUserStatus -local UserTypeEnum = LockCluster.types.DrlkUserType -local ProgrammingEventCodeEnum = LockCluster.types.ProgramEventCode - -local socket = require "cosock.socket" -local lock_utils = require "lock_utils" - -local DELAY_LOCK_EVENT = "_delay_lock_event" -local MAX_DELAY = 10 - -local reload_all_codes = function(driver, device, command) - -- starts at first user code index then iterates through all lock codes as they come in - device:send(LockCluster.attributes.SendPINOverTheAir:write(device, true)) - if (device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodeLength.NAME) == nil) then - device:send(LockCluster.attributes.MaxPINCodeLength:read(device)) - end - if (device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.minCodeLength.NAME) == nil) then - device:send(LockCluster.attributes.MinPINCodeLength:read(device)) - end - if (device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodes.NAME) == nil) then - device:send(LockCluster.attributes.NumberOfPINUsersSupported:read(device)) - end - if (device:get_field(lock_utils.CHECKING_CODE) == nil) then device:set_field(lock_utils.CHECKING_CODE, 0) end - device:emit_event(LockCodes.scanCodes("Scanning", { visibility = { displayed = false } })) - device:send(LockCluster.server.commands.GetPINCode(device, device:get_field(lock_utils.CHECKING_CODE))) -end - -local refresh = function(driver, device, cmd) - device:refresh() - device:send(LockCluster.attributes.LockState:read(device)) - device:send(Alarm.attributes.AlarmCount:read(device)) - -- we can't determine from fingerprints if devices support lock codes, so - -- here in the driver we'll do a check once to see if the device responds here - -- and if it does, we'll switch it to a profile with lock codes - if not device:supports_capability_by_id(LockCodes.ID) and not device:get_field(lock_utils.CHECKED_CODE_SUPPORT) then - device:send(LockCluster.attributes.NumberOfPINUsersSupported:read(device)) - -- we won't make this value persist because it's not that important - device:set_field(lock_utils.CHECKED_CODE_SUPPORT, true) - end -end - -local do_configure = function(self, device) - device:send(device_management.build_bind_request(device, PowerConfiguration.ID, self.environment_info.hub_zigbee_eui)) - device:send(PowerConfiguration.attributes.BatteryPercentageRemaining:configure_reporting(device, 600, 21600, 1)) - - device:send(device_management.build_bind_request(device, LockCluster.ID, self.environment_info.hub_zigbee_eui)) - device:send(LockCluster.attributes.LockState:configure_reporting(device, 0, 3600, 0)) - - device:send(device_management.build_bind_request(device, Alarm.ID, self.environment_info.hub_zigbee_eui)) - device:send(Alarm.attributes.AlarmCount:configure_reporting(device, 0, 21600, 0)) - - -- Don't send a reload all codes if this is a part of migration - if device.data.lockCodes == nil or device:get_field(lock_utils.MIGRATION_RELOAD_SKIPPED) == true then - device.thread:call_with_delay(2, function(d) - self:inject_capability_command(device, { - capability = capabilities.lockCodes.ID, - command = capabilities.lockCodes.commands.reloadAllCodes.NAME, - args = {} - }) - end) - else - device:set_field(lock_utils.MIGRATION_RELOAD_SKIPPED, true, { persist = true }) +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" + +local consts = require "lock_utils.constants" +local lock_utils = require "lock_utils.utils" +local table_utils = require "lock_utils.tables" +local zigbee_handlers = require "lock_handlers.zigbee_responses" +local capability_handlers = require "lock_handlers.capabilities" + +local LockLifecycle = {} + +function LockLifecycle.device_added(driver, device) + if device:supports_capability(capabilities.lockCodes) and device._provisioning_state == "TYPED" then + -- set the migrated field to true so new devices use lockCredentials/lockUsers from the start. + -- auto-migration is only run for typed devices, as provisioned devices have already been onboarded, + -- and should be migrated manually by the user. + device:emit_event(capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) -- persist the migration event in the datastore end + -- set initial state + driver:inject_capability_command(device, { + capability = capabilities.refresh.ID, + command = capabilities.refresh.commands.refresh.NAME, + args = {} + }) end -local alarm_handler = function(driver, device, zb_mess) - local ALARM_REPORT = { - [0] = Lock.lock.unknown(), - [1] = Lock.lock.unknown(), - -- Events 16-19 are low battery events, but are presented as descriptionText only - } - if (ALARM_REPORT[zb_mess.body.zcl_body.alarm_code.value] ~= nil) then - device:emit_event(ALARM_REPORT[zb_mess.body.zcl_body.alarm_code.value]) +function LockLifecycle.init(driver, device) + -- Restore users/credentials capability state from the persistent store in case + -- the capability state cache was wiped since the last driver run. + table_utils.restore_from_persistent_store(device) + + local lock_pins_supported_by_profile = device:supports_capability(capabilities.lockCodes) + if lock_pins_supported_by_profile and device:get_field(consts.DRIVER_STATE.SLGA_MIGRATED) == true then + -- ensure lockCodes capability state is reflected correctly for already migrated devices + device:emit_event(capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + device:emit_event(capabilities.lockCredentials.supportedCredentials({ consts.CRED_TYPE_PIN }, { visibility = { displayed = false } })) + elseif not lock_pins_supported_by_profile then + -- generically fingerprinted profiles do not have any codes/users/credentials capabilities. + -- We should check its PIN users if it should be re-profiled. + device:send(clusters.DoorLock.attributes.NumberOfPINUsersSupported:read(device)) end end -local get_pin_response_handler = function(driver, device, zb_mess) - local event = LockCodes.codeChanged("", { state_change = true }) - local code_slot = tostring(zb_mess.body.zcl_body.user_id.value) - event.data = {codeName = lock_utils.get_code_name(device, code_slot)} - if (zb_mess.body.zcl_body.user_status.value == UserStatusEnum.OCCUPIED_ENABLED) then - -- Code slot is occupied - event.value = code_slot .. lock_utils.get_change_type(device, code_slot) - local lock_codes = lock_utils.get_lock_codes(device) - lock_codes[code_slot] = event.data.codeName - device:emit_event(event) - lock_utils.lock_codes_event(device, lock_codes) - lock_utils.reset_code_state(device, code_slot) - else - -- Code slot is unoccupied - if (lock_utils.get_lock_codes(device)[code_slot] ~= nil) then - -- Code has been deleted - lock_utils.lock_codes_event(device, lock_utils.code_deleted(device, code_slot)) - else - -- Code is unset - event.value = code_slot .. " unset" - device:emit_event(event) - end - end +function LockLifecycle.do_configure(driver, device) + device:send(device_management.build_bind_request(device, clusters.PowerConfiguration.ID, driver.environment_info.hub_zigbee_eui)) + device:send(clusters.PowerConfiguration.attributes.BatteryPercentageRemaining:configure_reporting(device, 600, 21600, 1)) - code_slot = tonumber(code_slot) - if (code_slot == device:get_field(lock_utils.CHECKING_CODE)) then - -- the code we're checking has arrived - local last_slot = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodes.NAME) - 1 - if (code_slot >= last_slot) then - device:emit_event(LockCodes.scanCodes("Complete", { visibility = { displayed = false } })) - device:set_field(lock_utils.CHECKING_CODE, nil) - else - local checkingCode = device:get_field(lock_utils.CHECKING_CODE) + 1 - device:set_field(lock_utils.CHECKING_CODE, checkingCode) - device:send(LockCluster.server.commands.GetPINCode(device, checkingCode)) - end - end -end + device:send(device_management.build_bind_request(device, clusters.DoorLock.ID, driver.environment_info.hub_zigbee_eui)) + device:send(clusters.DoorLock.attributes.LockState:configure_reporting(device, 0, 3600, 0)) -local programming_event_handler = function(driver, device, zb_mess) - local event = LockCodes.codeChanged("", { state_change = true }) - local code_slot = tostring(zb_mess.body.zcl_body.user_id.value) - event.data = {} - if (zb_mess.body.zcl_body.program_event_code.value == ProgrammingEventCodeEnum.MASTER_CODE_CHANGED) then - -- Master code changed - event.value = "0 set" - event.data = {codeName = "Master Code"} - device:emit_event(event) - elseif (zb_mess.body.zcl_body.program_event_code.value == ProgrammingEventCodeEnum.PIN_CODE_DELETED) then - if (zb_mess.body.zcl_body.user_id.value == 0xFF) then - -- All codes deleted - for cs, _ in pairs(lock_utils.get_lock_codes(device)) do - lock_utils.code_deleted(device, cs) - end - lock_utils.lock_codes_event(device, {}) - else - -- One code deleted - if (lock_utils.get_lock_codes(device)[code_slot] ~= nil) then - lock_utils.lock_codes_event(device, lock_utils.code_deleted(device, code_slot)) - end - end - elseif (zb_mess.body.zcl_body.program_event_code.value == ProgrammingEventCodeEnum.PIN_CODE_ADDED or - zb_mess.body.zcl_body.program_event_code.value == ProgrammingEventCodeEnum.PIN_CODE_CHANGED) then - -- Code added or changed - local change_type = lock_utils.get_change_type(device, code_slot) - local code_name = lock_utils.get_code_name(device, code_slot) - event.value = code_slot .. change_type - event.data = {codeName = code_name} - device:emit_event(event) - if (change_type == " set") then - local lock_codes = lock_utils.get_lock_codes(device) - lock_codes[code_slot] = code_name - lock_utils.lock_codes_event(device, lock_codes) - end - end -end + device:send(device_management.build_bind_request(device, clusters.Alarms.ID, driver.environment_info.hub_zigbee_eui)) + device:send(clusters.Alarms.attributes.AlarmCount:configure_reporting(device, 0, 21600, 0)) -local handle_max_codes = function(driver, device, value) - if value.value ~= 0 then - -- Here's where we'll end up if we queried a lock whose profile does not have lock codes, - -- but it gave us a non-zero number of pin users, so we want to switch the profile - if not device:supports_capability_by_id(LockCodes.ID) then - device:try_update_metadata({profile = "base-lock"}) -- switch to a lock with codes - lock_utils.populate_state_from_data(device) -- if this was a migrated device, try to migrate the lock codes - if not device:get_field(lock_utils.MIGRATION_COMPLETE) then -- this means we didn't find any pre-migration lock codes - -- so we'll load them manually - driver:inject_capability_command(device, { - capability = capabilities.lockCodes.ID, - command = capabilities.lockCodes.commands.reloadAllCodes.NAME, - args = {} - }) - end - end - device:emit_event(LockCodes.maxCodes(value.value, { visibility = { displayed = false } })) + if device:supports_capability(capabilities.lockCredentials) then + -- ensure our user/credential state is accurate to the current device state + device.thread:call_with_delay(15, function() lock_utils.sync_device_state(device) end) end end -local handle_max_code_length = function(driver, device, value) - device:emit_event(LockCodes.maxCodeLength(value.value, { visibility = { displayed = false } })) -end - -local handle_min_code_length = function(driver, device, value) - device:emit_event(LockCodes.minCodeLength(value.value, { visibility = { displayed = false } })) -end - -local update_codes = function(driver, device, command) - local delay = 0 - -- args.codes is json - for name, code in pairs(command.args.codes) do - -- these seem to come in the format "code[slot#]: code" - local code_slot = tonumber(string.gsub(name, "code", ""), 10) - if (code_slot ~= nil) then - if (code ~= nil and (code ~= "0" and code ~= "")) then - device.thread:call_with_delay(delay, function () - device:send(LockCluster.server.commands.SetPINCode(device, - code_slot, - UserStatusEnum.OCCUPIED_ENABLED, - UserTypeEnum.UNRESTRICTED, - code)) - end) - delay = delay + 2 - else - device.thread:call_with_delay(delay, function () - device:send(LockCluster.server.commands.ClearPINCode(device, code_slot)) - end) - delay = delay + 2 - end - device.thread:call_with_delay(delay, function(d) - device:send(LockCluster.server.commands.GetPINCode(device, code_slot)) - end) - delay = delay + 2 +function LockLifecycle.info_changed(driver, device, event, args) + local profile_switched = device.profile.id ~= args.old_st_store.profile.id + if profile_switched and device:supports_capability(capabilities.lockCodes) then + -- ensure all slga migration steps are run, and that the latest device state is synced to the driver. + device:emit_event(capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) + if device:supports_capability(capabilities.lockCredentials) then + device:emit_event(capabilities.lockCredentials.supportedCredentials({ consts.CRED_TYPE_PIN }, { visibility = { displayed = false } })) end - end -end - -local delete_code = function(driver, device, command) - device:send(LockCluster.attributes.SendPINOverTheAir:write(device, true)) - device:send(LockCluster.server.commands.ClearPINCode(device, command.args.codeSlot)) - device.thread:call_with_delay(2, function(d) - device:send(LockCluster.server.commands.GetPINCode(device, command.args.codeSlot)) - end) -end - -local request_code = function(driver, device, command) - device:send(LockCluster.server.commands.GetPINCode(device, command.args.codeSlot)) -end - -local set_code = function(driver, device, command) - if (command.args.codePIN == "") then + -- ensure all requisite initial state is set driver:inject_capability_command(device, { - capability = capabilities.lockCodes.ID, - command = capabilities.lockCodes.commands.nameSlot.NAME, - args = {command.args.codeSlot, command.args.codeName} + capability = capabilities.refresh.ID, + command = capabilities.refresh.commands.refresh.NAME, + args = {} }) - else - device:send(LockCluster.server.commands.SetPINCode(device, - command.args.codeSlot, - UserStatusEnum.OCCUPIED_ENABLED, - UserTypeEnum.UNRESTRICTED, - command.args.codePIN) - ) - if (command.args.codeName ~= nil) then - -- wait for confirmation from the lock to commit this to memory - -- Groovy driver has a lot more info passed here as a description string, may need to be investigated - local codeState = device:get_field(lock_utils.CODE_STATE) or {} - codeState["setName"..command.args.codeSlot] = command.args.codeName - device:set_field(lock_utils.CODE_STATE, codeState, { persist = true }) - end - - device.thread:call_with_delay(4, function(d) - device:send(LockCluster.server.commands.GetPINCode(device, command.args.codeSlot)) - end) + -- ensure our user/credential state is accurate to the current device state + device.thread:call_with_delay(15, function() lock_utils.sync_device_state(device) end) end end -local name_slot = function(driver, device, command) - local code_slot = tostring(command.args.codeSlot) - local lock_codes = lock_utils.get_lock_codes(device) - if (lock_codes[code_slot] ~= nil) then - lock_codes[code_slot] = command.args.codeName - device:emit_event(LockCodes.codeChanged(code_slot .. " renamed", { state_change = true })) - lock_utils.lock_codes_event(device, lock_codes) +function LockLifecycle.driver_switched(driver, device) + if device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.migrated.NAME) == true then + device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) end -end - -local function device_added(driver, device) - lock_utils.populate_state_from_data(device) - - driver:inject_capability_command(device, { - capability = capabilities.refresh.ID, - command = capabilities.refresh.commands.refresh.NAME, - args = {} - }) -end - -local function init(driver, device) - lock_utils.populate_state_from_data(device) - -- temp fix before this can be changed to non-persistent - device:set_field(lock_utils.CODE_STATE, nil, { persist = true }) -end - --- The following two functions are from the lock defaults. They are in the base driver temporarily --- until the fix is widely released in the lua libs -local lock_state_handler = function(driver, device, value, zb_rx) - local attr = capabilities.lock.lock - local LOCK_STATE = { - [value.NOT_FULLY_LOCKED] = attr.unknown(), - [value.LOCKED] = attr.locked(), - [value.UNLOCKED] = attr.unlocked(), - [value.UNDEFINED] = attr.unknown(), - } - - -- this is where we decide whether or not we need to delay our lock event because we've - -- observed it coming before the event (or we're starting to compute the timer) - local delay = device:get_field(DELAY_LOCK_EVENT) or 100 - if (delay < MAX_DELAY) then - device.thread:call_with_delay(delay+.5, function () - device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, LOCK_STATE[value.value] or attr.unknown()) - end) - else - device:set_field(DELAY_LOCK_EVENT, socket.gettime()) - device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, LOCK_STATE[value.value] or attr.unknown()) - end -end - -local lock_operation_event_handler = function(driver, device, zb_rx) - local event_code = zb_rx.body.zcl_body.operation_event_code.value - local source = zb_rx.body.zcl_body.operation_event_source.value - local OperationEventCode = require "st.zigbee.generated.zcl_clusters.DoorLock.types.OperationEventCode" - local METHOD = { - [0] = "keypad", - [1] = "command", - [2] = "manual", - [3] = "rfid", - [4] = "fingerprint", - [5] = "bluetooth" - } - local STATUS = { - [OperationEventCode.LOCK] = capabilities.lock.lock.locked(), - [OperationEventCode.UNLOCK] = capabilities.lock.lock.unlocked(), - [OperationEventCode.ONE_TOUCH_LOCK] = capabilities.lock.lock.locked(), - [OperationEventCode.KEY_LOCK] = capabilities.lock.lock.locked(), - [OperationEventCode.KEY_UNLOCK] = capabilities.lock.lock.unlocked(), - [OperationEventCode.AUTO_LOCK] = capabilities.lock.lock.locked(), - [OperationEventCode.MANUAL_LOCK] = capabilities.lock.lock.locked(), - [OperationEventCode.MANUAL_UNLOCK] = capabilities.lock.lock.unlocked(), - [OperationEventCode.SCHEDULE_LOCK] = capabilities.lock.lock.locked(), - [OperationEventCode.SCHEDULE_UNLOCK] = capabilities.lock.lock.unlocked() - } - local event = STATUS[event_code] - if (event ~= nil) then - event["data"] = {} - if (source ~= 0 and event_code == OperationEventCode.AUTO_LOCK or - event_code == OperationEventCode.SCHEDULE_LOCK or - event_code == OperationEventCode.SCHEDULE_UNLOCK - ) then - event.data.method = "auto" - else - event.data.method = METHOD[source] - end - if (source == 0 and device:supports_capability_by_id(capabilities.lockCodes.ID)) then --keypad - local code_id = zb_rx.body.zcl_body.user_id.value - local code_name = "Code "..code_id - local lock_codes = device:get_field("lockCodes") - if (lock_codes ~= nil and - lock_codes[code_id] ~= nil) then - code_name = lock_codes[code_id] - end - event.data = {method = METHOD[0], codeId = code_id .. "", codeName = code_name} - end - - -- if this is an event corresponding to a recently-received attribute report, we - -- want to set our delay timer for future lock attribute report events - if device:get_latest_state( - device:get_component_id_for_endpoint(zb_rx.address_header.src_endpoint.value), - capabilities.lock.ID, - capabilities.lock.lock.ID) == event.value.value then - local preceding_event_time = device:get_field(DELAY_LOCK_EVENT) or 0 - local time_diff = socket.gettime() - preceding_event_time - if time_diff < MAX_DELAY then - device:set_field(DELAY_LOCK_EVENT, time_diff) - end - end - - device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, event) - end -end - -local function lock(driver, device, command) - device:send_to_component(command.component, LockCluster.server.commands.LockDoor(device)) -end - -local function unlock(driver, device, command) - device:send_to_component(command.component, LockCluster.server.commands.UnlockDoor(device)) + device:try_update_metadata({ provisioning_state = "PROVISIONED" }) end local zigbee_lock_driver = { - supported_capabilities = { - Lock, - LockCodes, - Battery, + lifecycle_handlers = { + added = LockLifecycle.device_added, + init = LockLifecycle.init, + doConfigure = LockLifecycle.do_configure, + driver_switched = LockLifecycle.driver_switched, + infoChanged = LockLifecycle.info_changed, }, zigbee_handlers = { cluster = { - [Alarm.ID] = { - [Alarm.client.commands.Alarm.ID] = alarm_handler + [clusters.Alarms.ID] = { + [clusters.Alarms.client.commands.Alarm.ID] = zigbee_handlers.alarm }, - [LockCluster.ID] = { - [LockCluster.client.commands.GetPINCodeResponse.ID] = get_pin_response_handler, - [LockCluster.client.commands.ProgrammingEventNotification.ID] = programming_event_handler, - [LockCluster.client.commands.OperatingEventNotification.ID] = lock_operation_event_handler + [clusters.DoorLock.ID] = { + [clusters.DoorLock.client.commands.ClearAllPINCodesResponse.ID] = zigbee_handlers.clear_all_pin_codes_response, + [clusters.DoorLock.client.commands.ClearPINCodeResponse.ID] = zigbee_handlers.clear_pin_code_response, + [clusters.DoorLock.client.commands.GetPINCodeResponse.ID] = zigbee_handlers.get_pin_code_response, + [clusters.DoorLock.client.commands.ProgrammingEventNotification.ID] = zigbee_handlers.programming_event_notification, + [clusters.DoorLock.client.commands.OperatingEventNotification.ID] = zigbee_handlers.operating_event_notification, + [clusters.DoorLock.client.commands.SetPINCodeResponse.ID] = zigbee_handlers.set_pin_code_response, } }, attr = { - [LockCluster.ID] = { - [LockCluster.attributes.LockState.ID] = lock_state_handler, - [LockCluster.attributes.MaxPINCodeLength.ID] = handle_max_code_length, - [LockCluster.attributes.MinPINCodeLength.ID] = handle_min_code_length, - [LockCluster.attributes.NumberOfPINUsersSupported.ID] = handle_max_codes + [clusters.DoorLock.ID] = { + [clusters.DoorLock.attributes.LockState.ID] = zigbee_handlers.lock_state, + [clusters.DoorLock.attributes.MaxPINCodeLength.ID] = zigbee_handlers.max_pin_code_length, + [clusters.DoorLock.attributes.MinPINCodeLength.ID] = zigbee_handlers.min_pin_code_length, + [clusters.DoorLock.attributes.NumberOfPINUsersSupported.ID] = zigbee_handlers.number_of_pin_users_supported, } } }, capability_handlers = { - [LockCodes.ID] = { - [LockCodes.commands.updateCodes.NAME] = update_codes, - [LockCodes.commands.deleteCode.NAME] = delete_code, - [LockCodes.commands.reloadAllCodes.NAME] = reload_all_codes, - [LockCodes.commands.requestCode.NAME] = request_code, - [LockCodes.commands.setCode.NAME] = set_code, - [LockCodes.commands.nameSlot.NAME] = name_slot, + [capabilities.lock.ID] = { + [capabilities.lock.commands.lock.NAME] = capability_handlers.lock, + [capabilities.lock.commands.unlock.NAME] = capability_handlers.unlock, }, - [Lock.ID] = { - [Lock.commands.lock.NAME] = lock, - [Lock.commands.unlock.NAME] = unlock, + [capabilities.lockUsers.ID] = { + [capabilities.lockUsers.commands.addUser.NAME] = capability_handlers.add_user, + [capabilities.lockUsers.commands.updateUser.NAME] = capability_handlers.update_user, + [capabilities.lockUsers.commands.deleteUser.NAME] = capability_handlers.delete_user, + [capabilities.lockUsers.commands.deleteAllUsers.NAME] = capability_handlers.delete_all_users, + }, + [capabilities.lockCredentials.ID] = { + [capabilities.lockCredentials.commands.addCredential.NAME] = capability_handlers.add_credential, + [capabilities.lockCredentials.commands.updateCredential.NAME] = capability_handlers.update_credential, + [capabilities.lockCredentials.commands.deleteCredential.NAME] = capability_handlers.delete_credential, + [capabilities.lockCredentials.commands.deleteAllCredentials.NAME] = capability_handlers.delete_all_credentials, }, [capabilities.refresh.ID] = { - [capabilities.refresh.commands.refresh.NAME] = refresh - } + [capabilities.refresh.commands.refresh.NAME] = capability_handlers.refresh, + }, }, - sub_drivers = require("sub_drivers"), - lifecycle_handlers = { - doConfigure = do_configure, - added = device_added, - init = init, + supported_capabilities = { + capabilities.lock, + capabilities.lockCredentials, + capabilities.lockUsers, + capabilities.battery, }, + sub_drivers = require("sub_drivers"), health_check = false, shared_device_thread_enabled = true, } defaults.register_for_default_handlers(zigbee_lock_driver, zigbee_lock_driver.supported_capabilities) -local lock = ZigbeeDriver("zigbee-lock", zigbee_lock_driver) -lock:run() +local driver = ZigbeeDriver("zigbee-lock", zigbee_lock_driver) +driver:run() diff --git a/drivers/SmartThings/zigbee-lock/src/legacy-handlers/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/can_handle.lua new file mode 100644 index 0000000000..81ff8f0e8a --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local consts = require "lock_utils.constants" + local slga_migrated = device:get_field(consts.DRIVER_STATE.SLGA_MIGRATED) or false + if not slga_migrated then + local subdriver = require("legacy-handlers") + return true, subdriver + end + return false +end diff --git a/drivers/SmartThings/zigbee-lock/src/legacy-handlers/init.lua b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/init.lua new file mode 100644 index 0000000000..b9d5ce78ad --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/init.lua @@ -0,0 +1,495 @@ +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Zigbee Driver utilities +local device_management = require "st.zigbee.device_management" + +-- Zigbee Spec Utils +local clusters = require "st.zigbee.zcl.clusters" +local Alarm = clusters.Alarms +local LockCluster = clusters.DoorLock +local PowerConfiguration = clusters.PowerConfiguration + +-- Capabilities +local capabilities = require "st.capabilities" +local Battery = capabilities.battery +local Lock = capabilities.lock +local LockCodes = capabilities.lockCodes + +-- Enums +local UserStatusEnum = LockCluster.types.DrlkUserStatus +local UserTypeEnum = LockCluster.types.DrlkUserType +local ProgrammingEventCodeEnum = LockCluster.types.ProgramEventCode + +local socket = require "cosock.socket" +local lock_utils = require "legacy-handlers.legacy_lock_utils" + +local DELAY_LOCK_EVENT = "_delay_lock_event" +local MAX_DELAY = 10 + +local reload_all_codes = function(driver, device, command) + -- starts at first user code index then iterates through all lock codes as they come in + device:send(LockCluster.attributes.SendPINOverTheAir:write(device, true)) + if (device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodeLength.NAME) == nil) then + device:send(LockCluster.attributes.MaxPINCodeLength:read(device)) + end + if (device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.minCodeLength.NAME) == nil) then + device:send(LockCluster.attributes.MinPINCodeLength:read(device)) + end + if (device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodes.NAME) == nil) then + device:send(LockCluster.attributes.NumberOfPINUsersSupported:read(device)) + end + if (device:get_field(lock_utils.CHECKING_CODE) == nil) then device:set_field(lock_utils.CHECKING_CODE, 0) end + device:emit_event(LockCodes.scanCodes("Scanning", { visibility = { displayed = false } })) + device:send(LockCluster.server.commands.GetPINCode(device, device:get_field(lock_utils.CHECKING_CODE))) +end + +local refresh = function(driver, device, cmd) + device:refresh() + device:send(LockCluster.attributes.LockState:read(device)) + device:send(Alarm.attributes.AlarmCount:read(device)) + -- we can't determine from fingerprints if devices support lock codes, so + -- here in the driver we'll do a check once to see if the device responds here + -- and if it does, we'll switch it to a profile with lock codes + if not device:supports_capability_by_id(LockCodes.ID) and not device:get_field(lock_utils.CHECKED_CODE_SUPPORT) then + device:send(LockCluster.attributes.NumberOfPINUsersSupported:read(device)) + -- we won't make this value persist because it's not that important + device:set_field(lock_utils.CHECKED_CODE_SUPPORT, true) + end +end + +local do_configure = function(self, device) + device:send(device_management.build_bind_request(device, PowerConfiguration.ID, self.environment_info.hub_zigbee_eui)) + device:send(PowerConfiguration.attributes.BatteryPercentageRemaining:configure_reporting(device, 600, 21600, 1)) + + device:send(device_management.build_bind_request(device, LockCluster.ID, self.environment_info.hub_zigbee_eui)) + device:send(LockCluster.attributes.LockState:configure_reporting(device, 0, 3600, 0)) + + device:send(device_management.build_bind_request(device, Alarm.ID, self.environment_info.hub_zigbee_eui)) + device:send(Alarm.attributes.AlarmCount:configure_reporting(device, 0, 21600, 0)) + + -- Don't send a reload all codes if this is a part of migration + if device.data.lockCodes == nil or device:get_field(lock_utils.MIGRATION_RELOAD_SKIPPED) == true then + device.thread:call_with_delay(2, function(d) + self:inject_capability_command(device, { + capability = capabilities.lockCodes.ID, + command = capabilities.lockCodes.commands.reloadAllCodes.NAME, + args = {} + }) + end) + else + device:set_field(lock_utils.MIGRATION_RELOAD_SKIPPED, true, { persist = true }) + end +end + +local alarm_handler = function(driver, device, zb_mess) + local ALARM_REPORT = { + [0] = Lock.lock.unknown(), + [1] = Lock.lock.unknown(), + -- Events 16-19 are low battery events, but are presented as descriptionText only + } + if (ALARM_REPORT[zb_mess.body.zcl_body.alarm_code.value] ~= nil) then + device:emit_event(ALARM_REPORT[zb_mess.body.zcl_body.alarm_code.value]) + end +end + +local get_pin_response_handler = function(driver, device, zb_mess) + local event = LockCodes.codeChanged("", { state_change = true }) + local code_slot = tostring(zb_mess.body.zcl_body.user_id.value) + event.data = {codeName = lock_utils.get_code_name(device, code_slot)} + if (zb_mess.body.zcl_body.user_status.value == UserStatusEnum.OCCUPIED_ENABLED) then + -- Code slot is occupied + event.value = code_slot .. lock_utils.get_change_type(device, code_slot) + local lock_codes = lock_utils.get_lock_codes(device) + lock_codes[code_slot] = event.data.codeName + device:emit_event(event) + lock_utils.lock_codes_event(device, lock_codes) + lock_utils.reset_code_state(device, code_slot) + else + -- Code slot is unoccupied + if (lock_utils.get_lock_codes(device)[code_slot] ~= nil) then + -- Code has been deleted + lock_utils.lock_codes_event(device, lock_utils.code_deleted(device, code_slot)) + else + -- Code is unset + event.value = code_slot .. " unset" + device:emit_event(event) + end + end + + code_slot = tonumber(code_slot) + if (code_slot == device:get_field(lock_utils.CHECKING_CODE)) then + -- the code we're checking has arrived + local last_slot = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodes.NAME) - 1 + if (code_slot >= last_slot) then + device:emit_event(LockCodes.scanCodes("Complete", { visibility = { displayed = false } })) + device:set_field(lock_utils.CHECKING_CODE, nil) + else + local checkingCode = device:get_field(lock_utils.CHECKING_CODE) + 1 + device:set_field(lock_utils.CHECKING_CODE, checkingCode) + device:send(LockCluster.server.commands.GetPINCode(device, checkingCode)) + end + end +end + +local programming_event_handler = function(driver, device, zb_mess) + local event = LockCodes.codeChanged("", { state_change = true }) + local code_slot = tostring(zb_mess.body.zcl_body.user_id.value) + event.data = {} + if (zb_mess.body.zcl_body.program_event_code.value == ProgrammingEventCodeEnum.MASTER_CODE_CHANGED) then + -- Master code changed + event.value = "0 set" + event.data = {codeName = "Master Code"} + device:emit_event(event) + elseif (zb_mess.body.zcl_body.program_event_code.value == ProgrammingEventCodeEnum.PIN_CODE_DELETED) then + if (zb_mess.body.zcl_body.user_id.value == 0xFF) then + -- All codes deleted + for cs, _ in pairs(lock_utils.get_lock_codes(device)) do + lock_utils.code_deleted(device, cs) + end + lock_utils.lock_codes_event(device, {}) + else + -- One code deleted + if (lock_utils.get_lock_codes(device)[code_slot] ~= nil) then + lock_utils.lock_codes_event(device, lock_utils.code_deleted(device, code_slot)) + end + end + elseif (zb_mess.body.zcl_body.program_event_code.value == ProgrammingEventCodeEnum.PIN_CODE_ADDED or + zb_mess.body.zcl_body.program_event_code.value == ProgrammingEventCodeEnum.PIN_CODE_CHANGED) then + -- Code added or changed + local change_type = lock_utils.get_change_type(device, code_slot) + local code_name = lock_utils.get_code_name(device, code_slot) + event.value = code_slot .. change_type + event.data = {codeName = code_name} + device:emit_event(event) + if (change_type == " set") then + local lock_codes = lock_utils.get_lock_codes(device) + lock_codes[code_slot] = code_name + lock_utils.lock_codes_event(device, lock_codes) + end + end +end + +local handle_max_codes = function(driver, device, value) + if value.value ~= 0 then + -- Here's where we'll end up if we queried a lock whose profile does not have lock codes, + -- but it gave us a non-zero number of pin users, so we want to switch the profile + if not device:supports_capability_by_id(LockCodes.ID) then + device:try_update_metadata({ profile = "base-lock" }) -- switch to a lock with codes + lock_utils.populate_state_from_data(device) -- if this was a migrated device, try to migrate the lock codes + if not device:get_field(lock_utils.MIGRATION_COMPLETE) then -- this means we didn't find any pre-migration lock codes + -- so we'll load them manually + driver:inject_capability_command(device, { + capability = capabilities.lockCodes.ID, + command = capabilities.lockCodes.commands.reloadAllCodes.NAME, + args = {} + }) + end + end + device:emit_event(LockCodes.maxCodes(value.value, { visibility = { displayed = false } })) + end +end + +local handle_max_code_length = function(driver, device, value) + device:emit_event(LockCodes.maxCodeLength(value.value, { visibility = { displayed = false } })) +end + +local handle_min_code_length = function(driver, device, value) + device:emit_event(LockCodes.minCodeLength(value.value, { visibility = { displayed = false } })) +end + +local update_codes = function(driver, device, command) + local delay = 0 + -- args.codes is json + for name, code in pairs(command.args.codes) do + -- these seem to come in the format "code[slot#]: code" + local code_slot = tonumber(string.gsub(name, "code", ""), 10) + if (code_slot ~= nil) then + if (code ~= nil and (code ~= "0" and code ~= "")) then + device.thread:call_with_delay(delay, function() + device:send(LockCluster.server.commands.SetPINCode(device, + code_slot, + UserStatusEnum.OCCUPIED_ENABLED, + UserTypeEnum.UNRESTRICTED, + code)) + end) + delay = delay + 2 + else + device.thread:call_with_delay(delay, function() + device:send(LockCluster.server.commands.ClearPINCode(device, code_slot)) + end) + delay = delay + 2 + end + device.thread:call_with_delay(delay, function(d) + device:send(LockCluster.server.commands.GetPINCode(device, code_slot)) + end) + delay = delay + 2 + end + end +end + +local delete_code = function(driver, device, command) + device:send(LockCluster.attributes.SendPINOverTheAir:write(device, true)) + device:send(LockCluster.server.commands.ClearPINCode(device, command.args.codeSlot)) + device.thread:call_with_delay(2, function(d) + device:send(LockCluster.server.commands.GetPINCode(device, command.args.codeSlot)) + end) +end + +local request_code = function(driver, device, command) + device:send(LockCluster.server.commands.GetPINCode(device, command.args.codeSlot)) +end + +local set_code = function(driver, device, command) + if (command.args.codePIN == "") then + driver:inject_capability_command(device, { + capability = capabilities.lockCodes.ID, + command = capabilities.lockCodes.commands.nameSlot.NAME, + args = { command.args.codeSlot, command.args.codeName } + }) + else + device:send(LockCluster.server.commands.SetPINCode(device, + command.args.codeSlot, + UserStatusEnum.OCCUPIED_ENABLED, + UserTypeEnum.UNRESTRICTED, + command.args.codePIN) + ) + if (command.args.codeName ~= nil) then + -- wait for confirmation from the lock to commit this to memory + -- Groovy driver has a lot more info passed here as a description string, may need to be investigated + local codeState = device:get_field(lock_utils.CODE_STATE) or {} + codeState["setName" .. command.args.codeSlot] = command.args.codeName + device:set_field(lock_utils.CODE_STATE, codeState, { persist = true }) + end + + device.thread:call_with_delay(4, function(d) + device:send(LockCluster.server.commands.GetPINCode(device, command.args.codeSlot)) + end) + end +end + +local name_slot = function(driver, device, command) + local code_slot = tostring(command.args.codeSlot) + local lock_codes = lock_utils.get_lock_codes(device) + if (lock_codes[code_slot] ~= nil) then + lock_codes[code_slot] = command.args.codeName + device:emit_event(LockCodes.codeChanged(code_slot .. " renamed", { state_change = true })) + lock_utils.lock_codes_event(device, lock_codes) + end +end + +local function init(driver, device) + lock_utils.populate_state_from_data(device) + -- temp fix before this can be changed to non-persistent + device:set_field(lock_utils.CODE_STATE, nil, { persist = true }) +end + +-- The following two functions are from the lock defaults. They are in the base driver temporarily +-- until the fix is widely released in the lua libs +local lock_state_handler = function(driver, device, value, zb_rx) + local attr = capabilities.lock.lock + local LOCK_STATE = { + [value.NOT_FULLY_LOCKED] = attr.unknown(), + [value.LOCKED] = attr.locked(), + [value.UNLOCKED] = attr.unlocked(), + [value.UNDEFINED] = attr.unknown(), + } + + -- this is where we decide whether or not we need to delay our lock event because we've + -- observed it coming before the event (or we're starting to compute the timer) + local delay = device:get_field(DELAY_LOCK_EVENT) or 100 + if (delay < MAX_DELAY) then + device.thread:call_with_delay(delay+.5, function () + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, LOCK_STATE[value.value] or attr.unknown()) + end) + else + device:set_field(DELAY_LOCK_EVENT, socket.gettime()) + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, LOCK_STATE[value.value] or attr.unknown()) + end +end + +local lock_operation_event_handler = function(driver, device, zb_rx) + local event_code = zb_rx.body.zcl_body.operation_event_code.value + local source = zb_rx.body.zcl_body.operation_event_source.value + local OperationEventCode = require "st.zigbee.generated.zcl_clusters.DoorLock.types.OperationEventCode" + local METHOD = { + [0] = "keypad", + [1] = "command", + [2] = "manual", + [3] = "rfid", + [4] = "fingerprint", + [5] = "bluetooth" + } + local STATUS = { + [OperationEventCode.LOCK] = capabilities.lock.lock.locked(), + [OperationEventCode.UNLOCK] = capabilities.lock.lock.unlocked(), + [OperationEventCode.ONE_TOUCH_LOCK] = capabilities.lock.lock.locked(), + [OperationEventCode.KEY_LOCK] = capabilities.lock.lock.locked(), + [OperationEventCode.KEY_UNLOCK] = capabilities.lock.lock.unlocked(), + [OperationEventCode.AUTO_LOCK] = capabilities.lock.lock.locked(), + [OperationEventCode.MANUAL_LOCK] = capabilities.lock.lock.locked(), + [OperationEventCode.MANUAL_UNLOCK] = capabilities.lock.lock.unlocked(), + [OperationEventCode.SCHEDULE_LOCK] = capabilities.lock.lock.locked(), + [OperationEventCode.SCHEDULE_UNLOCK] = capabilities.lock.lock.unlocked() + } + local event = STATUS[event_code] + if (event ~= nil) then + event["data"] = {} + if (source ~= 0 and event_code == OperationEventCode.AUTO_LOCK or + event_code == OperationEventCode.SCHEDULE_LOCK or + event_code == OperationEventCode.SCHEDULE_UNLOCK + ) then + event.data.method = "auto" + else + event.data.method = METHOD[source] + end + if (source == 0 and device:supports_capability_by_id(capabilities.lockCodes.ID)) then --keypad + local code_id = zb_rx.body.zcl_body.user_id.value + local code_name = "Code " .. code_id + local lock_codes = device:get_field("lockCodes") + if (lock_codes ~= nil and + lock_codes[code_id] ~= nil) then + code_name = lock_codes[code_id] + end + event.data = { method = METHOD[0], codeId = code_id .. "", codeName = code_name } + end + + -- if this is an event corresponding to a recently-received attribute report, we + -- want to set our delay timer for future lock attribute report events + if device:get_latest_state( + device:get_component_id_for_endpoint(zb_rx.address_header.src_endpoint.value), + capabilities.lock.ID, + capabilities.lock.lock.ID) == event.value.value then + local preceding_event_time = device:get_field(DELAY_LOCK_EVENT) or 0 + local time_diff = socket.gettime() - preceding_event_time + if time_diff < MAX_DELAY then + device:set_field(DELAY_LOCK_EVENT, time_diff) + end + end + + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, event) + end +end + +local function lock(driver, device, command) + device:send_to_component(command.component, LockCluster.server.commands.LockDoor(device)) +end + +local function unlock(driver, device, command) + device:send_to_component(command.component, LockCluster.server.commands.UnlockDoor(device)) +end + +local migrate = function(driver, device, command) + local post_migration_consts = require "lock_utils.constants" + local LockUsers = capabilities.lockUsers + local LockCredentials = capabilities.lockCredentials + + -- set supported credentials + device:emit_event(LockCredentials.supportedCredentials({ post_migration_consts.CRED_TYPE_PIN }, { visibility = { displayed = false } })) + + -- migrate max/min credential length + local cached_min_code_len = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.minCodeLength.NAME) + local cached_max_code_len = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodeLength.NAME) + if cached_min_code_len then + device:emit_event(LockCredentials.minPinCodeLen(cached_min_code_len, { visibility = { displayed = false } })) + end + if cached_max_code_len then + device:emit_event(LockCredentials.maxPinCodeLen(cached_max_code_len, { visibility = { displayed = false } })) + end + + -- migrate total codes supported + local cached_max_codes = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodes.NAME) + if cached_max_codes then + device:emit_event(LockCredentials.pinUsersSupported(cached_max_codes, { visibility = { displayed = false } })) + device:emit_event(LockUsers.totalUsersSupported(cached_max_codes, { visibility = { displayed = false } })) + end + + -- migrate stored lock codes slots and user names + local users, credentials = {}, {} + local cached_lock_codes = lock_utils.get_lock_codes(device) + local ordered_code_slots = {} + for code_slot_str, _ in pairs(cached_lock_codes) do + table.insert(ordered_code_slots, tonumber(code_slot_str)) + end + table.sort(ordered_code_slots) + for _, code_slot in ipairs(ordered_code_slots) do + local code_name = cached_lock_codes[tostring(code_slot)] + -- cached_lock_codes is formatted as { ["1"] = "Guest 1", ["2"] = "Guest 2" }, etc + if code_slot then + table.insert(users, { userIndex = code_slot, userType = "guest", userName = code_name }) + table.insert(credentials, { userIndex = code_slot, credentialIndex = code_slot, credentialType = post_migration_consts.CRED_TYPE_PIN, credentialName = code_name }) + end + end + -- manually ensure user/cred state is persisted, then emit events to populate the new capabilities with the migrated data. + device:set_field("persistedUsers", users, { persist = true }) + device:set_field("persistedCredentials", credentials, { persist = true }) + device:emit_event(LockUsers.users(users, { visibility = { displayed = false } })) + device:emit_event(LockCredentials.credentials(credentials, { visibility = { displayed = false } })) + + -- set and persist the migration complete flag and emit migrated event. Legacy subdriver will not be used again after this. + device:emit_event(LockCodes.migrated(true, { visibility = { displayed = false } })) + device:set_field(post_migration_consts.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) + + -- try to refresh all unstored state + driver:inject_capability_command(device, { + capability = capabilities.refresh.ID, + command = capabilities.refresh.commands.refresh.NAME, + args = {} + }) +end + +local legacy_capabilities_driver = { + NAME = "Lock Driver Using LockCodes Capability", + supported_capabilities = { + Lock, + LockCodes, + Battery, + }, + zigbee_handlers = { + cluster = { + [Alarm.ID] = { + [Alarm.client.commands.Alarm.ID] = alarm_handler + }, + [LockCluster.ID] = { + [LockCluster.client.commands.GetPINCodeResponse.ID] = get_pin_response_handler, + [LockCluster.client.commands.ProgrammingEventNotification.ID] = programming_event_handler, + [LockCluster.client.commands.OperatingEventNotification.ID] = lock_operation_event_handler + } + }, + attr = { + [LockCluster.ID] = { + [LockCluster.attributes.LockState.ID] = lock_state_handler, + [LockCluster.attributes.MaxPINCodeLength.ID] = handle_max_code_length, + [LockCluster.attributes.MinPINCodeLength.ID] = handle_min_code_length, + [LockCluster.attributes.NumberOfPINUsersSupported.ID] = handle_max_codes, + } + } + }, + capability_handlers = { + [LockCodes.ID] = { + [LockCodes.commands.updateCodes.NAME] = update_codes, + [LockCodes.commands.deleteCode.NAME] = delete_code, + [LockCodes.commands.reloadAllCodes.NAME] = reload_all_codes, + [LockCodes.commands.requestCode.NAME] = request_code, + [LockCodes.commands.setCode.NAME] = set_code, + [LockCodes.commands.nameSlot.NAME] = name_slot, + [LockCodes.commands.migrate.NAME] = migrate, + }, + [Lock.ID] = { + [Lock.commands.lock.NAME] = lock, + [Lock.commands.unlock.NAME] = unlock, + }, + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = refresh + } + }, + sub_drivers = require("legacy-handlers.sub_drivers"), + lifecycle_handlers = { + doConfigure = do_configure, + init = init, + }, + health_check = false, + can_handle = require("legacy-handlers.can_handle") +} + +return legacy_capabilities_driver diff --git a/drivers/SmartThings/zigbee-lock/src/legacy-handlers/legacy_lock_utils.lua b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/legacy_lock_utils.lua new file mode 100644 index 0000000000..6afa0aea3a --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/legacy_lock_utils.lua @@ -0,0 +1,89 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local utils = require "st.utils" +local capabilities = require "st.capabilities" +local json = require "st.json" +local LockCodes = capabilities.lockCodes + + +local legacy_lock_utils = { + -- Constants + LOCK_CODES = "lockCodes", + LOCK_USERS = "lockUsers", + CHECKING_CODE = "checkingCode", + CODE_STATE = "codeState", + MIGRATION_COMPLETE = "migrationComplete", + MIGRATION_RELOAD_SKIPPED = "migrationReloadSkipped", + CHECKED_CODE_SUPPORT = "checkedCodeSupport", +} + +legacy_lock_utils.get_lock_codes = function(device) + local lc = device:get_field(legacy_lock_utils.LOCK_CODES) + return lc ~= nil and lc or {} +end + +legacy_lock_utils.lock_codes_event = function(device, lock_codes) + device:set_field(legacy_lock_utils.LOCK_CODES, lock_codes, { persist = true } ) + device:emit_event(capabilities.lockCodes.lockCodes(json.encode(utils.deep_copy(lock_codes)), { visibility = { displayed = false } })) +end + + +function legacy_lock_utils.get_code_name(device, code_id) + if (device:get_field(legacy_lock_utils.CODE_STATE) ~= nil and device:get_field(legacy_lock_utils.CODE_STATE)["setName"..code_id] ~= nil) then + -- this means a code set operation succeeded + return device:get_field(legacy_lock_utils.CODE_STATE)["setName"..code_id] + elseif (legacy_lock_utils.get_lock_codes(device)[code_id] ~= nil) then + return legacy_lock_utils.get_lock_codes(device)[code_id] + else + return "Code " .. code_id + end +end + +function legacy_lock_utils.get_change_type(device, code_id) + if (legacy_lock_utils.get_lock_codes(device)[code_id] == nil) then + return " set" + else + return " changed" + end +end + +function legacy_lock_utils.reset_code_state(device, code_slot) + local codeState = device:get_field(legacy_lock_utils.CODE_STATE) + if (codeState ~= nil) then + codeState["setName".. code_slot] = nil + codeState["setCode".. code_slot] = nil + device:set_field(legacy_lock_utils.CODE_STATE, codeState, { persist = true }) + end +end + +function legacy_lock_utils.code_deleted(device, code_slot) + local lock_codes = legacy_lock_utils.get_lock_codes(device) + local event = LockCodes.codeChanged(code_slot.." deleted", { state_change = true }) + event.data = {codeName = legacy_lock_utils.get_code_name(device, code_slot)} + lock_codes[code_slot] = nil + device:emit_event(event) + legacy_lock_utils.reset_code_state(device, code_slot) + return lock_codes +end + +function legacy_lock_utils.populate_state_from_data(device) + if device.data.lockCodes ~= nil and device:get_field(legacy_lock_utils.MIGRATION_COMPLETE) ~= true then + -- build the lockCodes table + local lockCodes = {} + local lc_data = json.decode(device.data.lockCodes) + for k, v in pairs(lc_data) do + lockCodes[k] = v + end + -- Populate the devices `lockCodes` field + device:set_field(legacy_lock_utils.LOCK_CODES, utils.deep_copy(lockCodes), { persist = true }) + -- Populate the devices state history cache + device.state_cache["main"] = device.state_cache["main"] or {} + device.state_cache["main"][capabilities.lockCodes.ID] = device.state_cache["main"][capabilities.lockCodes.ID] or {} + device.state_cache["main"][capabilities.lockCodes.ID][capabilities.lockCodes.lockCodes.NAME] = {value = json.encode(utils.deep_copy(lockCodes))} + + device:set_field(legacy_lock_utils.MIGRATION_COMPLETE, true, { persist = true }) + end +end + +return legacy_lock_utils diff --git a/drivers/SmartThings/zigbee-lock/src/legacy-handlers/lock-without-codes/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/lock-without-codes/can_handle.lua new file mode 100644 index 0000000000..326df876df --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/lock-without-codes/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, cmd) + local fingerprints = require("legacy-handlers.lock-without-codes.fingerprints") + for _, fingerprint in ipairs(fingerprints) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("legacy-handlers.lock-without-codes") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/lock-without-codes/fingerprints.lua similarity index 100% rename from drivers/SmartThings/zigbee-lock/src/lock-without-codes/fingerprints.lua rename to drivers/SmartThings/zigbee-lock/src/legacy-handlers/lock-without-codes/fingerprints.lua diff --git a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/lock-without-codes/init.lua similarity index 96% rename from drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua rename to drivers/SmartThings/zigbee-lock/src/legacy-handlers/lock-without-codes/init.lua index e5c6de3408..4522580297 100644 --- a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/lock-without-codes/init.lua @@ -73,7 +73,7 @@ local lock_without_codes = { } } }, - can_handle = require("lock-without-codes.can_handle"), + can_handle = require("legacy-handlers.lock-without-codes.can_handle") } return lock_without_codes diff --git a/drivers/SmartThings/zigbee-lock/src/legacy-handlers/sub_drivers.lua b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/sub_drivers.lua new file mode 100644 index 0000000000..8cbfd6701f --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/sub_drivers.lua @@ -0,0 +1,10 @@ +-- Copyright 2026 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("legacy-handlers.yale"), + lazy_load_if_possible("legacy-handlers.yale-fingerprint-lock"), + lazy_load_if_possible("legacy-handlers.lock-without-codes") +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale-fingerprint-lock/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale-fingerprint-lock/can_handle.lua new file mode 100644 index 0000000000..3baa8f368c --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale-fingerprint-lock/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local fingerprints = require("yale-fingerprint-lock.fingerprints") + for _, fingerprint in ipairs(fingerprints) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("legacy-handlers.yale-fingerprint-lock") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale-fingerprint-lock/init.lua b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale-fingerprint-lock/init.lua new file mode 100644 index 0000000000..7e982fd943 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale-fingerprint-lock/init.lua @@ -0,0 +1,27 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local LockCluster = clusters.DoorLock +local LockCodes = capabilities.lockCodes + +local YALE_FINGERPRINT_MAX_CODES = 0x1E +local handle_max_codes = function(driver, device, value) + device:emit_event(LockCodes.maxCodes(YALE_FINGERPRINT_MAX_CODES), { visibility = { displayed = false } }) +end + +local yale_fingerprint_lock_driver = { + NAME = "YALE Fingerprint Lock", + zigbee_handlers = { + attr = { + [LockCluster.ID] = { + [LockCluster.attributes.NumberOfPINUsersSupported.ID] = handle_max_codes + } + } + }, + can_handle = require("legacy-handlers.yale-fingerprint-lock.can_handle") +} + +return yale_fingerprint_lock_driver diff --git a/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale/can_handle.lua similarity index 85% rename from drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua rename to drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale/can_handle.lua index 54340c7811..832914f6ea 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/can_handle.lua +++ b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale/can_handle.lua @@ -3,7 +3,7 @@ local function yale_can_handle(opts, driver, device, ...) if device:get_manufacturer() == "ASSA ABLOY iRevo" or device:get_manufacturer() == "Yale" then - return true, require("yale") + return true, require("legacy-handlers.yale") end return false end diff --git a/drivers/SmartThings/zigbee-lock/src/yale/init.lua b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale/init.lua similarity index 97% rename from drivers/SmartThings/zigbee-lock/src/yale/init.lua rename to drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale/init.lua index c315fbfa06..a85a9f8798 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/legacy-handlers/yale/init.lua @@ -15,7 +15,7 @@ local LockCodes = capabilities.lockCodes local UserStatusEnum = LockCluster.types.DrlkUserStatus local UserTypeEnum = LockCluster.types.DrlkUserType -local lock_utils = (require "lock_utils") +local lock_utils = (require "legacy-handlers.legacy_lock_utils") local reload_all_codes = function(driver, device, command) -- starts at first user code index then iterates through all lock codes as they come in @@ -142,8 +142,7 @@ local yale_door_lock_driver = { [LockCodes.commands.setCode.NAME] = set_code } }, - sub_drivers = require("yale.sub_drivers"), - can_handle = require("yale.can_handle"), + can_handle = require("legacy-handlers.yale.can_handle") } return yale_door_lock_driver diff --git a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua deleted file mode 100644 index 543e43a8b1..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/lock-without-codes/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function can_handle_lock_without_codes(opts, driver, device) - local FINGERPRINTS = require("lock-without-codes.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true, require("lock-without-codes") - end - end - return false -end - -return can_handle_lock_without_codes diff --git a/drivers/SmartThings/zigbee-lock/src/lock_handlers/capabilities.lua b/drivers/SmartThings/zigbee-lock/src/lock_handlers/capabilities.lua new file mode 100644 index 0000000000..371cf7a765 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/lock_handlers/capabilities.lua @@ -0,0 +1,248 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local lock_utils = require "lock_utils.utils" +local tables = require "lock_utils.tables" +local consts = require "lock_utils.constants" + +local Alarm = clusters.Alarms +local LockCluster = clusters.DoorLock +local UserStatusEnum = LockCluster.types.DrlkUserStatus +local UserTypeEnum = LockCluster.types.DrlkUserType + + +local CapabilityHandlers = {} + + +-- [[ LOCK CAPABILITY COMMANDS ]] -- + +function CapabilityHandlers.lock(driver, device, command) + device:send_to_component(command.component, LockCluster.server.commands.LockDoor(device)) +end + +function CapabilityHandlers.unlock(driver, device, command) + device:send_to_component(command.component, LockCluster.server.commands.UnlockDoor(device)) +end + + +-- [[ LOCK USERS CAPABILITY COMMANDS ]] -- + +function CapabilityHandlers.add_user(driver, device, command) + if lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.ADD, consts.COMMAND_RESULT.BUSY) + return + end + + -- Find the smallest positive userIndex not already in the table + local next_available_index = tables.next_index(device, "users") + local status = tables.add_entry(device, "users", { + userIndex = next_available_index, + userName = command.args.userName, + userType = command.args.userType, + }) + local additional_info = status == consts.COMMAND_RESULT.SUCCESS and { userIndex = next_available_index } + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.ADD, status, additional_info) +end + +function CapabilityHandlers.update_user(driver, device, command) + if lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.UPDATE, consts.COMMAND_RESULT.BUSY) + return + end + + local result_status + if not tables.find_entry(device, "users", command.args.userIndex) then + result_status = tables.add_entry(device, "users", { + userIndex = command.args.userIndex, + userName = command.args.userName, + userType = command.args.userType, + }) + else + result_status = tables.update_entry(device, "users", + command.args.userIndex, + { userName = command.args.userName, userType = command.args.userType } + ) + end + + local additional_info = result_status == consts.COMMAND_RESULT.SUCCESS and { userIndex = command.args.userIndex } + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.UPDATE, result_status, additional_info) +end + +function CapabilityHandlers.delete_user(driver, device, command) + if lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.DELETE, consts.COMMAND_RESULT.BUSY) + return + end + + local associated_credentials = tables.find_all_entries_by(device, "credentials", "userIndex", command.args.userIndex) + for _, associated_credential in ipairs(associated_credentials) do + -- Set busy state with the full user+credential context BEFORE injecting. + -- Injected capability commands are schema-validated, so extra args like userIndex + -- would be stripped. By setting device fields here we preserve the full context. + lock_utils.set_busy_state(device, consts.LOCK_USERS.DELETE, { + userIndex = command.args.userIndex, + credentialIndex = associated_credential.credentialIndex, + credentialType = consts.CRED_TYPE_PIN, + }) + driver:inject_capability_command(device, { + capability = capabilities.lockCredentials.ID, + command = capabilities.lockCredentials.commands.deleteCredential.NAME, + args = { + credentialIndex = associated_credential.credentialIndex, + credentialType = consts.CRED_TYPE_PIN, + } + }) + end + if #associated_credentials == 0 then + -- No associated credentials: delete the user entry directly and report the result + local status = tables.delete_entry(device, "users", command.args.userIndex) + local additional_info = status == consts.COMMAND_RESULT.SUCCESS and { userIndex = command.args.userIndex } + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.DELETE, status, additional_info) + end +end + +function CapabilityHandlers.delete_all_users(driver, device, command) + if lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.DELETE_ALL, consts.COMMAND_RESULT.BUSY) + return + end + -- Set busy state with DELETE_ALL context BEFORE injecting so the response handler + -- knows to clear both tables and emit results for both capabilities. + lock_utils.set_busy_state(device, consts.LOCK_USERS.DELETE_ALL, {}) + driver:inject_capability_command(device, { + capability = capabilities.lockCredentials.ID, + command = capabilities.lockCredentials.commands.deleteAllCredentials.NAME, + args = { credentialType = consts.CRED_TYPE_PIN } + }) +end + + +-- [[ LOCK CREDENTIALS CAPABILITY COMMANDS ]] -- + +function CapabilityHandlers.add_credential(driver, device, command) + if lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.ADD, consts.COMMAND_RESULT.BUSY) + return + end + + -- A userIndex of 0 means "auto-assign the next available slot" + local user_index = command.args.userIndex == 0 and tables.next_index(device, "users") or command.args.userIndex + local cred_index = command.args.userIndex == 0 and tables.next_index(device, "credentials") or command.args.userIndex + + if #(tables.get_state(device, "credentials") or {}) >= tables.get_max_entries(device, "credentials") then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.ADD, consts.COMMAND_RESULT.RESOURCE_EXHAUSTED) + return + end + + -- Set busy state and attempt to set the PIN on-device. + lock_utils.set_busy_state(device, consts.LOCK_CREDENTIALS.ADD, { + userIndex = user_index, + credentialIndex = cred_index, + credentialType = command.args.credentialType, + credentialName = command.args.credentialName, + }) + device:send(LockCluster.server.commands.SetPINCode(device, + cred_index, + UserStatusEnum.OCCUPIED_ENABLED, + UserTypeEnum.UNRESTRICTED, + command.args.credentialData) + ) +end + +function CapabilityHandlers.update_credential(driver, device, command) + if lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.UPDATE, consts.COMMAND_RESULT.BUSY) + return + end + + if not tables.find_entry(device, "credentials", command.args.credentialIndex) then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.UPDATE, consts.COMMAND_RESULT.FAILURE) + return + end + + lock_utils.set_busy_state(device, consts.LOCK_CREDENTIALS.UPDATE, { + userIndex = command.args.userIndex, + credentialIndex = command.args.credentialIndex, + credentialType = command.args.credentialType, + credentialName = command.args.credentialName, + }) + device:send(LockCluster.server.commands.SetPINCode(device, + command.args.credentialIndex, + UserStatusEnum.OCCUPIED_ENABLED, + UserTypeEnum.UNRESTRICTED, + command.args.credentialData) + ) +end + +function CapabilityHandlers.delete_credential(driver, device, command) + local cmd_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + + if cmd_in_progress == consts.LOCK_USERS.DELETE then + -- Injected by deleteUser; busy state was already set with the full LOCK_USERS.DELETE context. + local credential_args = device:get_field(consts.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE) or {} + device:send(LockCluster.attributes.SendPINOverTheAir:write(device, true)) + device:send(LockCluster.server.commands.ClearPINCode(device, credential_args.credentialIndex)) + elseif lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE, consts.COMMAND_RESULT.BUSY) + else + -- Standalone deleteCredential: look up the credential to obtain its associated userIndex. + local found_cred = tables.find_entry(device, "credentials", command.args.credentialIndex) + if not found_cred then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE, consts.COMMAND_RESULT.FAILURE) + return + end + lock_utils.set_busy_state(device, consts.LOCK_CREDENTIALS.DELETE, { + credentialIndex = command.args.credentialIndex, + credentialType = command.args.credentialType, + userIndex = found_cred.userIndex, + }) + device:send(LockCluster.attributes.SendPINOverTheAir:write(device, true)) + device:send(LockCluster.server.commands.ClearPINCode(device, command.args.credentialIndex)) + end +end + +function CapabilityHandlers.delete_all_credentials(driver, device, command) + local cmd_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + + if cmd_in_progress == consts.LOCK_USERS.DELETE_ALL then + -- Injected by deleteAllUsers; busy state was already set with LOCK_USERS.DELETE_ALL context. + device:send(LockCluster.server.commands.ClearAllPINCodes(device)) + elseif lock_utils.is_device_busy(device) then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE_ALL, consts.COMMAND_RESULT.BUSY) + else + lock_utils.set_busy_state(device, consts.LOCK_CREDENTIALS.DELETE_ALL, command.args) + device:send(LockCluster.server.commands.ClearAllPINCodes(device)) + end +end + + +-- [[ REFRESH CAPABILITY COMMANDS ]] -- + +function CapabilityHandlers.refresh(driver, device, cmd) + device:refresh() + device:send(LockCluster.attributes.LockState:read(device)) + device:send(Alarm.attributes.AlarmCount:read(device)) + + if device:supports_capability(capabilities.lockCredentials) then + -- If we are missing the cached values for these attributes, read them so we can properly manage them locally + if (device:get_latest_state("main", capabilities.lockCredentials.ID, capabilities.lockCredentials.maxPinCodeLen.NAME) == nil) then + device:send(clusters.DoorLock.attributes.MaxPINCodeLength:read(device)) + end + if (device:get_latest_state("main", capabilities.lockCredentials.ID, capabilities.lockCredentials.minPinCodeLen.NAME) == nil) then + device:send(clusters.DoorLock.attributes.MinPINCodeLength:read(device)) + end + if (device:get_latest_state("main", capabilities.lockCredentials.ID, capabilities.lockCredentials.pinUsersSupported.NAME) == nil) or + (device:get_latest_state("main", capabilities.lockUsers.ID, capabilities.lockUsers.totalUsersSupported.NAME) == nil) then + device:send(clusters.DoorLock.attributes.NumberOfPINUsersSupported:read(device)) + end + elseif not device:supports_capability(capabilities.lockCodes) then + -- Generically fingerprinted devices may support PINs even though the profile does not yet + -- reflect it. Read NumberOfPINUsersSupported so the response handler can trigger a profile + -- migration when appropriate. + device:send(clusters.DoorLock.attributes.NumberOfPINUsersSupported:read(device)) + end +end + +return CapabilityHandlers diff --git a/drivers/SmartThings/zigbee-lock/src/lock_handlers/zigbee_responses.lua b/drivers/SmartThings/zigbee-lock/src/lock_handlers/zigbee_responses.lua new file mode 100644 index 0000000000..9d1c53947a --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/lock_handlers/zigbee_responses.lua @@ -0,0 +1,438 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local socket = require "cosock.socket" + +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" + +local consts = require "lock_utils.constants" +local lock_utils = require "lock_utils.utils" +local tables = require "lock_utils.tables" + +local ZigbeeHandlers = {} + + +-- [[ DOOR LOCK CLUSTER COMMAND RESPONSES ]] -- + +function ZigbeeHandlers.set_pin_code_response(driver, device, zb_rx) + -- cached values from capability command + local command_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + local credential_args_in_use = device:get_field(consts.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE) + -- zb response values + local set_pin_code_status = zb_rx.body.zcl_body.status.value + + local SetCodeStatus = clusters.DoorLock.types.DrlkSetCodeStatus + -- SUCCESS = 0 + -- GENERAL_FAILURE = 1 + -- MEMORY_FULL = 2 + -- DUPLICATE_CODE = 3 + + -- mapped failures states + local RESPONSE_RESULT_MAP = { + [SetCodeStatus.GENERAL_FAILURE] = consts.COMMAND_RESULT.FAILURE, + [SetCodeStatus.MEMORY_FULL] = consts.COMMAND_RESULT.RESOURCE_EXHAUSTED, + [SetCodeStatus.DUPLICATE_CODE] = consts.COMMAND_RESULT.DUPLICATE, + } + + -- apply result based on response and identify command result status + local result_status + if set_pin_code_status == SetCodeStatus.SUCCESS then + if command_in_progress == consts.LOCK_CREDENTIALS.ADD then + tables.add_entry(device, "users", { + userIndex = credential_args_in_use.userIndex, + userName = "Guest " .. credential_args_in_use.userIndex, -- default + userType = "guest", -- default + }) + result_status = tables.add_entry(device, "credentials", { + userIndex = credential_args_in_use.userIndex, + credentialIndex = credential_args_in_use.credentialIndex, + credentialType = credential_args_in_use.credentialType, + credentialName = "Guest " .. credential_args_in_use.userIndex, -- default + }) + elseif command_in_progress == consts.LOCK_CREDENTIALS.UPDATE then + result_status = consts.COMMAND_RESULT.SUCCESS + end + elseif RESPONSE_RESULT_MAP[set_pin_code_status] then + result_status = RESPONSE_RESULT_MAP[set_pin_code_status] + else + result_status = consts.COMMAND_RESULT.FAILURE + end + + -- emit command result + if command_in_progress then + local additional_info = result_status == consts.COMMAND_RESULT.SUCCESS and { + userIndex = credential_args_in_use.userIndex, + credentialIndex = credential_args_in_use.credentialIndex, + } or nil + lock_utils.emit_command_result(device, capabilities.lockCredentials, command_in_progress, result_status, additional_info) + lock_utils.clear_busy_state(device) + end +end + + +function ZigbeeHandlers.clear_all_pin_codes_response(driver, device, zb_rx) + -- cached values from capability command + local command_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + -- zb response values + local clear_pin_code_status = zb_rx.body.zcl_body.status.value + + local ResponseStatus = clusters.DoorLock.types.DrlkPassFailStatus + -- PASS = 0 + -- FAIL = 1 + + -- apply result and identify command result statuses + local user_status, credential_status + if clear_pin_code_status == ResponseStatus.PASS then + -- Only clear the users table when this response is for a deleteAllUsers flow. + if command_in_progress == consts.LOCK_USERS.DELETE_ALL then + user_status = tables.delete_all_entries(device, "users") + end + credential_status = tables.delete_all_entries(device, "credentials") + elseif clear_pin_code_status == ResponseStatus.FAIL then + user_status = consts.COMMAND_RESULT.FAILURE + credential_status = consts.COMMAND_RESULT.FAILURE + end + + -- emit command results + if command_in_progress == consts.LOCK_USERS.DELETE_ALL then + -- deleteAllUsers injects deleteAllCredentials, so both command results should be emitted. + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.DELETE_ALL, user_status) + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE_ALL, credential_status) + elseif command_in_progress == consts.LOCK_CREDENTIALS.DELETE_ALL then + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE_ALL, credential_status) + end + lock_utils.clear_busy_state(device) +end + + +function ZigbeeHandlers.clear_pin_code_response(driver, device, zb_rx) + -- cached values from capability command + local credential_args_in_use = device:get_field(consts.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE) + local command_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + -- guard: avoid nil errors if the programming event failsafe already handled this command and cleared the busy state + if not credential_args_in_use then return end + + -- zb response values + local clear_pin_code_status = zb_rx.body.zcl_body.status.value + + local ResponseStatus = clusters.DoorLock.types.DrlkPassFailStatus + -- PASS = 0 + -- FAIL = 1 + + -- apply result and identify command result statuses + local user_status, credential_status + if clear_pin_code_status == ResponseStatus.PASS then + credential_status = tables.delete_entry(device, "credentials", credential_args_in_use.credentialIndex) + if command_in_progress == consts.LOCK_USERS.DELETE or tables.find_entry_by(device, "credentials", "userIndex", credential_args_in_use.userIndex) == nil then + user_status = tables.delete_entry(device, "users", credential_args_in_use.userIndex) + end + elseif clear_pin_code_status == ResponseStatus.FAIL then + user_status = consts.COMMAND_RESULT.FAILURE + credential_status = consts.COMMAND_RESULT.FAILURE + end + + -- emit command results + if command_in_progress == consts.LOCK_USERS.DELETE then + -- the deleteUser command injects a deleteCredential command, so both command results should be emitted in this case. + local user_info = user_status == consts.COMMAND_RESULT.SUCCESS and { userIndex = credential_args_in_use.userIndex } or nil + local cred_info = credential_status == consts.COMMAND_RESULT.SUCCESS and { credentialIndex = credential_args_in_use.credentialIndex, userIndex = credential_args_in_use.userIndex } or nil + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.DELETE, user_status, user_info) + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE, credential_status, cred_info) + elseif command_in_progress == consts.LOCK_CREDENTIALS.DELETE then + local cred_info = credential_status == consts.COMMAND_RESULT.SUCCESS and { credentialIndex = credential_args_in_use.credentialIndex, userIndex = credential_args_in_use.userIndex } or nil + lock_utils.emit_command_result(device, capabilities.lockCredentials, consts.LOCK_CREDENTIALS.DELETE, credential_status, cred_info) + end + lock_utils.clear_busy_state(device) +end + + +function ZigbeeHandlers.get_pin_code_response(driver, device, zb_rx) + -- cached values from capability command + local command_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + -- zb response values + local user_id = tonumber(zb_rx.body.zcl_body.user_id.value) + + if command_in_progress == consts.SYNC.CODES_FROM_LOCK then + -- Only add entries for slots that are actually occupied on the lock. + local user_status = zb_rx.body.zcl_body.user_status.value + if user_status == clusters.DoorLock.types.DrlkUserStatus.OCCUPIED_ENABLED then + -- If an entry already exists at this index, this will be a no-op. + tables.add_entry(device, "users", { + userIndex = user_id, + userName = "Guest " .. user_id, + userType = "guest", + }) + tables.add_entry(device, "credentials", { + userIndex = user_id, + credentialIndex = user_id, + credentialType = consts.CRED_TYPE_PIN, + credentialName = "Guest " .. user_id, + }) + -- reset the consecutive unoccupied codes counter since we found an occupied code + device:set_field(consts.SYNC.CONSECUTIVE_UNOCCUPIED_CODES, 0) + else + local consecutive_unoccupied_codes = (device:get_field(consts.SYNC.CONSECUTIVE_UNOCCUPIED_CODES) or 0) + 1 + device:set_field(consts.SYNC.CONSECUTIVE_UNOCCUPIED_CODES, consecutive_unoccupied_codes) + end + + if user_id >= tables.get_max_entries(device, "credentials") or + (device:get_field(consts.SYNC.CONSECUTIVE_UNOCCUPIED_CODES) or 0) > 5 then + -- stop if we hit the max number of codes supported by the lock, or if we have received 5 consecutive unoccupied codes. + device:set_field(consts.SYNC.CODE_INDEX, nil) + lock_utils.clear_busy_state(device) + else + local synced_code_index = device:get_field(consts.SYNC.CODE_INDEX) + 1 + device:set_field(consts.SYNC.CODE_INDEX, synced_code_index) + lock_utils.set_busy_state(device, consts.SYNC.CODES_FROM_LOCK, { checkingCode = synced_code_index }) + device:send(clusters.DoorLock.server.commands.GetPINCode(device, synced_code_index)) + end + end +end + + +-- [[ DOOR LOCK CLUSTER EVENT NOTIFICATIONS ]] -- + +function ZigbeeHandlers.programming_event_notification(driver, device, zb_rx) + -- zb response values + local user_id = tonumber(zb_rx.body.zcl_body.user_id.value) + local event_code = tonumber(zb_rx.body.zcl_body.program_event_code.value) + + if device:get_manufacturer() == "ASSA ABLOY iRevo" or device:get_manufacturer() == "Yale" then + if user_id >= 256 then -- Index is incorrectly written on these devices. Attempt to shift it to get an actual value + user_id = user_id >> 8 + end + end + + local ProgramEventCode = clusters.DoorLock.types.ProgramEventCode + -- MASTER_CODE_CHANGED = 1 + -- PIN_CODE_ADDED = 2 + -- PIN_CODE_DELETED = 3 + -- PIN_CODE_CHANGED = 4 + -- RFID_CODE_ADDED = 5 + -- RFID_CODE_DELETED = 6 + + + -- failsafes: handle the case where we receive a programming event notification for a command we've just sent, + -- which can be verified by checking that the user id matches the one used in the command, but before we receive + -- the response for that command. This gives us double the chance to handle the command + -- in case the response handler doesn't execute properly for some reason. + -- + -- cached values from capability command, if applicable. + local command_in_progress = device:get_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS) + local credential_args_in_use = device:get_field(consts.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE) or {} + if command_in_progress and credential_args_in_use.credentialIndex == user_id then + local result_status + if event_code == ProgramEventCode.PIN_CODE_ADDED then + if command_in_progress == consts.LOCK_CREDENTIALS.ADD then + tables.add_entry(device, "users", { + userIndex = credential_args_in_use.userIndex, + userName = "Guest " .. credential_args_in_use.userIndex, -- default + userType = "guest", -- default + }) + result_status = tables.add_entry(device, "credentials", { + userIndex = credential_args_in_use.userIndex, + credentialIndex = credential_args_in_use.credentialIndex, + credentialType = credential_args_in_use.credentialType, + credentialName = credential_args_in_use.credentialName, -- optional + }) + elseif command_in_progress == consts.LOCK_CREDENTIALS.UPDATE then + -- some devices emit PIN_CODE_ADDED even for UPDATE events + result_status = consts.COMMAND_RESULT.SUCCESS + end + elseif event_code == ProgramEventCode.PIN_CODE_DELETED and (command_in_progress == consts.LOCK_CREDENTIALS.DELETE or command_in_progress == consts.LOCK_USERS.DELETE) then + result_status = tables.delete_entry(device, "credentials", credential_args_in_use.credentialIndex) + -- the deleteUser command injects a deleteCredential command, so ensure the lockUsers command results is emitted in this case. + if command_in_progress == consts.LOCK_USERS.DELETE or tables.find_entry_by(device, "credentials", "userIndex", credential_args_in_use.userIndex) == nil then + local user_status = tables.delete_entry(device, "users", credential_args_in_use.userIndex) + local user_info = user_status == consts.COMMAND_RESULT.SUCCESS and { userIndex = credential_args_in_use.userIndex } or nil + if command_in_progress == consts.LOCK_USERS.DELETE then + lock_utils.emit_command_result(device, capabilities.lockUsers, consts.LOCK_USERS.DELETE, user_status, user_info) + command_in_progress = consts.LOCK_CREDENTIALS.DELETE -- ensure the lockCredentials result is emitted after this with the correct command context + end + end + elseif event_code == ProgramEventCode.PIN_CODE_CHANGED and command_in_progress == consts.LOCK_CREDENTIALS.UPDATE then + result_status = consts.COMMAND_RESULT.SUCCESS + end + + if result_status then + lock_utils.emit_command_result(device, + capabilities.lockCredentials, + command_in_progress, + result_status, + { userIndex = credential_args_in_use.userIndex, credentialIndex = credential_args_in_use.credentialIndex } + ) + lock_utils.clear_busy_state(device) + return + end + end + + -- handle the case where we receive a programming event notification for a code we've just deleted, + if event_code == ProgramEventCode.PIN_CODE_ADDED then + -- if no "addCredential" command is in progress, check if the credential is already stored locally. + local credential = tables.find_entry(device, "credentials", user_id) + if not credential then + -- check what user slot we have to associate this with and add a default user + local next_index = tables.next_index(device, "users") + if next_index and next_index <= tables.get_max_entries(device, "users") then + tables.add_entry(device, "users", { + userIndex = next_index, + userName = "Guest " .. next_index, + userType = "guest", + }) + tables.add_entry(device, "credentials", { + userIndex = next_index, + credentialIndex = user_id, + credentialType = consts.CRED_TYPE_PIN, + credentialName = "Guest " .. next_index, + }) + end + end + elseif event_code == ProgramEventCode.PIN_CODE_DELETED then + -- check if a credential exists locally for this user id, then + -- try to delete the entries in our tables corresponding to this code. + local credential = tables.find_entry(device, "credentials", user_id) + if credential then + tables.delete_entry(device, "credentials", user_id) + -- Only delete the user if they have no remaining credentials + if tables.find_entry_by(device, "credentials", "userIndex", credential.userIndex) == nil then + tables.delete_entry(device, "users", credential.userIndex) + end + end + end +end + +function ZigbeeHandlers.operating_event_notification(driver, device, zb_rx) + local op_event_code = tonumber(zb_rx.body.zcl_body.operation_event_code.value) + local op_event_source = tonumber(zb_rx.body.zcl_body.operation_event_source.value) + local user_id = tonumber(zb_rx.body.zcl_body.user_id.value) + + -- get lock event or return + local OpEventCode = clusters.DoorLock.types.OperationEventCode + local OP_EVENT_CODE_CAPABILITY_MAP = { + [OpEventCode.LOCK] = capabilities.lock.lock.locked(), + [OpEventCode.UNLOCK] = capabilities.lock.lock.unlocked(), + [OpEventCode.ONE_TOUCH_LOCK] = capabilities.lock.lock.locked(), + [OpEventCode.KEY_LOCK] = capabilities.lock.lock.locked(), + [OpEventCode.KEY_UNLOCK] = capabilities.lock.lock.unlocked(), + [OpEventCode.AUTO_LOCK] = capabilities.lock.lock.locked(), + [OpEventCode.MANUAL_LOCK] = capabilities.lock.lock.locked(), + [OpEventCode.MANUAL_UNLOCK] = capabilities.lock.lock.unlocked(), + [OpEventCode.SCHEDULE_LOCK] = capabilities.lock.lock.locked(), + [OpEventCode.SCHEDULE_UNLOCK] = capabilities.lock.lock.unlocked() + } + local lock_event = OP_EVENT_CODE_CAPABILITY_MAP[op_event_code] + if not lock_event then return end + lock_event.data = {} + + -- get method of lock event + local OpEventSource = clusters.DoorLock.types.DrlkOperationEventSource + local OP_EVENT_SOURCE_CAPABILITY_MAP = { + [OpEventSource.KEYPAD] = "keypad", + [OpEventSource.RF] = "command", + [OpEventSource.MANUAL] = "manual", + [OpEventSource.RFID] = "rfid", + } + if op_event_code == OpEventCode.AUTO_LOCK or + op_event_code == OpEventCode.SCHEDULE_LOCK or + op_event_code == OpEventCode.SCHEDULE_UNLOCK + then + lock_event.data.method = "auto" + else + lock_event.data.method = OP_EVENT_SOURCE_CAPABILITY_MAP[op_event_source] or "manual" + end + + -- get stored lockUsers data if applicable + if op_event_source == OpEventSource.KEYPAD and device:supports_capability(capabilities.lockUsers) then + local credential = tables.find_entry(device, "credentials", user_id) + local associated_user = credential and tables.find_entry_by(device, "users", "userIndex", credential.userIndex) or nil + if associated_user then + lock_event.data.userIndex = associated_user.userIndex + lock_event.data.userName = associated_user.userName + lock_event.data.userType = associated_user.userType + else + lock_event.data.userIndex = user_id + lock_event.data.userName = "Guest " .. user_id -- default + end + end + + -- if this is an event corresponding to a recently-received attribute report, we + -- want to set our delay timer for future lock attribute report events + local endpoint_id = zb_rx.address_header.src_endpoint.value + if lock_event.value.value == device:get_latest_state( + device:get_component_id_for_endpoint(endpoint_id), + capabilities.lock.ID, + capabilities.lock.lock.ID + ) then + local preceding_event_time = device:get_field(consts.DELAY_LOCK_EVENT) or 0 + local time_diff = socket.gettime() - preceding_event_time + if time_diff < consts.MAX_DELAY then + device:set_field(consts.DELAY_LOCK_EVENT, time_diff) + end + end + + device:emit_event_for_endpoint(endpoint_id, lock_event) +end + + +-- [[ DOOR LOCK CLUSTER ATTRIBUTES ]] -- + +function ZigbeeHandlers.lock_state(driver, device, value, zb_rx) + local attr = capabilities.lock.lock + local LOCK_STATE = { + [value.NOT_FULLY_LOCKED] = attr.unknown(), + [value.LOCKED] = attr.locked(), + [value.UNLOCKED] = attr.unlocked(), + [value.UNDEFINED] = attr.unknown(), + } + + -- this is where we decide whether or not we need to delay our lock event because we've + -- observed it coming before the event (or we're starting to compute the timer) + local delay = device:get_field(consts.DELAY_LOCK_EVENT) or 100 + if (delay < consts.MAX_DELAY) then + device.thread:call_with_delay(delay+.5, function () + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, LOCK_STATE[value.value] or attr.unknown()) + end) + else + device:set_field(consts.DELAY_LOCK_EVENT, socket.gettime()) + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, LOCK_STATE[value.value] or attr.unknown()) + end +end + +function ZigbeeHandlers.max_pin_code_length(driver, device, value) + device:emit_event(capabilities.lockCredentials.maxPinCodeLen(value.value, { visibility = { displayed = false } })) +end + +function ZigbeeHandlers.min_pin_code_length(driver, device, value) + device:emit_event(capabilities.lockCredentials.minPinCodeLen(value.value, { visibility = { displayed = false } })) +end + +function ZigbeeHandlers.number_of_pin_users_supported(driver, device, value) + if not device:supports_capability_by_id(capabilities.lockCodes.ID) and value.value > 0 then + -- this device was generically fingerprinted, but supports PIN users, so we should migrate it. + device:try_update_metadata({ profile = "base-lock" }) + end + if device:supports_capability(capabilities.lockCredentials) then + device:emit_event(capabilities.lockCredentials.pinUsersSupported(value.value, {visibility = {displayed = false}})) + end + if device:supports_capability(capabilities.lockUsers) then + device:emit_event(capabilities.lockUsers.totalUsersSupported(value.value, {visibility = {displayed = false}})) + end +end + + +-- [[ ALARMS CLUSTER COMMANDS ]] -- + +function ZigbeeHandlers.alarm(driver, device, zb_rx) + local ALARM_REPORT = { + [0] = capabilities.lock.lock.unknown(), + [1] = capabilities.lock.lock.unknown(), + -- Events 16-19 are low battery events, but are presented as descriptionText only + } + if (ALARM_REPORT[zb_rx.body.zcl_body.alarm_code.value] ~= nil) then + device:emit_event(ALARM_REPORT[zb_rx.body.zcl_body.alarm_code.value]) + end +end + + +return ZigbeeHandlers diff --git a/drivers/SmartThings/zigbee-lock/src/lock_utils.lua b/drivers/SmartThings/zigbee-lock/src/lock_utils.lua deleted file mode 100644 index a02a59963c..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/lock_utils.lua +++ /dev/null @@ -1,87 +0,0 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 -local utils = require "st.utils" -local capabilities = require "st.capabilities" -local json = require "st.json" -local LockCodes = capabilities.lockCodes - - -local lock_utils = { - -- Constants - LOCK_CODES = "lockCodes", - CHECKING_CODE = "checkingCode", - CODE_STATE = "codeState", - MIGRATION_COMPLETE = "migrationComplete", - MIGRATION_RELOAD_SKIPPED = "migrationReloadSkipped", - CHECKED_CODE_SUPPORT = "checkedCodeSupport" -} - -lock_utils.get_lock_codes = function(device) - local lc = device:get_field(lock_utils.LOCK_CODES) - return lc ~= nil and lc or {} -end - -lock_utils.lock_codes_event = function(device, lock_codes) - device:set_field(lock_utils.LOCK_CODES, lock_codes, { persist = true } ) - device:emit_event(capabilities.lockCodes.lockCodes(json.encode(utils.deep_copy(lock_codes)), { visibility = { displayed = false } })) -end - - -function lock_utils.get_code_name(device, code_id) - if (device:get_field(lock_utils.CODE_STATE) ~= nil and device:get_field(lock_utils.CODE_STATE)["setName"..code_id] ~= nil) then - -- this means a code set operation succeeded - return device:get_field(lock_utils.CODE_STATE)["setName"..code_id] - elseif (lock_utils.get_lock_codes(device)[code_id] ~= nil) then - return lock_utils.get_lock_codes(device)[code_id] - else - return "Code " .. code_id - end -end - -function lock_utils.get_change_type(device, code_id) - if (lock_utils.get_lock_codes(device)[code_id] == nil) then - return " set" - else - return " changed" - end -end - -function lock_utils.reset_code_state(device, code_slot) - local codeState = device:get_field(lock_utils.CODE_STATE) - if (codeState ~= nil) then - codeState["setName".. code_slot] = nil - codeState["setCode".. code_slot] = nil - device:set_field(lock_utils.CODE_STATE, codeState, { persist = true }) - end -end - -function lock_utils.code_deleted(device, code_slot) - local lock_codes = lock_utils.get_lock_codes(device) - local event = LockCodes.codeChanged(code_slot.." deleted", { state_change = true }) - event.data = {codeName = lock_utils.get_code_name(device, code_slot)} - lock_codes[code_slot] = nil - device:emit_event(event) - lock_utils.reset_code_state(device, code_slot) - return lock_codes -end - -function lock_utils.populate_state_from_data(device) - if device.data.lockCodes ~= nil and device:get_field(lock_utils.MIGRATION_COMPLETE) ~= true then - -- build the lockCodes table - local lockCodes = {} - local lc_data = json.decode(device.data.lockCodes) - for k, v in pairs(lc_data) do - lockCodes[k] = v - end - -- Populate the devices `lockCodes` field - device:set_field(lock_utils.LOCK_CODES, utils.deep_copy(lockCodes), { persist = true }) - -- Populate the devices state history cache - device.state_cache["main"] = device.state_cache["main"] or {} - device.state_cache["main"][capabilities.lockCodes.ID] = device.state_cache["main"][capabilities.lockCodes.ID] or {} - device.state_cache["main"][capabilities.lockCodes.ID][capabilities.lockCodes.lockCodes.NAME] = {value = json.encode(utils.deep_copy(lockCodes))} - - device:set_field(lock_utils.MIGRATION_COMPLETE, true, { persist = true }) - end -end - -return lock_utils diff --git a/drivers/SmartThings/zigbee-lock/src/lock_utils/constants.lua b/drivers/SmartThings/zigbee-lock/src/lock_utils/constants.lua new file mode 100644 index 0000000000..78537182f1 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/lock_utils/constants.lua @@ -0,0 +1,47 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lock_constants = {} + +lock_constants.DRIVER_STATE = { + BUSY = "busy", + COMMAND_IN_PROGRESS = "commandInProgress", + CREDENTIAL_ARGS_IN_USE = "currentCredential", + SLGA_MIGRATED = "slgaMigrated", +} + +lock_constants.SYNC = { + CODES_FROM_LOCK = "syncCodesFromLock", + CODE_INDEX = "syncCodeIndex", + CONSECUTIVE_UNOCCUPIED_CODES = "consecutiveUnoccupiedCodes", +} + +lock_constants.COMMAND_RESULT = { + SUCCESS = "success", + FAILURE = "failure", + DUPLICATE = "duplicate", + OCCUPIED = "occupied", + INVALID_COMMAND = "invalidCommand", + RESOURCE_EXHAUSTED = "resourceExhausted", + BUSY = "busy" +} + +lock_constants.LOCK_CREDENTIALS = { + ADD = "addCredential", + UPDATE = "updateCredential", + DELETE = "deleteCredential", + DELETE_ALL = "deleteAllCredentials" +} + +lock_constants.LOCK_USERS = { + ADD = "addUser", + UPDATE = "updateUser", + DELETE = "deleteUser", + DELETE_ALL = "deleteAllUsers" +} + +lock_constants.CRED_TYPE_PIN = "pin" +lock_constants.DELAY_LOCK_EVENT = "_delay_lock_event" +lock_constants.MAX_DELAY = 10 + +return lock_constants diff --git a/drivers/SmartThings/zigbee-lock/src/lock_utils/tables.lua b/drivers/SmartThings/zigbee-lock/src/lock_utils/tables.lua new file mode 100644 index 0000000000..bfe3562344 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/lock_utils/tables.lua @@ -0,0 +1,268 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local st_utils = require "st.utils" +local COMMAND_RESULT = require "lock_utils.constants".COMMAND_RESULT + +local table_utils = {} + +-- DEFS describes how each capability-backed state table is structured: +-- +-- capability SmartThings capability (used for device support checks and to get latest state) +-- attribute Capability attribute function (used to emit state) +-- max_entries Attribute of the capability that defines the maximum number of entries allowed in the table +-- match_key Key used to identify entries in flat tables +-- required_keys Keys that must be non-nil when adding an entry +-- persistent_field device:set_field key used to back up the table across restarts +-- + +local DEFS = { + users = { + capability = capabilities.lockUsers, + attribute = capabilities.lockUsers.users, + max_entries = capabilities.lockUsers.totalUsersSupported, + match_key = "userIndex", + required_keys = {"userIndex", "userType"}, + persistent_field = "persistedUsers", + }, + credentials = { + capability = capabilities.lockCredentials, + attribute = capabilities.lockCredentials.credentials, + max_entries = capabilities.lockCredentials.pinUsersSupported, + match_key = "credentialIndex", + required_keys = {"userIndex", "credentialIndex", "credentialType"}, + persistent_field = "persistedCredentials", + } +} + +-- Resolve a table name to its definition. Logs an error and returns nil if unknown. +local function resolve_table_def(device, table_name) + local def = DEFS[table_name] + if not def then + device.log.error(string.format("table_helpers: unknown table %q", table_name)) + end + return def +end + +-- Validate that an entry table contains all required keys. +local function validate_entry(device, entry, required_keys) + for _, key in ipairs(required_keys or {}) do + if entry[key] == nil then + device.log.error(string.format("table_helpers: entry missing required key %q", key)) + return false + end + end + return true +end + +-- Write the current table contents to the device's persistent field store so that +-- the state survives driver restarts and can be restored if the capability state +-- cache is wiped. +local function persist_table(device, def, data) + device:set_field(def.persistent_field, st_utils.deep_copy(data), { persist = true }) +end + +-- Read the current state for a table and return a deep-copied array. +-- Accepts either a string table name ("users", "credentials") or a DEFS entry directly. +-- Returns nil (with a warning) if the capability is unsupported by the device. +-- When the capability state cache has been wiped (get_latest_state returns nil), +-- falls back to the persistent field store so that callers always receive the +-- last-known state rather than an empty table. +--- @return table[] | nil +function table_utils.get_state(device, name_or_def) + local def = type(name_or_def) == "string" and resolve_table_def(device, name_or_def) or name_or_def + if not def then return nil end + if not device:supports_capability(def.capability, "main") then + device.log.warn(string.format( + "table_helpers: device does not support capability %q", def.capability.ID + )) + return + end + local state = device:get_latest_state("main", def.capability.ID, def.attribute.NAME) + if state ~= nil then + return st_utils.deep_copy(state) + end + -- Capability state cache is absent (e.g. after a hub reboot); fall back to the + -- persistent store so that callers see the last-known table contents. + return st_utils.deep_copy(device:get_field(def.persistent_field) or {}) +end + +-- Find an entry in a named table where the match_key equals value. +-- Returns the matching entry, or nil if not found. +function table_utils.find_entry(device, table_name, value) + local def = resolve_table_def(device, table_name) + if not def then return nil end + local t = table_utils.get_state(device, def) + if not t then return nil end + for _, entry in ipairs(t) do + if entry[def.match_key] == value then return entry end + end + return nil +end + +-- Find an entry in a named table where entry[key] equals value (arbitrary key search). +-- Returns the matching entry, or nil if not found. +function table_utils.find_entry_by(device, table_name, key, value) + local def = resolve_table_def(device, table_name) + if not def then return nil end + local t = table_utils.get_state(device, def) + if not t then return nil end + for _, entry in ipairs(t) do + if entry[key] == value then return entry end + end + return nil +end + +-- Find all entries in a named table where entry[key] equals value (arbitrary key search). +-- Returns an array of matching entries, or an empty array if none found. +function table_utils.find_all_entries_by(device, table_name, key, value) + local def = resolve_table_def(device, table_name) + if not def then return {} end + local t = table_utils.get_state(device, def) + if not t then return {} end + local matches = {} + for _, entry in ipairs(t) do + if entry[key] == value then table.insert(matches, entry) end + end + return matches +end + +-- Return the lowest positive integer not yet used as the match_key in the named table. +-- Used to auto-assign the next available slot for a new entry. +function table_utils.next_index(device, table_name) + local def = resolve_table_def(device, table_name) + if not def then return 1 end + local t = table_utils.get_state(device, def) or {} + local occupied = {} + for _, entry in ipairs(t) do occupied[entry[def.match_key]] = true end + local idx = 1 + while occupied[idx] do idx = idx + 1 end + return idx +end + +function table_utils.get_max_entries(device, table_name) + local def = resolve_table_def(device, table_name) + if not def then return end + return device:get_latest_state("main", def.capability.ID, def.max_entries.NAME, 20) -- arbitrary, default to 20 if the attribute is missing +end + +-- Add an entry to a named table. The entry must satisfy all required_keys for +-- that table. An entry whose match_key value already exists in the +-- table is skipped to prevent duplicates. If the table has a max_entries limit, +-- entries that exceed the limit are not added. +function table_utils.add_entry(device, table_name, entry) + device.log.debug("table_helpers: attempting to add entry " .. st_utils.stringify_table(entry) .. " to " .. table_name) + local def = resolve_table_def(device, table_name) + if not def then return COMMAND_RESULT.FAILURE end + if not validate_entry(device, entry, def.required_keys) then return COMMAND_RESULT.FAILURE end + local t = table_utils.get_state(device, def) + if not t then return COMMAND_RESULT.FAILURE end + + if #t >= table_utils.get_max_entries(device, table_name) then + device.log.warn(string.format( + "table_helpers: cannot add entry to %q, max entries reached", table_name + )) + return COMMAND_RESULT.RESOURCE_EXHAUSTED + end + + -- Object entry: skip if an entry with the same match_key value already exists + if def.match_key then + for _, existing in ipairs(t) do + if existing[def.match_key] == entry[def.match_key] then + device.log.warn(string.format( + "table_helpers: entry with %s == %s already exists in %q, skipping", + def.match_key, tostring(entry[def.match_key]), table_name + )) + return COMMAND_RESULT.OCCUPIED + end + end + end + + table.insert(t, st_utils.deep_copy(entry)) + + device:emit_event(def.attribute(t, {visibility = {displayed = false}})) + persist_table(device, def, t) + return COMMAND_RESULT.SUCCESS +end + + +--- Update fields of an existing entry in a table. +--- The entry to update is identified by the match_key parameter in DEFS. +function table_utils.update_entry(device, table_name, match_value, updates) + device.log.debug("table_helpers: attempting to update entry " .. match_value .. " in " .. table_name .. ": " .. st_utils.stringify_table(updates)) + local def = resolve_table_def(device, table_name) + if not def then return COMMAND_RESULT.FAILURE end + local t = table_utils.get_state(device, def) + if not t then return COMMAND_RESULT.FAILURE end + + for _, entry in ipairs(t) do + if entry[def.match_key] == match_value then + for k, v in pairs(updates) do + entry[k] = v + end + device:emit_event(def.attribute(t, {visibility = {displayed = false}})) + persist_table(device, def, t) + return COMMAND_RESULT.SUCCESS + end + end + + device.log.warn(string.format( + "table_helpers: no entry found in %q with %s == %s", + table_name, def.match_key, tostring(match_value) + )) + return COMMAND_RESULT.FAILURE +end + + +-- Delete an entry from a table. +-- +-- Returns SUCCESS, or FAILURE if nothing matched. +function table_utils.delete_entry(device, table_name, matcher) + device.log.debug("table_helpers: attempting to delete entry " .. matcher .. " from " .. table_name) + local def = resolve_table_def(device, table_name) + if not def then return COMMAND_RESULT.FAILURE end + local t = table_utils.get_state(device, def) + if not t then return COMMAND_RESULT.FAILURE end + + local predicate = function(entry) return entry[def.match_key] == matcher end + + for i, entry in ipairs(t) do + if predicate(entry) then + table.remove(t, i) + device:emit_event(def.attribute(t, {visibility = {displayed = false}})) + persist_table(device, def, t) + return COMMAND_RESULT.SUCCESS + end + end + return COMMAND_RESULT.FAILURE +end + +-- Delete all entries from a table. +function table_utils.delete_all_entries(device, table_name) + device.log.debug("table_helpers: attempting to delete all entries from " .. table_name) + local def = resolve_table_def(device, table_name) + if not def then return COMMAND_RESULT.FAILURE end + device:emit_event(def.attribute({}, {visibility = {displayed = false}})) + persist_table(device, def, {}) + return COMMAND_RESULT.SUCCESS +end + +-- Restore capability state from the persistent field store. +-- Called during init to re-emit table events if the capability state cache +-- has been wiped (e.g. after a hub reboot). Only emits for tables that have +-- persisted data, are in a nil state, and whose capability is supported by the device. +function table_utils.restore_from_persistent_store(device) + for _, internal in pairs(DEFS) do + if device:supports_capability(internal.capability, "main") and + device:get_latest_state("main", internal.capability.ID, internal.attribute.NAME) == nil + then + local persisted = st_utils.deep_copy(device:get_field(internal.persistent_field)) + if persisted and #persisted > 0 then + device:emit_event(internal.attribute(persisted, {visibility = {displayed = false}})) + end + end + end +end + +return table_utils diff --git a/drivers/SmartThings/zigbee-lock/src/lock_utils/utils.lua b/drivers/SmartThings/zigbee-lock/src/lock_utils/utils.lua new file mode 100644 index 0000000000..90bdd96f33 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/lock_utils/utils.lua @@ -0,0 +1,66 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local consts = require "lock_utils.constants" + +local lock_utils = {} + +-- [[ BUSY STATE MANAGEMENT ]] -- + +-- Check if we are currently busy performing a task, or at least 10 seconds have passed since the busy state was last set. +-- If busy, return true. If not busy, clear any stale state and return false. +function lock_utils.is_device_busy(device) + local c_time = os.time() + local busy_since = device:get_field(consts.DRIVER_STATE.BUSY) or false + + if (busy_since == false) or (c_time - busy_since > 10) then + lock_utils.clear_busy_state(device) + return false + end + return true +end + +-- Set states that may be required when in busy state +function lock_utils.set_busy_state(device, command_name, command_args) + device:set_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS, command_name) + device:set_field(consts.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE, command_args or {}) + device:set_field(consts.DRIVER_STATE.BUSY, os.time()) +end + +-- Clear states that were set when in busy state +function lock_utils.clear_busy_state(device) + device:set_field(consts.DRIVER_STATE.COMMAND_IN_PROGRESS, nil) + device:set_field(consts.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE, nil) + device:set_field(consts.DRIVER_STATE.BUSY, false) +end + + +-- [[ SYNC STATE MANAGEMENT ]] -- + +function lock_utils.sync_device_state(device) + local clusters = require "st.zigbee.zcl.clusters" + -- Per spec, this attribute should be a boolean set to True if it is ok for the door lock server to send PINs over the air. + device:send(clusters.DoorLock.attributes.SendPINOverTheAir:write(device, true)) + + -- if consts.SYNC.CODE_INDEX is nil, we haven't started syncing codes from the lock yet, + -- so start the process from 1. note: the Zigbee "Master Code" is associated with index 0. + if (device:get_field(consts.SYNC.CODE_INDEX) == nil) then + device:set_field(consts.SYNC.CODE_INDEX, 1) + end + lock_utils.set_busy_state(device, consts.SYNC.CODES_FROM_LOCK) + device:send(clusters.DoorLock.server.commands.GetPINCode(device, device:get_field(consts.SYNC.CODE_INDEX))) +end + + +-- [[ COMMAND RESULT STATE MANAGEMENT ]] -- + +function lock_utils.emit_command_result(device, capability, command_name, status_code, additional_info) + local info = additional_info or {} + info.commandName = command_name + info.statusCode = status_code + if capability then + device:emit_event(capability.commandResult(info, {state_change = true, visibility = {displayed = false}})) + end +end + +return lock_utils diff --git a/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua b/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua index fff290df5d..e09a598efa 100644 --- a/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua @@ -10,7 +10,9 @@ local cluster_base = require "st.zigbee.cluster_base" local PowerConfiguration = clusters.PowerConfiguration local DoorLock = clusters.DoorLock local Lock = capabilities.lock -local lock_utils = require "lock_utils" +local consts = require "lock_utils.constants" +local tables = require "lock_utils.tables" +local socket = require "cosock.socket" local SAMSUNG_SDS_MFR_SPECIFIC_UNLOCK_COMMAND = 0x1F local SAMSUNG_SDS_MFR_CODE = 0x0003 @@ -54,7 +56,8 @@ local function emit_event_if_latest_state_missing(device, component, capability, end local device_added = function(self, device) - lock_utils.populate_state_from_data(device) + device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) -- set migrated for all Samsung SDS devices. They do not require any legacy functionality. + emit_event_if_latest_state_missing(device, "main", capabilities.lock, capabilities.lock.lock.NAME, capabilities.lock.lock.unlocked()) device:emit_event(capabilities.battery.battery(100)) end @@ -68,10 +71,86 @@ end local battery_init = battery_defaults.build_linear_voltage_init(4.0, 6.0) local device_init = function(driver, device, event) + device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) -- set migrated for all Samsung SDS devices. They do not require any legacy functionality. battery_init(driver, device, event) device:remove_monitored_attribute(clusters.PowerConfiguration.ID, clusters.PowerConfiguration.attributes.BatteryVoltage.ID) device:remove_configured_attribute(clusters.PowerConfiguration.ID, clusters.PowerConfiguration.attributes.BatteryVoltage.ID) - lock_utils.populate_state_from_data(device) +end + +local operating_event_notification = function(driver, device, zb_rx) + local op_event_code = tonumber(zb_rx.body.zcl_body.operation_event_code.value) + local op_event_source = tonumber(zb_rx.body.zcl_body.operation_event_source.value) + + -- get lock event or return + local OpEventCode = clusters.DoorLock.types.OperationEventCode + local OP_EVENT_CODE_CAPABILITY_MAP = { + [OpEventCode.LOCK] = capabilities.lock.lock.locked(), + [OpEventCode.UNLOCK] = capabilities.lock.lock.unlocked(), + [OpEventCode.ONE_TOUCH_LOCK] = capabilities.lock.lock.locked(), + [OpEventCode.KEY_LOCK] = capabilities.lock.lock.locked(), + [OpEventCode.KEY_UNLOCK] = capabilities.lock.lock.unlocked(), + [OpEventCode.AUTO_LOCK] = capabilities.lock.lock.locked(), + [OpEventCode.MANUAL_LOCK] = capabilities.lock.lock.locked(), + [OpEventCode.MANUAL_UNLOCK] = capabilities.lock.lock.unlocked(), + [OpEventCode.SCHEDULE_LOCK] = capabilities.lock.lock.locked(), + [OpEventCode.SCHEDULE_UNLOCK] = capabilities.lock.lock.unlocked() + } + local lock_event = OP_EVENT_CODE_CAPABILITY_MAP[op_event_code] + if not lock_event then return end + lock_event.data = {} + + -- get method of lock event + local OpEventSource = clusters.DoorLock.types.DrlkOperationEventSource + local OP_EVENT_SOURCE_CAPABILITY_MAP = { + [OpEventSource.KEYPAD] = "keypad", + [OpEventSource.RF] = "command", + [OpEventSource.MANUAL] = "manual", + [OpEventSource.RFID] = "rfid", + -- These last two sources are not found in the spec, but they were in + -- the legacy driver and appear to be related to the Samsung SDS + [4] = "fingerprint", + [5] = "bluetooth", + } + if (op_event_source ~= OpEventSource.KEYPAD and ( + op_event_code == OpEventCode.AUTO_LOCK or + op_event_code == OpEventCode.SCHEDULE_LOCK or + op_event_code == OpEventCode.SCHEDULE_UNLOCK + )) then + lock_event.data.method = "auto" + else + lock_event.data.method = OP_EVENT_SOURCE_CAPABILITY_MAP[op_event_source] or "manual" + end + + -- get stored lockUsers data if applicable + if op_event_source == OpEventSource.KEYPAD and device:supports_capability(capabilities.lockUsers) then + local user_id = tonumber(zb_rx.body.zcl_body.user_id.value) + local associated_user = tables.find_entry(device, "users", user_id) + if associated_user then + lock_event.data.userIndex = user_id .. "" + lock_event.data.userName = associated_user.userName + lock_event.data.userType = associated_user.userType + else + lock_event.data.userIndex = user_id .. "" + lock_event.data.userName = "Guest " .. user_id -- default + end + end + + -- if this is an event corresponding to a recently-received attribute report, we + -- want to set our delay timer for future lock attribute report events + local endpoint_id = zb_rx.address_header.src_endpoint.value + if lock_event.value.value == device:get_latest_state( + device:get_component_id_for_endpoint(endpoint_id), + capabilities.lock.ID, + capabilities.lock.lock.ID + ) then + local preceding_event_time = device:get_field(consts.DELAY_LOCK_EVENT) or 0 + local time_diff = socket.gettime() - preceding_event_time + if time_diff < consts.MAX_DELAY then + device:set_field(consts.DELAY_LOCK_EVENT, time_diff) + end + end + + device:emit_event_for_endpoint(endpoint_id, lock_event) end local samsung_sds_driver = { @@ -79,7 +158,8 @@ local samsung_sds_driver = { zigbee_handlers = { cluster = { [DoorLock.ID] = { - [SAMSUNG_SDS_MFR_SPECIFIC_UNLOCK_COMMAND] = mfg_lock_door_handler + [SAMSUNG_SDS_MFR_SPECIFIC_UNLOCK_COMMAND] = mfg_lock_door_handler, + [clusters.DoorLock.client.commands.OperatingEventNotification.ID] = operating_event_notification, } }, attr = { diff --git a/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua b/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua index ff4bf8980d..d3491ed664 100644 --- a/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-lock/src/sub_drivers.lua @@ -3,9 +3,9 @@ local lazy_load_if_possible = require "lazy_load_subdriver" local sub_drivers = { + lazy_load_if_possible("legacy-handlers"), lazy_load_if_possible("samsungsds"), - lazy_load_if_possible("yale"), + lazy_load_if_possible("bad-battery-reporter"), lazy_load_if_possible("yale-fingerprint-lock"), - lazy_load_if_possible("lock-without-codes"), } return sub_drivers diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_bad_battery_reporter.lua similarity index 59% rename from drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua rename to drivers/SmartThings/zigbee-lock/src/test/test_bad_battery_reporter.lua index fcae7eda03..61fb28ce8d 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_yale_fingerprint_bad_battery_reporter.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_bad_battery_reporter.lua @@ -50,4 +50,42 @@ test.register_message_test( } ) +local mock_device_yrd = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + zigbee_endpoints ={ + [1] = { + id = 1, + manufacturer ="Yale", + model ="YRD220/240 TSDB", + server_clusters = {} + } + } +}) + +local function test_init_yrd() + test.mock_device.add_test_device(mock_device_yrd) +end + +test.register_message_test( + "Battery percentage report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_device_yrd.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device_yrd, 55) } + }, + { + channel = "capability", + direction = "send", + message = mock_device_yrd:generate_test_message("main", capabilities.battery.battery(55)) + } + }, + { + test_init = test_init_yrd, + min_api_version = 17 + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_generic_fingerprint_profile_update.lua similarity index 56% rename from drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua rename to drivers/SmartThings/zigbee-lock/src/test/test_generic_fingerprint_profile_update.lua index 4580101cd0..e6d6a22b78 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_generic_lock_migration.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_generic_fingerprint_profile_update.lua @@ -6,6 +6,7 @@ local test = require "integration_test" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" local PowerConfiguration = clusters.PowerConfiguration local DoorLock = clusters.DoorLock @@ -31,6 +32,18 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device)}) test.socket.zigbee:__queue_receive({mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device, 8)}) mock_device:expect_metadata_update({profile = "base-lock"}) + + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ profile = t_utils.get_profile_definition("base-lock.yml") })) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.supportedCredentials({"pin"}, { visibility = { displayed = false } }))) + -- inject_capability_command(refresh) is called inline by info_changed + test.socket.zigbee:__expect_send({mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device)}) + test.socket.zigbee:__expect_send({mock_device.id, DoorLock.attributes.LockState:read(mock_device)}) + test.socket.zigbee:__expect_send({mock_device.id, Alarm.attributes.AlarmCount:read(mock_device)}) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.MaxPINCodeLength:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.MinPINCodeLength:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device) }) end, { min_api_version = 17 diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_init_lifecycle_handlers.lua b/drivers/SmartThings/zigbee-lock/src/test/test_init_lifecycle_handlers.lua new file mode 100644 index 0000000000..1ff448a719 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/test/test_init_lifecycle_handlers.lua @@ -0,0 +1,484 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Integration tests for the four lifecycle handlers defined in init.lua: +-- added (device_added), doConfigure (do_configure), +-- infoChanged (info_changed), init (LockLifecycle.init) + +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" + +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local DoorLock = clusters.DoorLock +local PowerConfiguration = clusters.PowerConfiguration +local Alarms = clusters.Alarms +local constants = require "lock_utils.constants" + +-- ── Shared mock devices ──────────────────────────────────────────────────── +-- base-lock profile: lock + lockCodes + lockCredentials + lockUsers + battery +local mock_device_base = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), +}) + +-- Same profile but provisioning_state = "TYPED" (freshly fingerprinted) +local mock_device_typed = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + _provisioning_state = "TYPED", +}) + +-- lock-battery profile: lock + battery only (no lockCodes / lockCredentials) +local mock_device_battery = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("lock-battery.yml"), +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +-- Helper: make a test_init function that suppresses startup messages and +-- registers the given device. +local function make_test_init(device) + return function() + test.disable_startup_messages() + test.mock_device.add_test_device(device) + end +end + +-- Helper: expect the five zigbee messages produced by sync_device_state on a +-- freshly-started device (no cached capability state, CODE_INDEX starts at 1). +local function expect_sync_device_state(device) + test.socket.zigbee:__expect_send({ device.id, DoorLock.attributes.SendPINOverTheAir:write(device, true) }) + test.socket.zigbee:__expect_send({ device.id, DoorLock.server.commands.GetPINCode(device, 1) }) +end + +-- Helper: expect the messages produced by the legacy reload_all_codes path after +-- the 2-second doConfigure timer fires on a non-SLGA_MIGRATED device. +-- Unlike sync_device_state, reload_all_codes emits scanCodes("Scanning") and +-- starts iterating from code slot 0 (CHECKING_CODE = 0). +local function expect_reload_all_codes_messages(device) + test.socket.zigbee:__expect_send({ device.id, DoorLock.attributes.SendPINOverTheAir:write(device, true) }) + test.socket.zigbee:__expect_send({ device.id, DoorLock.attributes.MaxPINCodeLength:read(device) }) + test.socket.zigbee:__expect_send({ device.id, DoorLock.attributes.MinPINCodeLength:read(device) }) + test.socket.zigbee:__expect_send({ device.id, DoorLock.attributes.NumberOfPINUsersSupported:read(device) }) + test.socket.capability:__expect_send( + device:generate_test_message("main", + capabilities.lockCodes.scanCodes("Scanning", { visibility = { displayed = false } })) + ) + test.socket.zigbee:__expect_send({ device.id, DoorLock.server.commands.GetPINCode(device, 0) }) +end + +-- Helper: expect the six zigbee messages sent by do_configure for any device. +local function expect_do_configure_zigbee(device) + test.socket.zigbee:__expect_send({ + device.id, + zigbee_test_utils.build_bind_request(device, zigbee_test_utils.mock_hub_eui, PowerConfiguration.ID), + }) + test.socket.zigbee:__expect_send({ + device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:configure_reporting(device, 600, 21600, 1), + }) + test.socket.zigbee:__expect_send({ + device.id, + zigbee_test_utils.build_bind_request(device, zigbee_test_utils.mock_hub_eui, DoorLock.ID), + }) + test.socket.zigbee:__expect_send({ + device.id, + DoorLock.attributes.LockState:configure_reporting(device, 0, 3600, 0), + }) + test.socket.zigbee:__expect_send({ + device.id, + zigbee_test_utils.build_bind_request(device, zigbee_test_utils.mock_hub_eui, Alarms.ID), + }) + test.socket.zigbee:__expect_send({ + device.id, + Alarms.attributes.AlarmCount:configure_reporting(device, 0, 21600, 0), + }) +end + +-- ============================================================================ +-- added (device_added) +-- ============================================================================ + +test.register_coroutine_test( + "added: TYPED device with lockCodes emits migrated event, persists SLGA_MIGRATED, and injects refresh", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "added" }) + + -- Migrated event is emitted before the injected refresh + test.socket.capability:__expect_send( + mock_device_typed:generate_test_message("main", + capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + ) + -- inject_capability_command calls the refresh handler inline + test.socket.zigbee:__expect_send({ + mock_device_typed.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device_typed), + }) + test.socket.zigbee:__expect_send({ + mock_device_typed.id, + DoorLock.attributes.LockState:read(mock_device_typed), + }) + test.socket.zigbee:__expect_send({ + mock_device_typed.id, + Alarms.attributes.AlarmCount:read(mock_device_typed), + }) + test.socket.zigbee:__expect_send({ + mock_device_typed.id, + DoorLock.attributes.MaxPINCodeLength:read(mock_device_typed), + }) + test.socket.zigbee:__expect_send({ + mock_device_typed.id, + DoorLock.attributes.MinPINCodeLength:read(mock_device_typed), + }) + test.socket.zigbee:__expect_send({ + mock_device_typed.id, + DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device_typed), + }) + test.wait_for_events() + + assert( + mock_device_typed:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true, + "SLGA_MIGRATED must be true after added fires for a TYPED device" + ) + end, + { test_init = make_test_init(mock_device_typed) } +) + +test.register_coroutine_test( + "added: non-TYPED (PROVISIONED) device with lockCodes does NOT emit migrated but still injects refresh", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_base.id, "added" }) + + -- No migrated capability event expected + test.socket.zigbee:__expect_send({ + mock_device_base.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device_base), + }) + test.socket.zigbee:__expect_send({ + mock_device_base.id, + DoorLock.attributes.LockState:read(mock_device_base), + }) + test.socket.zigbee:__expect_send({ + mock_device_base.id, + Alarms.attributes.AlarmCount:read(mock_device_base), + }) + test.wait_for_events() + + assert( + mock_device_base:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) ~= true, + "SLGA_MIGRATED must NOT be set for a non-TYPED device" + ) + end, + { test_init = make_test_init(mock_device_base) } +) + +test.register_coroutine_test( + "added: device without lockCodes does NOT emit migrated event but still injects refresh", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "added" }) + + -- No migrated capability event expected. + -- For non-SLGA_MIGRATED devices the legacy-handlers refresh fires, which reads + -- NumberOfPINUsersSupported when the device has no lockCodes and no cached code support. + test.socket.zigbee:__expect_send({ + mock_device_battery.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device_battery), + }) + test.socket.zigbee:__expect_send({ + mock_device_battery.id, + DoorLock.attributes.LockState:read(mock_device_battery), + }) + test.socket.zigbee:__expect_send({ + mock_device_battery.id, + Alarms.attributes.AlarmCount:read(mock_device_battery), + }) + test.socket.zigbee:__expect_send({ + mock_device_battery.id, + DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device_battery), + }) + test.wait_for_events() + end, + { test_init = make_test_init(mock_device_battery) } +) + +-- ============================================================================ +-- doConfigure (do_configure) +-- ============================================================================ + +test.register_coroutine_test( + "doConfigure: SLGA_MIGRATED device with lockCredentials sends bind/configure then calls sync_device_state after 2-second delay", + function() + -- Pre-seed SLGA_MIGRATED so the main driver's do_configure fires (not legacy-handlers). + mock_device_base:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) + + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device_base.id, "doConfigure" }) + + expect_do_configure_zigbee(mock_device_base) + mock_device_base:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + + -- Timer fires: sync_device_state is called (GetPINCode slot 1) + test.mock_time.advance_time(2) + test.socket.zigbee:__set_channel_ordering("relaxed") + expect_sync_device_state(mock_device_base) + test.wait_for_events() + end, + { test_init = make_test_init(mock_device_base) } +) + +test.register_coroutine_test( + "doConfigure: SLGA_MIGRATED device without lockCredentials sends bind/configure but does NOT create sync timer", + function() + -- Pre-seed SLGA_MIGRATED so the main driver's do_configure fires. + mock_device_battery:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) + + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "doConfigure" }) + + expect_do_configure_zigbee(mock_device_battery) + mock_device_battery:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + -- lock-battery has no lockCredentials, so no 2-second timer is created. + end, + { test_init = make_test_init(mock_device_battery) } +) + +test.register_coroutine_test( + "doConfigure: non-SLGA_MIGRATED device triggers legacy reloadAllCodes (with scanCodes emit) after 2-second delay", + function() + -- mock_device_typed has no SLGA_MIGRATED → legacy-handlers' do_configure fires, + -- which injects a reloadAllCodes capability command after a 2-second delay. + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "doConfigure" }) + + expect_do_configure_zigbee(mock_device_typed) + mock_device_typed:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + + -- Timer fires: legacy reload_all_codes runs, iterating from code slot 0. + test.mock_time.advance_time(2) + test.socket.zigbee:__set_channel_ordering("relaxed") + expect_reload_all_codes_messages(mock_device_typed) + test.wait_for_events() + end, + { test_init = make_test_init(mock_device_typed) } +) + +-- ============================================================================ +-- infoChanged (info_changed) +-- Each test uses a per-test fresh device (upvalue pattern) to avoid +-- raw_st_data contamination from generate_info_changed across tests. +-- init is triggered first so the driver loads the device into device_cache, +-- allowing infoChanged to correctly identify the old profile. +-- ============================================================================ + +do + local dev + test.register_coroutine_test( + "infoChanged: switching from non-lockCodes to lockCodes+lockCredentials profile triggers full SLGA migration and two syncs", + function() + -- Warm up device_cache with the original (lock-battery) profile via init. + -- For a non-SLGA_MIGRATED device, legacy-handlers' init fires (no zigbee sends). + test.socket.device_lifecycle:__queue_receive({ dev.id, "init" }) + test.wait_for_events() + + -- Switch to base-lock (lockCodes + lockCredentials) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.device_lifecycle:__queue_receive( + dev:generate_info_changed({ profile = t_utils.get_profile_definition("base-lock.yml") }) + ) + -- Migration events + test.socket.capability:__expect_send( + dev:generate_test_message("main", + capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + dev:generate_test_message("main", + capabilities.lockCredentials.supportedCredentials({ "pin" }, { visibility = { displayed = false } })) + ) + -- inject_capability_command calls the refresh handler inline + test.socket.zigbee:__expect_send({ + dev.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(dev), + }) + test.socket.zigbee:__expect_send({ + dev.id, + DoorLock.attributes.LockState:read(dev), + }) + test.socket.zigbee:__expect_send({ + dev.id, + Alarms.attributes.AlarmCount:read(dev), + }) + test.socket.zigbee:__expect_send({ + dev.id, + DoorLock.attributes.MaxPINCodeLength:read(dev), + }) + test.socket.zigbee:__expect_send({ + dev.id, + DoorLock.attributes.MinPINCodeLength:read(dev), + }) + test.socket.zigbee:__expect_send({ + dev.id, + DoorLock.attributes.NumberOfPINUsersSupported:read(dev), + }) + -- Immediate sync_device_state + test.wait_for_events() + + assert( + dev:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true, + "SLGA_MIGRATED must be set after infoChanged profile switch" + ) + + -- Delayed sync_device_state (2 s) + test.mock_time.advance_time(2) + test.socket.zigbee:__set_channel_ordering("relaxed") + expect_sync_device_state(dev) + test.wait_for_events() + end, + { + test_init = function() + test.disable_startup_messages() + dev = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("lock-battery.yml"), + }) + test.mock_device.add_test_device(dev) + end, + } + ) +end + +do + local dev + test.register_coroutine_test( + "infoChanged: no profile change does nothing", + function() + -- Warm up device_cache + test.socket.device_lifecycle:__queue_receive({ dev.id, "init" }) + test.wait_for_events() + + -- infoChanged with no profile change + test.socket.device_lifecycle:__queue_receive(dev:generate_info_changed({})) + test.wait_for_events() + -- No capability events, no zigbee sends expected + end, + { + test_init = function() + test.disable_startup_messages() + dev = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + }) + test.mock_device.add_test_device(dev) + end, + } + ) +end + + +-- ============================================================================ +-- init (LockLifecycle.init) +-- ============================================================================ + +test.register_coroutine_test( + "init: device with lockCodes and SLGA_MIGRATED=true emits migrated + supportedCredentials", + function() + mock_device_base:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) + + test.socket.device_lifecycle:__queue_receive({ mock_device_base.id, "init" }) + test.socket.capability:__expect_send( + mock_device_base:generate_test_message("main", + capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device_base:generate_test_message("main", + capabilities.lockCredentials.supportedCredentials({ "pin" }, { visibility = { displayed = false } })) + ) + test.wait_for_events() + end, + { test_init = make_test_init(mock_device_base) } +) + +test.register_coroutine_test( + "init: device with lockCodes but SLGA_MIGRATED not set does nothing", + function() + -- SLGA_MIGRATED is not set; lockCodes is supported; elseif branch does not apply + test.socket.device_lifecycle:__queue_receive({ mock_device_base.id, "init" }) + test.wait_for_events() + -- No capability events and no zigbee sends expected + end, + { test_init = make_test_init(mock_device_base) } +) + +test.register_coroutine_test( + "init: SLGA_MIGRATED device without lockCodes sends NumberOfPINUsersSupported read to detect re-profiling", + function() + -- Pre-seed SLGA_MIGRATED=true so legacy-handlers is bypassed and the main driver's + -- init fires. lock-battery has no lockCodes, so the elseif branch executes and + -- reads NumberOfPINUsersSupported to detect whether the device should be re-profiled. + mock_device_battery:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) + + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "init" }) + test.socket.zigbee:__expect_send({ + mock_device_battery.id, + DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device_battery), + }) + test.wait_for_events() + end, + { test_init = make_test_init(mock_device_battery) } +) + +-- ============================================================================ +-- driverSwitched (LockLifecycle.driver_switched) +-- ============================================================================ + +test.register_coroutine_test( + "driver_switched: device with lockCodes and migrated=true persists SLGA_MIGRATED and updates metadata", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "added" }) + + -- Migrated event is emitted before the injected refresh + test.socket.capability:__expect_send( + mock_device_typed:generate_test_message("main", + capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) + ) + -- inject_capability_command calls the refresh handler inline + test.socket.zigbee:__expect_send({ + mock_device_typed.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device_typed), + }) + test.socket.zigbee:__expect_send({ + mock_device_typed.id, + DoorLock.attributes.LockState:read(mock_device_typed), + }) + test.socket.zigbee:__expect_send({ + mock_device_typed.id, + Alarms.attributes.AlarmCount:read(mock_device_typed), + }) + test.socket.zigbee:__expect_send({ + mock_device_typed.id, + DoorLock.attributes.MaxPINCodeLength:read(mock_device_typed), + }) + test.socket.zigbee:__expect_send({ + mock_device_typed.id, + DoorLock.attributes.MinPINCodeLength:read(mock_device_typed), + }) + test.socket.zigbee:__expect_send({ + mock_device_typed.id, + DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device_typed), + }) + test.wait_for_events() + + -- driverSwitched occurs after added, so migrated=true is already set in the capability state cache + test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "driver_switched" }) + mock_device_typed:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + + assert(mock_device_typed:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true) + end, + { test_init = make_test_init(mock_device_typed) } +) + +-- ============================================================================ +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_lock_code_slga_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_lock_code_slga_migration.lua new file mode 100644 index 0000000000..e29788fe36 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/test/test_lock_code_slga_migration.lua @@ -0,0 +1,76 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Mock out globals +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 DoorLock = clusters.DoorLock +local PowerConfiguration = clusters.PowerConfiguration +local Alarm = clusters.Alarms +local capabilities = require "st.capabilities" +local constants = require "lock_utils.constants" + +local json = require "st.json" + +local mock_datastore = require "integration_test.mock_env_datastore" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("base-lock.yml"), + data = { + lockCodes = json.encode({ + ["1"] = "Zach", + ["5"] = "Steven" + }), + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Device called 'migrate' command", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.wait_for_events() + -- Validate lockCodes field + mock_datastore.__assert_device_store_contains(mock_device.id, "lockCodes", { ["1"] = "Zach", ["5"] = "Steven" }) + -- Validate migration complete flag + mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) + -- Set min/max code length attributes + test.socket.zigbee:__queue_receive({ mock_device.id, DoorLock.attributes.MinPINCodeLength:build_test_attr_report(mock_device, 5) }) + test.socket.zigbee:__queue_receive({ mock_device.id, DoorLock.attributes.MaxPINCodeLength:build_test_attr_report(mock_device, 10) }) + test.socket.zigbee:__queue_receive({ mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device, 4) }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.minCodeLength(5, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.maxCodeLength(10, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.maxCodes(4, { visibility = { displayed = false } }))) + test.wait_for_events() + -- Validate `migrate` command functionality. + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "migrate", args = {} } }) + + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.supportedCredentials({"pin"}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.minPinCodeLen(5, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.maxPinCodeLen(10, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.pinUsersSupported(4, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockUsers.totalUsersSupported(4, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockUsers.users({{userIndex=1, userName="Zach", userType="guest"}, {userIndex=5, userName="Steven", userType="guest"}}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.credentials({{credentialIndex=1, credentialType="pin", userIndex=1, credentialName="Zach"}, {credentialIndex=5, credentialType="pin", userIndex=5, credentialName="Steven"}}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))) + test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Alarm.attributes.AlarmCount:read(mock_device) }) + test.wait_for_events() + assert(mock_device:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true, "SLGA_MIGRATED field should be set to true after migration") + end +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_lock_credentials_commands.lua b/drivers/SmartThings/zigbee-lock/src/test/test_lock_credentials_commands.lua new file mode 100644 index 0000000000..33293e784d --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/test/test_lock_credentials_commands.lua @@ -0,0 +1,578 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Integration tests for the lockCredentials capability commands: +-- addCredential, updateCredential, deleteCredential, deleteAllCredentials + +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local DoorLock = clusters.DoorLock +local table_utils = require "lock_utils.tables" +local constants = require "lock_utils.constants" + +local DoorLockUserStatus = DoorLock.types.DrlkUserStatus +local DoorLockUserType = DoorLock.types.DrlkUserType +local SetCodeStatus = DoorLock.types.DrlkSetCodeStatus +local ResponseStatus = DoorLock.types.DrlkPassFailStatus + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), +}) +zigbee_test_utils.prepare_zigbee_env_info() + +-- ── helpers ──────────────────────────────────────────────────────────────── + +local function test_init() + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +local function seed_users(entries) + for _, entry in ipairs(entries) do + local so_far = {} + for _, e in ipairs(entries) do + table.insert(so_far, e) + if e == entry then break end + end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users(so_far, { visibility = { displayed = false } })) + ) + assert(table_utils.add_entry(mock_device, "users", entry) == constants.COMMAND_RESULT.SUCCESS) + end + test.wait_for_events() +end + +local function seed_credentials(entries) + for _, entry in ipairs(entries) do + local so_far = {} + for _, e in ipairs(entries) do + table.insert(so_far, e) + if e == entry then break end + end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials(so_far, { visibility = { displayed = false } })) + ) + assert(table_utils.add_entry(mock_device, "credentials", entry) == constants.COMMAND_RESULT.SUCCESS) + end + test.wait_for_events() +end + +-- ============================================================================ +-- addCredential +-- ============================================================================ + +test.register_coroutine_test( + "addCredential: sends SetPINCode to the lock and emits success when the lock acknowledges", + function() + -- Queue the capability command: userIndex=1, userType="guest", credentialType="pin", credentialData="1234" + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 1, "guest", "pin", "1234" } }, + }) + + -- Expect SetPINCode to be sent to the lock + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "1234"), + }) + test.wait_for_events() + + -- Lock responds with SUCCESS + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.SetPINCodeResponse.build_test_rx(mock_device, SetCodeStatus.SUCCESS), + }) + + -- Handler adds the credential and user to the credentials table + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + )) + ) + -- commandResult with userIndex and credentialIndex + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "success", userIndex = 1, credentialIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addCredential: emits failure when the lock returns GENERAL_FAILURE", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 1, "guest", "pin", "1234" } }, + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "1234"), + }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.SetPINCodeResponse.build_test_rx(mock_device, SetCodeStatus.GENERAL_FAILURE), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addCredential: emits resourceExhausted when the lock returns MEMORY_FULL", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 2, "guest", "pin", "5678" } }, + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 2, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "5678"), + }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.SetPINCodeResponse.build_test_rx(mock_device, SetCodeStatus.MEMORY_FULL), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "resourceExhausted" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addCredential: emits duplicate when the lock returns DUPLICATE_CODE", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 1, "guest", "pin", "1234" } }, + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "1234"), + }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.SetPINCodeResponse.build_test_rx(mock_device, SetCodeStatus.DUPLICATE_CODE), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "duplicate" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addCredential: returns busy when another operation is already in progress", + function() + -- Put the device into busy state by starting an addCredential operation + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 1, "guest", "pin", "1234" } }, + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "1234"), + }) + test.wait_for_events() + + -- Second addCredential while first is still pending → should get busy + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 2, "guest", "pin", "5678" } }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "busy" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- updateCredential +-- ============================================================================ + +test.register_coroutine_test( + "updateCredential: sends SetPINCode and emits success when the lock acknowledges", + function() + seed_credentials({ { userIndex = 1, credentialIndex = 1, credentialType = "pin" } }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "updateCredential", + args = { 1, 1, "pin", "newPin9" } }, + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "newPin9"), + }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.SetPINCodeResponse.build_test_rx(mock_device, SetCodeStatus.SUCCESS), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "updateCredential", statusCode = "success", userIndex = 1, credentialIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "updateCredential: returns failure immediately when the credential does not exist in the table", + function() + -- No credentials seeded — credentialIndex 99 does not exist + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "updateCredential", + args = { 99, 99, "pin", "badPin" } }, + }) + + -- No zigbee message should be sent + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "updateCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "updateCredential: returns busy when another operation is already in progress", + function() + seed_credentials({ { userIndex = 1, credentialIndex = 1, credentialType = "pin" } }) + + -- Start first updateCredential to make device busy + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "updateCredential", + args = { 1, 1, "pin", "pin1111" } }, + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "pin1111"), + }) + test.wait_for_events() + + -- Second updateCredential while first is pending + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "updateCredential", + args = { 1, 1, "pin", "pin2222" } }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "updateCredential", statusCode = "busy" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteCredential +-- ============================================================================ + +test.register_coroutine_test( + "deleteCredential: sends ClearPINCode and emits success with indices when the lock returns PASS", + function() + seed_users({ { userIndex = 1, userName = "Alice", userType = "guest" } }) + seed_credentials({ { userIndex = 1, credentialIndex = 1, credentialType = "pin" } }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 1, "pin" } }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.SendPINOverTheAir:write(mock_device, true) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearPINCode(mock_device, 1) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearPINCodeResponse.build_test_rx(mock_device, ResponseStatus.PASS), + }) + + -- credential table and user should be deleted + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "success", credentialIndex = 1, userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteCredential: emits failure when the lock returns FAIL", + function() + seed_credentials({ { userIndex = 1, credentialIndex = 1, credentialType = "pin" } }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 1, "pin" } }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.SendPINOverTheAir:write(mock_device, true) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearPINCode(mock_device, 1) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearPINCodeResponse.build_test_rx(mock_device, ResponseStatus.FAIL), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteCredential: returns failure immediately when the credentialIndex does not exist in the table", + function() + -- No credentials seeded — index 5 is unknown + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 5, "pin" } }, + }) + + -- No zigbee messages should be sent + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteCredential: returns busy when another operation is already in progress", + function() + seed_credentials({ + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 2, credentialIndex = 2, credentialType = "pin" }, + }) + + -- Start first deleteCredential to make device busy + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 1, "pin" } }, + }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.SendPINOverTheAir:write(mock_device, true) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearPINCode(mock_device, 1) }) + test.wait_for_events() + + -- Second deleteCredential while first is pending → busy + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 2, "pin" } }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "busy" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteAllCredentials +-- ============================================================================ + +test.register_coroutine_test( + "deleteAllCredentials: sends ClearAllPINCodes and emits success when the lock returns PASS", + function() + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + }) + seed_credentials({ + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteAllCredentials", args = {} }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearAllPINCodes(mock_device) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearAllPINCodesResponse.build_test_rx(mock_device, ResponseStatus.PASS), + }) + + -- deleteAllCredentials only clears credentials; users table is untouched + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "success" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteAllCredentials: emits failure when the lock returns FAIL", + function() + seed_credentials({ { userIndex = 1, credentialIndex = 1, credentialType = "pin" } }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteAllCredentials", args = {} }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearAllPINCodes(mock_device) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearAllPINCodesResponse.build_test_rx(mock_device, ResponseStatus.FAIL), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteAllCredentials: returns busy when another operation is already in progress", + function() + seed_credentials({ { userIndex = 1, credentialIndex = 1, credentialType = "pin" } }) + + -- First deleteAllCredentials starts the zigbee flow (device is now busy) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteAllCredentials", args = {} }, + }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearAllPINCodes(mock_device) }) + test.wait_for_events() + + -- Second deleteAllCredentials while first is pending → busy + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteAllCredentials", args = {} }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "busy" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_lock_pre_configured.lua b/drivers/SmartThings/zigbee-lock/src/test/test_lock_pre_configured.lua new file mode 100644 index 0000000000..0eb4ac396f --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/test/test_lock_pre_configured.lua @@ -0,0 +1,653 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Integration tests for lockUsers and lockCredentials commands with state +-- pre-configured before each test. Two users and two credentials are seeded +-- at the start of every test so tests can focus on the various response states +-- produced by the zigbee response handlers in commands.lua. + +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local DoorLock = clusters.DoorLock +local table_utils = require "lock_utils.tables" +local constants = require "lock_utils.constants" + +local DoorLockUserStatus = DoorLock.types.DrlkUserStatus +local DoorLockUserType = DoorLock.types.DrlkUserType +local SetCodeStatus = DoorLock.types.DrlkSetCodeStatus +local ResponseStatus = DoorLock.types.DrlkPassFailStatus + +-- ── Shared device ────────────────────────────────────────────────────────── + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), +}) +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) + +-- ── Seeding helpers ──────────────────────────────────────────────────────── + +local INITIAL_USERS = { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, +} +local INITIAL_CREDS = { + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 2, credentialIndex = 2, credentialType = "pin" }, +} + +-- Seed a list of entries into a named table, consuming the resulting events. +local function seed_table(attribute_fn, table_name, entries) + local accumulated = {} + for _, entry in ipairs(entries) do + table.insert(accumulated, entry) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + attribute_fn(accumulated, { visibility = { displayed = false } })) + ) + assert( + table_utils.add_entry(mock_device, table_name, entry) == constants.COMMAND_RESULT.SUCCESS, + "seed_table: add_entry failed for " .. table_name + ) + end + test.wait_for_events() +end + +-- Pre-configure each test with 2 users and 2 credentials. +local function setup_state() + seed_table(capabilities.lockUsers.users, "users", INITIAL_USERS) + seed_table(capabilities.lockCredentials.credentials, "credentials", INITIAL_CREDS) +end + +-- ============================================================================ +-- addUser — pre-configured device (2 users already present) +-- ============================================================================ + +test.register_coroutine_test( + "addUser (pre-configured): assigns the next available index (3) when two users already exist", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Carol", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "success", userIndex = 3 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- updateUser — pre-configured device +-- ============================================================================ + +test.register_coroutine_test( + "updateUser (pre-configured): updates Alice's name and emits success with userIndex", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "updateUser", args = { 1, "AliceRenamed", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "AliceRenamed", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "updateUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + + +-- ============================================================================ +-- deleteUser with associated credential — exercises clear_pin_code_response +-- ============================================================================ + +test.register_coroutine_test( + "deleteUser (pre-configured, PASS): removes both user and credential and emits success for each", + function() + setup_state() + + -- Delete user 1 who has credential 1 → driver injects deleteCredential → ClearPINCode + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteUser", args = { 1 } }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.SendPINOverTheAir:write(mock_device, true) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearPINCode(mock_device, 1) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearPINCodeResponse.build_test_rx(mock_device, ResponseStatus.PASS), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 2, credentialIndex = 2, credentialType = "pin" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 2, userName = "Bob", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "success", credentialIndex = 1, userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteUser (pre-configured, FAIL): emits failure for both capabilities when the lock rejects", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteUser", args = { 2 } }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.SendPINOverTheAir:write(mock_device, true) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearPINCode(mock_device, 2) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearPINCodeResponse.build_test_rx(mock_device, ResponseStatus.FAIL), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteUser", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- addCredential response states — set_pin_code_response +-- ============================================================================ + +test.register_coroutine_test( + "addCredential (pre-configured): succeeds for a new slot and emits userIndex + credentialIndex", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 3, "guest", "pin", "pin03" } }, + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 3, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "pin03"), + }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.SetPINCodeResponse.build_test_rx(mock_device, SetCodeStatus.SUCCESS), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + { userIndex = 3, userName = "Guest 3", userType = "guest" }, -- default name since lock doesn't provide one + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 2, credentialIndex = 2, credentialType = "pin" }, + { userIndex = 3, credentialIndex = 3, credentialType = "pin", credentialName = "Guest 3" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "success", userIndex = 3, credentialIndex = 3 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addCredential (pre-configured): emits duplicate when the lock rejects with DUPLICATE_CODE", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 3, "guest", "pin", "pin01" } }, + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 3, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "pin01"), + }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.SetPINCodeResponse.build_test_rx(mock_device, SetCodeStatus.DUPLICATE_CODE), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "duplicate" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addCredential (pre-configured): emits resourceExhausted when the lock returns MEMORY_FULL", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 3, "guest", "pin", "pin03" } }, + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 3, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "pin03"), + }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.SetPINCodeResponse.build_test_rx(mock_device, SetCodeStatus.MEMORY_FULL), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "resourceExhausted" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- updateCredential response states — set_pin_code_response +-- ============================================================================ + +test.register_coroutine_test( + "updateCredential (pre-configured): succeeds and emits success with userIndex and credentialIndex", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "updateCredential", + args = { 1, 1, "pin", "newPin1" } }, + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "newPin1"), + }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.SetPINCodeResponse.build_test_rx(mock_device, SetCodeStatus.SUCCESS), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "updateCredential", statusCode = "success", userIndex = 1, credentialIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "updateCredential (pre-configured): returns failure immediately for a non-existent credentialIndex", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "updateCredential", + args = { 99, 99, "pin", "badPin" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "updateCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteCredential standalone (lockCredentials.DELETE path) +-- ============================================================================ + +test.register_coroutine_test( + "deleteCredential (pre-configured, PASS): removes credential and emits success with indices", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 1, "pin" } }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.SendPINOverTheAir:write(mock_device, true) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearPINCode(mock_device, 1) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearPINCodeResponse.build_test_rx(mock_device, ResponseStatus.PASS), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 2, credentialIndex = 2, credentialType = "pin" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 2, userName = "Bob", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "success", credentialIndex = 1, userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteCredential (pre-configured, FAIL): emits failure when the lock rejects the clear", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 2, "pin" } }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.SendPINOverTheAir:write(mock_device, true) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearPINCode(mock_device, 2) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearPINCodeResponse.build_test_rx(mock_device, ResponseStatus.FAIL), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteAllCredentials standalone (lockCredentials.DELETE_ALL path) +-- ============================================================================ + +test.register_coroutine_test( + "deleteAllCredentials (pre-configured, PASS): clears only credentials and emits success", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteAllCredentials", args = {} }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearAllPINCodes(mock_device) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearAllPINCodesResponse.build_test_rx(mock_device, ResponseStatus.PASS), + }) + + -- Only credentials table is cleared; users table is untouched (no lockUsers.users event) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "success" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteAllCredentials (pre-configured, FAIL): emits failure and leaves tables unchanged", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteAllCredentials", args = {} }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearAllPINCodes(mock_device) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearAllPINCodesResponse.build_test_rx(mock_device, ResponseStatus.FAIL), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteAllUsers (lockUsers.DELETE_ALL path — clears both tables) +-- ============================================================================ + +test.register_coroutine_test( + "deleteAllUsers (pre-configured, PASS): clears both tables and emits success for both capabilities", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteAllUsers", args = {} }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearAllPINCodes(mock_device) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearAllPINCodesResponse.build_test_rx(mock_device, ResponseStatus.PASS), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteAllUsers", statusCode = "success" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "success" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteAllUsers (pre-configured, FAIL): emits failure for both capabilities when the lock rejects", + function() + setup_state() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteAllUsers", args = {} }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearAllPINCodes(mock_device) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearAllPINCodesResponse.build_test_rx(mock_device, ResponseStatus.FAIL), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteAllUsers", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_lock_programming_events.lua b/drivers/SmartThings/zigbee-lock/src/test/test_lock_programming_events.lua new file mode 100644 index 0000000000..3c06afef66 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/test/test_lock_programming_events.lua @@ -0,0 +1,574 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Tests for the ProgrammingEventNotification handler in lock_handlers/zigbee_responses.lua. +-- +-- Cases covered: +-- • PIN_CODE_ADDED received while NOT busy (manual addition at the lock) +-- • PIN_CODE_DELETED received while NOT busy (manual deletion at the lock) +-- • PIN_CODE_CHANGED received while NOT busy (manual update — not currently handled) +-- • PIN_CODE_ADDED while addCredential in flight with matching user (failsafe path) +-- • PIN_CODE_CHANGED while updateCredential in flight with matching user (failsafe path) +-- • PIN_CODE_DELETED while deleteCredential in flight with matching user (failsafe path) +-- • PIN_CODE_ADDED while busy with DIFFERENT user (processed as manual event) +-- • PIN_CODE_DELETED while busy with DIFFERENT user (processed as manual event) +-- • PIN_CODE_ADDED received after BUSY ends (late notification from our SetPINCode; credential +-- not double-added) +-- • PIN_CODE_ADDED received after BUSY ends (both entries already exist; complete no-op) +-- • PIN_CODE_DELETED received after BUSY ends (late notification from our ClearPINCode; credential +-- already deleted) +-- • PIN_CODE_CHANGED received after BUSY ends (late notification from our SetPINCode update; +-- not handled, no effect) + +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 DoorLock = clusters.DoorLock +local capabilities = require "st.capabilities" +local constants = require "lock_utils.constants" + +local DoorLockUserStatus = DoorLock.types.DrlkUserStatus +local DoorLockUserType = DoorLock.types.DrlkUserType +local ProgrammingEventCode = DoorLock.types.ProgramEventCode +local SetCodeStatus = DoorLock.types.DrlkSetCodeStatus +local ResponseStatus = DoorLock.types.DrlkPassFailStatus + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), +}) + +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) + +-- Build a ProgrammingEventNotification ZigBee receive message for the given event code and user ID. +local function build_programming_event(event_code, user_id) + return { + mock_device.id, + DoorLock.client.commands.ProgrammingEventNotification.build_test_rx( + mock_device, + 0x00, -- program_event_source (keypad) + event_code, + user_id, + "1234", -- PIN (not used by the handler) + DoorLockUserType.UNRESTRICTED, + DoorLockUserStatus.OCCUPIED_ENABLED, + 0x0000, -- local_alarm_mask + "data" -- user_description + ) + } +end + +-- ───────────────────────────────────────────────────────────────────────────── +-- NOT-BUSY CASES (manual events from the lock, no command in flight) +-- ───────────────────────────────────────────────────────────────────────────── + +test.register_coroutine_test( + "ProgrammingEventNotification PIN_CODE_ADDED while not busy syncs user and credential entries", + function() + -- Route events to the new capabilities handler, not the legacy lockCodes handler. + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_ADDED, 1)) + -- Users table receives the new entry + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + ) + ) + ) + -- Credentials table receives the new entry + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + ) + ) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "ProgrammingEventNotification PIN_CODE_DELETED while not busy removes user and credential entries", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + -- Set up an existing entry by processing a manual addition first. + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_ADDED, 1)) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + ) + ) + ) + test.wait_for_events() + + -- Now delete the same entry via a manual deletion event. + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_DELETED, 1)) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } }) + ) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "ProgrammingEventNotification PIN_CODE_CHANGED while not busy has no effect on table state", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + -- PIN_CODE_CHANGED is not handled by the notification handler, so no events should + -- be emitted and table state should remain unchanged. + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_CHANGED, 1)) + test.wait_for_events() + end +) + +-- ───────────────────────────────────────────────────────────────────────────── +-- BUSY CASES (notification arrives while one of our commands is in flight) +-- The handler must be a no-op so we do not double-process the result. +-- ───────────────────────────────────────────────────────────────────────────── + +test.register_coroutine_test( + "ProgrammingEventNotification PIN_CODE_ADDED while addCredential is in flight acts as failsafe", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + -- Simulate an addCredential command being in flight with matching user ID. + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.LOCK_CREDENTIALS.ADD, {}) + mock_device:set_field(constants.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE, { + userIndex = 1, credentialIndex = 1, credentialType = "pin" + }, {}) + + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_ADDED, 1)) + -- Failsafe path: notification handled as command success, user and credential added, commandResult emitted. + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin" } }, + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "success", userIndex = 1, credentialIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + ) + ) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "ProgrammingEventNotification PIN_CODE_CHANGED while updateCredential is in flight acts as failsafe", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + -- Simulate an updateCredential command being in flight with matching user ID. + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.LOCK_CREDENTIALS.UPDATE, {}) + mock_device:set_field(constants.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE, { + userIndex = 1, credentialIndex = 1, credentialType = "pin" + }, {}) + + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_CHANGED, 1)) + -- Failsafe path: notification handled as command success, commandResult emitted. + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "updateCredential", statusCode = "success", userIndex = 1, credentialIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + ) + ) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "ProgrammingEventNotification PIN_CODE_DELETED while deleteCredential is in flight acts as failsafe", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + -- First, add a credential so we have something to delete via the failsafe path. + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_ADDED, 1)) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + ) + ) + ) + test.wait_for_events() + + -- Simulate a deleteCredential command being in flight with matching user ID. + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.LOCK_CREDENTIALS.DELETE, {}) + mock_device:set_field(constants.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE, { + userIndex = 1, credentialIndex = 1, credentialType = "pin" + }, {}) + + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_DELETED, 1)) + -- Failsafe path: notification handled as command success, credential and user removed, commandResult emitted. + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "success", userIndex = 1, credentialIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + ) + ) + ) + test.wait_for_events() + end +) + +-- ───────────────────────────────────────────────────────────────────────────── +-- BUSY WITH DIFFERENT USER (notification for a different user arrives while +-- a command is in flight; processed as a normal manual event) +-- ───────────────────────────────────────────────────────────────────────────── + +test.register_coroutine_test( + "ProgrammingEventNotification PIN_CODE_ADDED for different user while busy is processed as manual event", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + -- Simulate an addCredential command in flight for user 1. + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.LOCK_CREDENTIALS.ADD, {}) + mock_device:set_field(constants.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE, { + userIndex = 1, credentialIndex = 1, credentialType = "pin" + }, {}) + + -- Notification arrives for user 2 (different user) — should be processed as manual event. + -- The manual handler assigns userIndex = next_index (1, the first available slot), not user_id (2). + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_ADDED, 2)) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 2, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + ) + ) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "ProgrammingEventNotification PIN_CODE_DELETED for different user while busy is processed as manual event", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + -- First, add a credential for user 2 so we have something to delete. + -- The manual handler assigns userIndex = next_index (1, the first available slot), not user_id (2). + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_ADDED, 2)) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 2, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + ) + ) + ) + test.wait_for_events() + + -- Simulate a deleteCredential command in flight for user 1. + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.LOCK_CREDENTIALS.DELETE, {}) + mock_device:set_field(constants.DRIVER_STATE.CREDENTIAL_ARGS_IN_USE, { + userIndex = 1, credentialIndex = 1, credentialType = "pin" + }, {}) + + -- Notification arrives for user 2 (different user) — should be processed as manual deletion. + -- Delete handler emits credentials first, then users. + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_DELETED, 2)) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } }) + ) + ) + test.wait_for_events() + end +) + +-- ───────────────────────────────────────────────────────────────────────────── +-- POST-BUSY CASES (busy state was already cleared by the ZigBee response +-- handler before the ProgrammingEventNotification arrives; the notification was +-- sent by the lock in response to our own SetPINCode / ClearPINCode command) +-- ───────────────────────────────────────────────────────────────────────────── + +test.register_coroutine_test( + "Late PIN_CODE_ADDED after addCredential: user and credential already exist; complete no-op", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + -- Complete an addCredential flow so both the user and credentials tables already have entries. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 1, "guest", "pin", "1234" } }, + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "1234"), + }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.SetPINCodeResponse.build_test_rx(mock_device, SetCodeStatus.SUCCESS), + }) + -- User and credential added; commandResult emitted; busy state cleared. + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "success", userIndex = 1, credentialIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + + -- Late ProgrammingEventNotification PIN_CODE_ADDED for the same slot arrives after busy ends. + -- The credential is already in the table → find_entry returns it → handler exits early. + -- Both user and credential already exist, so no events are emitted. + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_ADDED, 1)) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Late PIN_CODE_ADDED when both user and credential already exist: both add_entry calls are no-ops", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + -- Populate both tables by processing a not-busy PIN_CODE_ADDED (simulates the completed command state). + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_ADDED, 1)) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + )) + ) + test.wait_for_events() + + -- Late notification for the same slot; both add_entry calls return OCCUPIED → no events. + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_ADDED, 1)) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Late PIN_CODE_DELETED after deleteCredential: credential and user already removed; no-op", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + -- Populate both tables (user at index 1, credential at index 1). + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_ADDED, 1)) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + )) + ) + test.wait_for_events() + + -- Run a standalone deleteCredential flow; this removes the credential but leaves the user. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "deleteCredential", + args = { 1, "pin" } }, + }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.SendPINOverTheAir:write(mock_device, true) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearPINCode(mock_device, 1) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearPINCodeResponse.build_test_rx(mock_device, ResponseStatus.PASS), + }) + -- Credential and user entries removed (no remaining credentials for this user). Busy state cleared. + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "success", credentialIndex = 1, userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + + -- Late PIN_CODE_DELETED for the same slot arrives after busy ends. + -- The credential and user are already gone → therefore nothing happens. + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_DELETED, 1)) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Late PIN_CODE_CHANGED after updateCredential: not handled by notification handler, no events emitted", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + -- Seed user and credential so updateCredential has an existing entry to modify. + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_ADDED, 1)) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + )) + ) + test.wait_for_events() + + -- Complete an updateCredential flow so the credential entry is updated and busy state is cleared. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "updateCredential", + args = { 1, 1, "pin", "5678" } }, + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "5678"), + }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.SetPINCodeResponse.build_test_rx(mock_device, SetCodeStatus.SUCCESS), + }) + -- UPDATE doesn't modify the credentials table metadata, only the PIN code (not stored). + -- The response handler just emits commandResult; busy state is cleared. + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "updateCredential", statusCode = "success", userIndex = 1, credentialIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + + -- Late PIN_CODE_CHANGED notification arrives after busy ends. + -- The notification handler does not handle PIN_CODE_CHANGED → no events emitted. + test.socket.zigbee:__queue_receive(build_programming_event(ProgrammingEventCode.PIN_CODE_CHANGED, 1)) + test.wait_for_events() + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_lock_tables.lua b/drivers/SmartThings/zigbee-lock/src/test/test_lock_tables.lua new file mode 100644 index 0000000000..82ecd7f964 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/test/test_lock_tables.lua @@ -0,0 +1,728 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Unit tests for lock_utils/tables.lua +-- Tests directly call table_utils functions and verify both return values +-- and emitted capability events. + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local capabilities = require "st.capabilities" +local st_utils = require "st.utils" +local table_utils = require "lock_utils.tables" +local constants = require "lock_utils.constants" + +-- --------------------------------------------------------------------------- +-- Shared mock device +-- --------------------------------------------------------------------------- + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), +}) +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) + +-- --------------------------------------------------------------------------- +-- Helpers +-- --------------------------------------------------------------------------- + +-- Seed the users table with `entries` and consume the resulting emit_events. +-- After this call the state cache has those entries and the socket is clean. +local function seed_users(entries) + for _, entry in ipairs(entries) do + -- Build the expected post-insert table up to this entry. + local expected = {} + for _, e in ipairs(entries) do + table.insert(expected, e) + if e == entry then break end + end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users(expected, { visibility = { displayed = false } })) + ) + local result = table_utils.add_entry(mock_device, "users", entry) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "seed_users: add_entry failed for entry userIndex=" .. tostring(entry.userIndex)) + end + test.wait_for_events() +end + +-- Seed the credentials table with `entries` and consume the resulting emit_events. +local function seed_credentials(entries) + for _, entry in ipairs(entries) do + local expected = {} + for _, e in ipairs(entries) do + table.insert(expected, e) + if e == entry then break end + end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials(expected, { visibility = { displayed = false } })) + ) + local result = table_utils.add_entry(mock_device, "credentials", entry) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "seed_credentials: add_entry failed for entry credentialIndex=" .. tostring(entry.credentialIndex)) + end + test.wait_for_events() +end + +-- =========================================================================== +-- add_entry +-- =========================================================================== + +test.register_coroutine_test( + "add_entry: adds a new user entry and emits the updated table", + function() + local entry = { userIndex = 1, userType = "guest", userName = "Alice" } + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({ entry }, { visibility = { displayed = false } })) + ) + + local result = table_utils.add_entry(mock_device, "users", entry) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "add_entry: returns OCCUPIED when an entry with the same userIndex already exists", + function() + local entry = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ entry }) + + -- Attempt to add a different entry with the same userIndex (match_key) + local duplicate = { userIndex = 1, userType = "unrestricted", userName = "Bob" } + local result = table_utils.add_entry(mock_device, "users", duplicate) + assert(result == constants.COMMAND_RESULT.OCCUPIED, + "Expected OCCUPIED, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "add_entry: returns RESOURCE_EXHAUSTED when table is at max capacity", + function() + local entries = {} + for i = 1, 20 do + entries[i] = { userIndex = i, userType = "guest", userName = "User" .. i } + end + seed_users(entries) + + local overflow = { userIndex = 21, userType = "guest", userName = "Overflow" } + local result = table_utils.add_entry(mock_device, "users", overflow) + assert(result == constants.COMMAND_RESULT.RESOURCE_EXHAUSTED, + "Expected RESOURCE_EXHAUSTED, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "add_entry: returns FAILURE when a required key is missing", + function() + -- userType is required for users table + local incomplete = { userIndex = 1 } + local result = table_utils.add_entry(mock_device, "users", incomplete) + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "add_entry: returns FAILURE for an unknown table name", + function() + local result = table_utils.add_entry(mock_device, "nonexistent_table", + { userIndex = 1, userType = "guest" }) + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "add_entry: adds a credential entry and emits updated credentials table", + function() + local entry = { userIndex = 1, credentialIndex = 1, credentialType = "pin" } + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({ entry }, { visibility = { displayed = false } })) + ) + + local result = table_utils.add_entry(mock_device, "credentials", entry) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + + test.wait_for_events() + end +) + +-- =========================================================================== +-- update_entry +-- =========================================================================== + +test.register_coroutine_test( + "update_entry: updates an existing user entry and emits updated table", + function() + local original = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ original }) + + local expected_after_update = { + { userIndex = 1, userType = "adminMember", userName = "Alice_Updated" } + } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users(expected_after_update, { visibility = { displayed = false } })) + ) + + local result = table_utils.update_entry(mock_device, "users", 1, + { userName = "Alice_Updated", userType = "adminMember" }) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "update_entry: returns FAILURE when no entry matches the match_key value", + function() + local result = table_utils.update_entry(mock_device, "users", 99, + { userName = "Ghost" }) + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "update_entry: only updates the specified fields, leaves others intact", + function() + local entry1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local entry2 = { userIndex = 2, userType = "guest", userName = "Bob" } + seed_users({ entry1, entry2 }) + + -- Update only userName of entry 2; userType should remain "guest" + local expected = { + { userIndex = 1, userType = "guest", userName = "Alice" }, + { userIndex = 2, userType = "guest", userName = "Bob_Renamed" }, + } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users(expected, { visibility = { displayed = false } })) + ) + + local result = table_utils.update_entry(mock_device, "users", 2, { userName = "Bob_Renamed" }) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "update_entry: returns FAILURE for an unknown table name", + function() + local result = table_utils.update_entry(mock_device, "bad_table", 1, { userName = "X" }) + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +-- =========================================================================== +-- delete_entry +-- =========================================================================== + +test.register_coroutine_test( + "delete_entry: deletes an existing entry and returns COMMAND_RESULT.SUCCESS", + function() + local entry = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ entry }) + + -- After deletion the table is empty + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + + local result = table_utils.delete_entry(mock_device, "users", 1) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "delete_entry: returns FAILURE when no entry matches the match_key value", + function() + local result = table_utils.delete_entry(mock_device, "users", 99) + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "delete_entry: remaining entries are intact after a deletion", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e2 = { userIndex = 2, userType = "guest", userName = "Bob" } + local e3 = { userIndex = 3, userType = "guest", userName = "Carol" } + seed_users({ e1, e2, e3 }) + + -- Delete the middle entry; expect e1 and e3 remain + local expected_remaining = { + { userIndex = 1, userType = "guest", userName = "Alice" }, + { userIndex = 3, userType = "guest", userName = "Carol" }, + } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users(expected_remaining, { visibility = { displayed = false } })) + ) + + local result = table_utils.delete_entry(mock_device, "users", 2) + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "delete_entry: returns FAILURE for an unknown table name", + function() + local result = table_utils.delete_entry(mock_device, "bad_table", 1) + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +-- =========================================================================== +-- delete_all_entries +-- =========================================================================== + +test.register_coroutine_test( + "delete_all_entries: emits an empty users table and returns SUCCESS", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e2 = { userIndex = 2, userType = "guest", userName = "Bob" } + seed_users({ e1, e2 }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + + local result = table_utils.delete_all_entries(mock_device, "users") + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "delete_all_entries: returns SUCCESS even when the table is already empty", + function() + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + + local result = table_utils.delete_all_entries(mock_device, "users") + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "delete_all_entries: returns FAILURE for an unknown table name", + function() + local result = table_utils.delete_all_entries(mock_device, "bad_table") + assert(result == constants.COMMAND_RESULT.FAILURE, + "Expected FAILURE, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "delete_all_entries: emits an empty credentials table and returns SUCCESS", + function() + local cred = { userIndex = 1, credentialIndex = 1, credentialType = "pin" } + seed_credentials({ cred }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } })) + ) + + local result = table_utils.delete_all_entries(mock_device, "credentials") + assert(result == constants.COMMAND_RESULT.SUCCESS, + "Expected SUCCESS, got: " .. tostring(result)) + + test.wait_for_events() + end +) + +-- =========================================================================== +-- find_entry +-- =========================================================================== + +test.register_coroutine_test( + "find_entry: returns the matching entry when it exists", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e2 = { userIndex = 2, userType = "guest", userName = "Bob" } + seed_users({ e1, e2 }) + + local result = table_utils.find_entry(mock_device, "users", 2) + assert(type(result) == "table", + "Expected a table entry, got: " .. tostring(result)) + assert(result.userIndex == 2, + "Expected userIndex == 2, got: " .. tostring(result.userIndex)) + assert(result.userName == "Bob", + "Expected userName == 'Bob', got: " .. tostring(result.userName)) + end +) + +test.register_coroutine_test( + "find_entry: returns nil when no entry matches the value", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ e1 }) + + local result = table_utils.find_entry(mock_device, "users", 99) + assert(result == nil, + "Expected nil for missing entry, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "find_entry: finds a credential entry by credentialIndex", + function() + local cred = { userIndex = 1, credentialIndex = 5, credentialType = "pin" } + seed_credentials({ cred }) + + local result = table_utils.find_entry(mock_device, "credentials", 5) + assert(type(result) == "table", + "Expected a table entry, got: " .. tostring(result)) + assert(result.credentialIndex == 5, + "Expected credentialIndex == 5, got: " .. tostring(result.credentialIndex)) + end +) + +-- =========================================================================== +-- find_entry_by +-- =========================================================================== + +test.register_coroutine_test( + "find_entry_by: returns the entry matching an arbitrary key", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e2 = { userIndex = 2, userType = "adminMember", userName = "Bob" } + seed_users({ e1, e2 }) + + local result = table_utils.find_entry_by(mock_device, "users", "userName", "Bob") + assert(type(result) == "table", + "Expected a table entry, got: " .. tostring(result)) + assert(result.userIndex == 2, + "Expected userIndex == 2, got: " .. tostring(result.userIndex)) + end +) + +test.register_coroutine_test( + "find_entry_by: returns nil when no entry matches", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ e1 }) + + local result = table_utils.find_entry_by(mock_device, "users", "userName", "Nobody") + assert(result == nil, + "Expected nil for unmatched key/value, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "find_entry_by: returns the first matching entry when multiple entries share the same value", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e2 = { userIndex = 2, userType = "guest", userName = "Bob" } + local e3 = { userIndex = 3, userType = "guest", userName = "Carol" } + seed_users({ e1, e2, e3 }) + + local result = table_utils.find_entry_by(mock_device, "users", "userType", "guest") + assert(type(result) == "table", + "Expected a table entry, got: " .. tostring(result)) + assert(result.userIndex == 1, + "Expected first match userIndex == 1, got: " .. tostring(result.userIndex)) + end +) + +-- =========================================================================== +-- next_index +-- =========================================================================== + +test.register_coroutine_test( + "next_index: returns 1 when the table is empty", + function() + local result = table_utils.next_index(mock_device, "users") + assert(result == 1, + "Expected 1 for empty table, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "next_index: returns the next sequential index after a contiguous range", + function() + local entries = {} + for i = 1, 3 do + entries[i] = { userIndex = i, userType = "guest", userName = "User" .. i } + end + seed_users(entries) + + local result = table_utils.next_index(mock_device, "users") + assert(result == 4, + "Expected 4 after indices 1-3, got: " .. tostring(result)) + end +) + +-- this should not happen during normal operation. +test.register_coroutine_test( + "next_index: returns the lowest gap when indices are non-contiguous", + function() + -- Insert entries at indices 1 and 3, leaving a gap at 2 + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e3 = { userIndex = 3, userType = "guest", userName = "Carol" } + seed_users({ e1, e3 }) + + local result = table_utils.next_index(mock_device, "users") + assert(result == 2, + "Expected 2 as the lowest gap, got: " .. tostring(result)) + end +) + +-- =========================================================================== +-- get_max_entries +-- =========================================================================== + +test.register_coroutine_test( + "get_max_entries: returns the default of 20 when the attribute is not set", + function() + local result = table_utils.get_max_entries(mock_device, "users") + assert(result == 20, + "Expected default max of 20, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "get_max_entries: returns the default of 20 for the credentials table when the attribute is not set", + function() + local result = table_utils.get_max_entries(mock_device, "credentials") + assert(result == 20, + "Expected default max of 20, got: " .. tostring(result)) + end +) + +-- =========================================================================== +-- Persistence — mutations write to the persistent store +-- =========================================================================== + +test.register_coroutine_test( + "persist: add_entry writes the new users table to the persistent store immediately", + function() + local entry = { userIndex = 1, userType = "guest", userName = "Alice" } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({ entry }, { visibility = { displayed = false } })) + ) + table_utils.add_entry(mock_device, "users", entry) + test.wait_for_events() + + local persisted = st_utils.deep_copy(mock_device:get_field("persistedUsers")) + assert(type(persisted) == "table" and #persisted == 1, + "Expected 1 persisted user, got: " .. tostring(persisted and #persisted)) + assert(persisted[1].userIndex == 1 and persisted[1].userName == "Alice", + "Persisted user data does not match the added entry") + end +) + +test.register_coroutine_test( + "persist: update_entry writes the updated users table to the persistent store immediately", + function() + local entry = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ entry }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userType = "adminMember", userName = "Alice" } }, + { visibility = { displayed = false } })) + ) + table_utils.update_entry(mock_device, "users", 1, { userType = "adminMember" }) + test.wait_for_events() + + local persisted = st_utils.deep_copy(mock_device:get_field("persistedUsers")) + assert(type(persisted) == "table" and #persisted == 1, + "Expected 1 persisted user after update") + assert(persisted[1].userType == "adminMember", + "Persisted user type was not updated, got: " .. tostring(persisted[1].userType)) + end +) + +test.register_coroutine_test( + "persist: delete_entry removes the entry from the persistent store immediately", + function() + local e1 = { userIndex = 1, userType = "guest", userName = "Alice" } + local e2 = { userIndex = 2, userType = "guest", userName = "Bob" } + seed_users({ e1, e2 }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({ e2 }, { visibility = { displayed = false } })) + ) + table_utils.delete_entry(mock_device, "users", 1) + test.wait_for_events() + + local persisted = st_utils.deep_copy(mock_device:get_field("persistedUsers")) + assert(type(persisted) == "table" and #persisted == 1, + "Expected 1 persisted user after delete, got: " .. tostring(persisted and #persisted)) + assert(persisted[1].userIndex == 2, + "Expected remaining user to have userIndex == 2, got: " .. tostring(persisted[1].userIndex)) + end +) + +test.register_coroutine_test( + "persist: delete_all_entries writes an empty table to the persistent store", + function() + local entry = { userIndex = 1, userType = "guest", userName = "Alice" } + seed_users({ entry }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + table_utils.delete_all_entries(mock_device, "users") + test.wait_for_events() + + local persisted = st_utils.deep_copy(mock_device:get_field("persistedUsers")) + assert(type(persisted) == "table" and #persisted == 0, + "Expected empty persistent store after delete_all, got: " .. tostring(persisted and #persisted)) + end +) + +test.register_coroutine_test( + "persist: add_entry writes the new credentials table to the persistent store immediately", + function() + local cred = { userIndex = 1, credentialIndex = 1, credentialType = "pin" } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({ cred }, { visibility = { displayed = false } })) + ) + table_utils.add_entry(mock_device, "credentials", cred) + test.wait_for_events() + + local persisted = st_utils.deep_copy(mock_device:get_field("persistedCredentials")) + assert(type(persisted) == "table" and #persisted == 1, + "Expected 1 persisted credential, got: " .. tostring(persisted and #persisted)) + assert(persisted[1].credentialIndex == 1, + "Persisted credential index does not match") + end +) + +-- =========================================================================== +-- Persistence — get_state falls back to the persistent store +-- =========================================================================== + +test.register_coroutine_test( + "persist: get_state returns data from the persistent store when capability state cache is absent", + function() + -- The device was loaded with a pre-seeded persistent field (set in test_init + -- below), but no capability event has been emitted for users, so get_latest_state + -- returns nil. get_state must fall back to the persistent store. + local state = table_utils.get_state(mock_device, "users") + assert(type(state) == "table" and #state == 1, + "Expected 1 user from persistent-store fallback, got: " .. tostring(state and #state)) + assert(state[1].userIndex == 1 and state[1].userName == "Alice", + "Fallback data does not match pre-seeded persistent entry") + end, + { + test_init = function() + -- Pre-seed persistent store BEFORE add_test_device so that wrapped_init + -- copies the field into the device's persistent_store on startup. + mock_device:set_field( + "persistedUsers", + { { userIndex = 1, userType = "guest", userName = "Alice" } }, + { persist = true } + ) + test.mock_device.add_test_device(mock_device) + end, + } +) + +-- =========================================================================== +-- Persistence — restore_from_persistent_store re-emits stored tables +-- =========================================================================== + +test.register_coroutine_test( + "persist: restore_from_persistent_store emits users capability event for stored data", + function() + local user = { userIndex = 1, userType = "guest", userName = "Alice" } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({ user }, { visibility = { displayed = false } })) + ) + table_utils.restore_from_persistent_store(mock_device) + test.wait_for_events() + end, + { + test_init = function() + mock_device:set_field( + "persistedUsers", + { { userIndex = 1, userType = "guest", userName = "Alice" } }, + { persist = true } + ) + test.mock_device.add_test_device(mock_device) + end, + } +) + +test.register_coroutine_test( + "persist: restore_from_persistent_store emits credentials capability event for stored data", + function() + local cred = { userIndex = 1, credentialIndex = 1, credentialType = "pin" } + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({ cred }, { visibility = { displayed = false } })) + ) + table_utils.restore_from_persistent_store(mock_device) + test.wait_for_events() + end, + { + test_init = function() + mock_device:set_field( + "persistedCredentials", + { { userIndex = 1, credentialIndex = 1, credentialType = "pin" } }, + { persist = true } + ) + test.mock_device.add_test_device(mock_device) + end, + } +) + +test.register_coroutine_test( + "persist: restore_from_persistent_store is a no-op when the persistent store is empty", + function() + -- No capability events expected when there is nothing in the persistent store. + table_utils.restore_from_persistent_store(mock_device) + test.wait_for_events() + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_lock_users_commands.lua b/drivers/SmartThings/zigbee-lock/src/test/test_lock_users_commands.lua new file mode 100644 index 0000000000..2f68f0cfb6 --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/test/test_lock_users_commands.lua @@ -0,0 +1,654 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Integration tests for the lockUsers capability commands: +-- addUser, updateUser, deleteUser, deleteAllUsers + +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local DoorLock = clusters.DoorLock +local table_utils = require "lock_utils.tables" +local constants = require "lock_utils.constants" + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), +}) +zigbee_test_utils.prepare_zigbee_env_info() + +-- ── helpers ──────────────────────────────────────────────────────────────── + +local function test_init() + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +-- Directly insert users into the device state via table_utils (mirrors test_lock_tables.lua). +-- Consumes the resulting capability events so the socket queue stays clean. +local function seed_users(entries) + for _, entry in ipairs(entries) do + local so_far = {} + for _, e in ipairs(entries) do + table.insert(so_far, e) + if e == entry then break end + end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users(so_far, { visibility = { displayed = false } })) + ) + assert(table_utils.add_entry(mock_device, "users", entry) == constants.COMMAND_RESULT.SUCCESS, + "seed_users: add_entry failed for userIndex=" .. tostring(entry.userIndex)) + end + test.wait_for_events() +end + +-- Directly insert credentials into the device state. +local function seed_credentials(entries) + for _, entry in ipairs(entries) do + local so_far = {} + for _, e in ipairs(entries) do + table.insert(so_far, e) + if e == entry then break end + end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials(so_far, { visibility = { displayed = false } })) + ) + assert(table_utils.add_entry(mock_device, "credentials", entry) == constants.COMMAND_RESULT.SUCCESS, + "seed_credentials: add_entry failed for credentialIndex=" .. tostring(entry.credentialIndex)) + end + test.wait_for_events() +end + +-- Set totalUsersSupported on the mock device and consume the resulting event. +local function set_total_users_supported(n) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.totalUsersSupported(n, { visibility = { displayed = false } })) + ) + mock_device:emit_event(capabilities.lockUsers.totalUsersSupported(n, { visibility = { displayed = false } })) + test.wait_for_events() +end + +-- ============================================================================ +-- addUser +-- ============================================================================ + +test.register_coroutine_test( + "addUser: assigns userIndex 1 for the first user and emits a success commandResult", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Alice", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Alice", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addUser: assigns the next sequential userIndex when users already exist", + function() + seed_users({ { userIndex = 1, userName = "Alice", userType = "guest" } }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Bob", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "success", userIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addUser: fills a gap left by a deleted user rather than appending beyond max", + function() + -- Seed indices 1 and 3; index 2 is the expected gap to fill + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Bob", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "success", userIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "addUser: returns resourceExhausted when totalUsersSupported has been reached", + function() + set_total_users_supported(2) + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Carol", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "resourceExhausted" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- updateUser +-- ============================================================================ + +test.register_coroutine_test( + "updateUser: updates an existing user and emits a success commandResult with userIndex", + function() + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "updateUser", args = { 1, "AliceUpdated", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "AliceUpdated", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "updateUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "updateUser: adds user and returns success when the target userIndex does not exist", + function() + -- empty table, add user + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "updateUser", args = { 99, "Ghost", "guest" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 99, userName = "Ghost", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "updateUser", statusCode = "success", userIndex = 99 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "updateUser: can change a user's type as well as name", + function() + seed_users({ { userIndex = 1, userName = "Alice", userType = "guest" } }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "updateUser", args = { 1, "Alice", "adminMember" } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Alice", userType = "adminMember" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "updateUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteUser (no associated credential — pure local delete) +-- ============================================================================ + +test.register_coroutine_test( + "deleteUser: removes a user with no credential and emits a success commandResult with userIndex", + function() + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteUser", args = { 1 } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 2, userName = "Bob", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteUser", statusCode = "success", userIndex = 1 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteUser: returns failure when the target userIndex is not in the users table", + function() + -- empty table + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteUser", args = { 99 } }, + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteUser", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- deleteAllUsers (injects deleteAllCredentials → ClearAllPINCodes zigbee flow) +-- ============================================================================ + +test.register_coroutine_test( + "deleteAllUsers: sends ClearAllPINCodes and emits success for both users and credentials on PASS", + function() + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + }) + seed_credentials({ + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 2, credentialIndex = 2, credentialType = "pin" }, + }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteAllUsers", args = {} }, + }) + + -- deleteAllUsers injects deleteAllCredentials, which sends ClearAllPINCodes + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearAllPINCodes(mock_device) }) + test.wait_for_events() + + local ResponseStatus = DoorLock.types.DrlkPassFailStatus + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearAllPINCodesResponse.build_test_rx(mock_device, ResponseStatus.PASS), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteAllUsers", statusCode = "success" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "success" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "deleteAllUsers: emits failure for both users and credentials when the lock returns FAIL", + function() + seed_users({ { userIndex = 1, userName = "Alice", userType = "guest" } }) + seed_credentials({ { userIndex = 1, credentialIndex = 1, credentialType = "pin" } }) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteAllUsers", args = {} }, + }) + + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearAllPINCodes(mock_device) }) + test.wait_for_events() + + local ResponseStatus = DoorLock.types.DrlkPassFailStatus + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearAllPINCodesResponse.build_test_rx(mock_device, ResponseStatus.FAIL), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteAllUsers", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteAllCredentials", statusCode = "failure" }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- State-consistency: add → delete → re-add (indices 1, 2, 3 lifecycle) +-- ============================================================================ +-- +-- These tests verify that the users and credentials tables stay in sync +-- through a full lifecycle: populate three slots, remove the middle one, +-- then re-add a user and credential into the freed slot. The goal is to +-- confirm there is no stale index state that would cause duplicate entries, +-- wrong slot assignment, or mismatched user↔credential links. + +local DoorLockUserStatus = DoorLock.types.DrlkUserStatus +local DoorLockUserType = DoorLock.types.DrlkUserType +local SetCodeStatus = DoorLock.types.DrlkSetCodeStatus +local ResponseStatus = DoorLock.types.DrlkPassFailStatus + +test.register_coroutine_test( + "State-consistency: add users 1-3, deleteUser 2 (no credential), re-add user reclaims index 2", + function() + -- Populate three user slots directly (no credentials, so deleteUser will take + -- the no-ZigBee path and delete the user entry locally). + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + }) + + -- Delete user at index 2; no credential is linked so this is a pure local delete. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteUser", args = { 2 } }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteUser", statusCode = "success", userIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + + -- Re-add a user. next_index sees occupied = {1, 3}, so it assigns index 2. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Dave", "guest" } }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + { userIndex = 2, userName = "Dave", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "success", userIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "State-consistency: add users+credentials 1-3, deleteUser 2 (with credential), re-add user+credential reclaims index 2 cleanly", + function() + -- Populate three user and credential slots. + seed_users({ + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 2, userName = "Bob", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + }) + seed_credentials({ + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 2, credentialIndex = 2, credentialType = "pin" }, + { userIndex = 3, credentialIndex = 3, credentialType = "pin" }, + }) + + -- Delete user at index 2. The handler finds a linked credential and injects + -- deleteCredential, which sends ClearPINCode to the lock. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "deleteUser", args = { 2 } }, + }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.SendPINOverTheAir:write(mock_device, true) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.ClearPINCode(mock_device, 2) }) + test.wait_for_events() + + -- Lock acknowledges the deletion. + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.ClearPINCodeResponse.build_test_rx(mock_device, ResponseStatus.PASS), + }) + -- clear_pin_code_response (LOCK_USERS.DELETE path): deletes credentials first, then user. + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 3, credentialIndex = 3, credentialType = "pin" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "deleteUser", statusCode = "success", userIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "deleteCredential", statusCode = "success", credentialIndex = 2, userIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + + -- Re-add a user. next_index sees occupied = {1, 3} and assigns index 2. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockUsers.ID, command = "addUser", args = { "Dave", "guest" } }, + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { + { userIndex = 1, userName = "Alice", userType = "guest" }, + { userIndex = 3, userName = "Carol", userType = "guest" }, + { userIndex = 2, userName = "Dave", userType = "guest" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.commandResult( + { commandName = "addUser", statusCode = "success", userIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + + -- Re-add a credential for the new user at slot 2. The old credential at + -- credentialIndex 2 was cleanly removed, so add_entry must succeed without + -- returning OCCUPIED or any stale-state error. + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = capabilities.lockCredentials.ID, command = "addCredential", + args = { 2, "guest", "pin", "9999" } }, + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 2, DoorLockUserStatus.OCCUPIED_ENABLED, DoorLockUserType.UNRESTRICTED, "9999"), + }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.SetPINCodeResponse.build_test_rx(mock_device, SetCodeStatus.SUCCESS), + }) + -- Credential is freshly added at index 2 alongside the retained entries at 1 and 3. + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { + { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, + { userIndex = 3, credentialIndex = 3, credentialType = "pin" }, + { userIndex = 2, credentialIndex = 2, credentialType = "pin", credentialName = "Guest 2" }, + }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.commandResult( + { commandName = "addCredential", statusCode = "success", userIndex = 2, credentialIndex = 2 }, + { state_change = true, visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua deleted file mode 100644 index d4ee3f770f..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_code_migration.lua +++ /dev/null @@ -1,248 +0,0 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - --- Mock out globals -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 PowerConfiguration = clusters.PowerConfiguration -local DoorLock = clusters.DoorLock -local Alarm = clusters.Alarms - -local json = require "st.json" - -local mock_datastore = require "integration_test.mock_env_datastore" - -local mock_device = test.mock_device.build_test_zigbee_device( - { - profile = t_utils.get_profile_definition("base-lock.yml"), - data = { - lockCodes = json.encode({ - ["1"] = "Zach", - ["2"] = "Steven" - }) - } - } -) - -local mock_device_no_data = test.mock_device.build_test_zigbee_device( - { - profile = t_utils.get_profile_definition("base-lock.yml"), - data = {} - } -) -zigbee_test_utils.prepare_zigbee_env_info() -local function test_init()end - -test.set_test_init_function(test_init) - -test.register_coroutine_test( - "Device added data lock codes population", - function() - test.mock_device.add_test_device(mock_device) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) - test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) - test.socket.zigbee:__expect_send({ mock_device.id, Alarm.attributes.AlarmCount:read(mock_device) }) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device.id, "lockCodes", { ["1"] = "Zach", ["2"] = "Steven" }) - -- Validate state cache - mock_datastore.__assert_device_store_contains(mock_device.id, "__state_cache", - { - main = { - lockCodes = { - lockCodes = {value = json.encode({ ["1"] = "Zach", ["2"] = "Steven" }) } - } - } - } - ) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) - end, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Device added without data should function", - function() - test.mock_device.add_test_device(mock_device_no_data) - test.socket.device_lifecycle:__queue_receive({ mock_device_no_data.id, "added" }) - test.socket.zigbee:__expect_send({ mock_device_no_data.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device_no_data) }) - test.socket.zigbee:__expect_send({ mock_device_no_data.id, DoorLock.attributes.LockState:read(mock_device_no_data) }) - test.socket.zigbee:__expect_send({ mock_device_no_data.id, Alarm.attributes.AlarmCount:read(mock_device_no_data) }) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device.id, "lockCodes", nil) - -- Validate state cache - mock_datastore.__assert_device_store_contains(mock_device.id, "__state_cache", nil) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", nil) - end, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Device init after added shouldn't change the datastores", - function() - test.mock_device.add_test_device(mock_device) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) - test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) - test.socket.zigbee:__expect_send({ mock_device.id, Alarm.attributes.AlarmCount:read(mock_device) }) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device.id, "lockCodes", { ["1"] = "Zach", ["2"] = "Steven" }) - -- Validate state cache - mock_datastore.__assert_device_store_contains(mock_device.id, "__state_cache", - { - main = { - lockCodes = { - lockCodes = {value = json.encode({ ["1"] = "Zach", ["2"] = "Steven" }) } - } - } - } - ) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device.id, "lockCodes", { ["1"] = "Zach", ["2"] = "Steven" }) - -- Validate state cache - mock_datastore.__assert_device_store_contains(mock_device.id, "__state_cache", - { - main = { - lockCodes = { - lockCodes = {value = json.encode({ ["1"] = "Zach", ["2"] = "Steven" }) } - } - } - } - ) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) - end, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Device init with new data should populate fields", - function() - test.mock_device.add_test_device(mock_device_no_data) - test.socket.device_lifecycle:__queue_receive({ mock_device_no_data.id, "added" }) - test.socket.zigbee:__expect_send({ mock_device_no_data.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device_no_data) }) - test.socket.zigbee:__expect_send({ mock_device_no_data.id, DoorLock.attributes.LockState:read(mock_device_no_data) }) - test.socket.zigbee:__expect_send({ mock_device_no_data.id, Alarm.attributes.AlarmCount:read(mock_device_no_data) }) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "lockCodes", nil) - -- Validate state cache - mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "__state_cache", {}) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "migrationComplete", nil) - test.socket.device_lifecycle():__queue_receive(mock_device_no_data:generate_info_changed( - { - data = { - lockCodes = json.encode({ ["1"] = "Zach", ["2"] = "Steven" }) - } - } - )) - test.wait_for_events() - test.socket.device_lifecycle:__queue_receive({ mock_device_no_data.id, "init" }) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "lockCodes", { ["1"] = "Zach", ["2"] = "Steven" }) - -- Validate state cache - mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "__state_cache", - { - main = { - lockCodes = { - lockCodes = {value = json.encode({ ["1"] = "Zach", ["2"] = "Steven" }) } - } - } - } - ) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device_no_data.id, "migrationComplete", true) - end, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "Device added data lock codes population, device response produces no events", - function() - test.mock_device.add_test_device(mock_device) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) - test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) - test.socket.zigbee:__expect_send({ mock_device.id, Alarm.attributes.AlarmCount:read(mock_device) }) - test.wait_for_events() - -- Validate lockCodes field - mock_datastore.__assert_device_store_contains(mock_device.id, "lockCodes", { ["1"] = "Zach", ["2"] = "Steven" }) - -- Validate state cache - mock_datastore.__assert_device_store_contains(mock_device.id, "__state_cache", - { - main = { - lockCodes = { - lockCodes = {value = json.encode({ ["1"] = "Zach", ["2"] = "Steven" }) } - } - } - } - ) - -- Validate migration complete flag - mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true) - test.wait_for_events() - - -- run do_configure step after added and verify no refresh all codes - test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") - test.wait_for_events() - - 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, - PowerConfiguration.ID) }) - test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:configure_reporting(mock_device, - 600, - 21600, - 1) }) - test.socket.zigbee:__expect_send({ mock_device.id, zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - DoorLock.ID) }) - test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:configure_reporting(mock_device, - 0, - 3600, - 0) }) - test.socket.zigbee:__expect_send({ mock_device.id, zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - Alarm.ID) }) - test.socket.zigbee:__expect_send({ mock_device.id, Alarm.attributes.AlarmCount:configure_reporting(mock_device, - 0, - 21600, - 0) }) - - mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - -- Validate migration reload skipped datastore - test.wait_for_events() - mock_datastore.__assert_device_store_contains(mock_device.id, "migrationReloadSkipped", true) - -- Verify the timer doesn't fire as it wasn't created - test.mock_time.advance_time(4) - test.wait_for_events() - end, - { - min_api_version = 17 - } -) - - -test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_legacy.lua similarity index 99% rename from drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua rename to drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_legacy.lua index 12f8331253..c3b316f486 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_legacy.lua @@ -18,6 +18,7 @@ local DoorLockUserStatus = DoorLock.types.DrlkUserStatus local DoorLockUserType = DoorLock.types.DrlkUserType local ProgrammingEventCode = DoorLock.types.ProgramEventCode +local consts = require "lock_utils.constants" local json = require "dkjson" local mock_device = test.mock_device.build_test_zigbee_device( @@ -25,7 +26,8 @@ local mock_device = 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)end + test.mock_device.add_test_device(mock_device) +end test.set_test_init_function(test_init) @@ -42,6 +44,8 @@ end test.register_coroutine_test( "Configure should configure all necessary attributes and begin reading codes", function() + mock_device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, false, { persist = true }) + test.wait_for_events() test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") test.wait_for_events() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_responses.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_responses.lua new file mode 100644 index 0000000000..e561a4867a --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_responses.lua @@ -0,0 +1,566 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +-- +-- Additional tests for lock_handlers/zigbee_responses.lua to improve coverage. +-- Covers: +-- • get_pin_code_response: sync codes from lock flow +-- • programming_event_notification: Yale/ASSA ABLOY user_id >= 256 shift +-- • operating_event_notification: keypad source with lockUsers capability +-- • operating_event_notification: schedule events with non-keypad source (auto method) +-- • alarm: alarm codes 0 and 1 +-- • lock_state: attribute handler with delay logic +-- • max_pin_code_length / min_pin_code_length: attribute handlers +-- • number_of_pin_users_supported: attribute handler with profile migration + +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local DoorLock = clusters.DoorLock +local Alarms = clusters.Alarms +local constants = require "lock_utils.constants" +local table_utils = require "lock_utils.tables" + +local DoorLockUserStatus = DoorLock.types.DrlkUserStatus +local DoorLockUserType = DoorLock.types.DrlkUserType +local OperationEventCode = DoorLock.types.OperationEventCode +local OperationEventSource = DoorLock.types.DrlkOperationEventSource +local ProgrammingEventCode = DoorLock.types.ProgramEventCode +local LockState = DoorLock.attributes.LockState + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), +}) + +local mock_device_yale = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Yale", + model = "YRD256", + server_clusters = { DoorLock.ID }, + }, + }, +}) + +local mock_device_no_lock_codes = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("lock-without-codes.yml"), +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_device) + test.mock_device.add_test_device(mock_device_yale) + test.mock_device.add_test_device(mock_device_no_lock_codes) +end + +test.set_test_init_function(test_init) + +-- Helper: build OperatingEventNotification +local function build_operating_event(device, event_code, event_source, user_id) + return { + device.id, + DoorLock.client.commands.OperatingEventNotification.build_test_rx( + device, + event_source, + event_code, + user_id or 0, + "1234", + 0x0000, + "data" + ), + } +end + +-- Helper: build ProgrammingEventNotification +local function build_programming_event(device, event_code, user_id) + return { + device.id, + DoorLock.client.commands.ProgrammingEventNotification.build_test_rx( + device, + 0x00, + event_code, + user_id, + "1234", + DoorLockUserType.UNRESTRICTED, + DoorLockUserStatus.OCCUPIED_ENABLED, + 0x0000, + "data" + ), + } +end + +-- ============================================================================ +-- get_pin_code_response: sync codes from lock +-- ============================================================================ + +test.register_coroutine_test( + "get_pin_code_response: syncs codes from lock and requests next code", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + -- Set up the sync state + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.SYNC.CODES_FROM_LOCK, {}) + mock_device:set_field(constants.SYNC.CODE_INDEX, 1, {}) + + -- Receive GetPINCodeResponse for user_id 1 + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 1, -- user_id + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" -- PIN code + ), + }) + + -- Should add user entry + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + -- Should add credential entry + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + )) + ) + -- Should request next code + test.socket.zigbee:__expect_send({ + mock_device.id, + DoorLock.server.commands.GetPINCode(mock_device, 2), + }) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "get_pin_code_response: completes sync when max entries reached", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + -- Set up the sync state at the last code index + -- get_max_entries defaults to 20 when attribute is missing, so use user_id 20 + mock_device:set_field(constants.DRIVER_STATE.BUSY, os.time(), {}) + mock_device:set_field(constants.DRIVER_STATE.COMMAND_IN_PROGRESS, constants.SYNC.CODES_FROM_LOCK, {}) + mock_device:set_field(constants.SYNC.CODE_INDEX, 20, {}) + + -- Receive GetPINCodeResponse for user_id 20 (at default max) + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 20, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ), + }) + + -- Should add entries + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 20, userName = "Guest 20", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 20, credentialIndex = 20, credentialType = "pin", credentialName = "Guest 20" } }, + { visibility = { displayed = false } } + )) + ) + -- Should NOT request next code (sync complete) + test.wait_for_events() + + -- Verify sync state is cleared (clear_busy_state sets BUSY to false, not nil) + assert(mock_device:get_field(constants.SYNC.CODE_INDEX) == nil) + assert(mock_device:get_field(constants.DRIVER_STATE.BUSY) == false) + end +) + +-- ============================================================================ +-- programming_event_notification: Yale user_id shift +-- ============================================================================ + +test.register_coroutine_test( + "programming_event_notification: Yale device shifts user_id >= 256", + function() + mock_device_yale:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + -- Send a PIN_CODE_ADDED with user_id 256 (0x100), which should shift to 1 + test.socket.zigbee:__queue_receive( + build_programming_event(mock_device_yale, ProgrammingEventCode.PIN_CODE_ADDED, 256) + ) + + -- After shifting 256 >> 8 = 1, should add entries for user_id 1 + test.socket.capability:__expect_send( + mock_device_yale:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "Guest 1", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device_yale:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "Guest 1" } }, + { visibility = { displayed = false } } + )) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- operating_event_notification: keypad with lockUsers (user info lookup) +-- ============================================================================ + +test.register_coroutine_test( + "operating_event_notification: keypad unlock includes user info when user and credential exist", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + -- First, add a user entry (using valid userType "guest") + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.users( + { { userIndex = 1, userName = "John Doe", userType = "guest" } }, + { visibility = { displayed = false } } + )) + ) + table_utils.add_entry(mock_device, "users", { + userIndex = 1, + userName = "John Doe", + userType = "guest", + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.credentials( + { { userIndex = 1, credentialIndex = 1, credentialType = "pin", credentialName = "John's PIN" } }, + { visibility = { displayed = false } } + )) + ) + table_utils.add_entry(mock_device, "credentials", { + userIndex = 1, + credentialIndex = 1, + credentialType = "pin", + credentialName = "John's PIN", + }) + test.wait_for_events() + + -- Send unlock event from keypad with user_id 1 + test.socket.zigbee:__queue_receive( + build_operating_event(mock_device, OperationEventCode.UNLOCK, OperationEventSource.KEYPAD, 1) + ) + + -- Should emit unlocked with user info + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.unlocked({ + data = { + method = "keypad", + userIndex = 1, + userName = "John Doe", + userType = "guest", + }, + }) + ) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "operating_event_notification: keypad unlock with unknown user uses default name", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + -- Send unlock event from keypad with user_id 99 (no matching entry) + test.socket.zigbee:__queue_receive( + build_operating_event(mock_device, OperationEventCode.UNLOCK, OperationEventSource.KEYPAD, 99) + ) + + -- Should emit unlocked with default user name + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.unlocked({ + data = { + method = "keypad", + userIndex = 99, + userName = "Guest 99", + }, + }) + ) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- operating_event_notification: schedule events with "auto" method +-- ============================================================================ + +test.register_coroutine_test( + "operating_event_notification: SCHEDULE_LOCK with RF source uses auto method", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + test.socket.zigbee:__queue_receive( + build_operating_event(mock_device, OperationEventCode.SCHEDULE_LOCK, OperationEventSource.RF, 0) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.locked({ data = { method = "auto" } }) + ) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "operating_event_notification: SCHEDULE_UNLOCK with MANUAL source uses auto method", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + test.socket.zigbee:__queue_receive( + build_operating_event(mock_device, OperationEventCode.SCHEDULE_UNLOCK, OperationEventSource.MANUAL, 0) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.unlocked({ data = { method = "auto" } }) + ) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "operating_event_notification: AUTO_LOCK with RF source uses auto method", + function() + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + test.socket.zigbee:__queue_receive( + build_operating_event(mock_device, OperationEventCode.AUTO_LOCK, OperationEventSource.RF, 0) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lock.lock.locked({ data = { method = "auto" } }) + ) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- alarm: alarm handler +-- ============================================================================ + +test.register_coroutine_test( + "alarm: alarm code 0 emits lock.unknown", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + Alarms.client.commands.Alarm.build_test_rx(mock_device, 0, DoorLock.ID), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.lock.unknown()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "alarm: alarm code 1 emits lock.unknown", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + Alarms.client.commands.Alarm.build_test_rx(mock_device, 1, DoorLock.ID), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.lock.unknown()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "alarm: unrecognized alarm code does not emit event", + function() + -- Alarm code 16 (low battery) is not in ALARM_REPORT map + test.socket.zigbee:__queue_receive({ + mock_device.id, + Alarms.client.commands.Alarm.build_test_rx(mock_device, 16, DoorLock.ID), + }) + + -- No event should be emitted + test.wait_for_events() + end +) + +-- ============================================================================ +-- lock_state: attribute handler +-- ============================================================================ + +test.register_coroutine_test( + "lock_state: LOCKED state emits locked event", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + LockState:build_test_attr_report(mock_device, LockState.LOCKED), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.lock.locked()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "lock_state: UNLOCKED state emits unlocked event", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + LockState:build_test_attr_report(mock_device, LockState.UNLOCKED), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.lock.unlocked()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "lock_state: NOT_FULLY_LOCKED state emits unknown event", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + LockState:build_test_attr_report(mock_device, LockState.NOT_FULLY_LOCKED), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.lock.unknown()) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- max_pin_code_length / min_pin_code_length: attribute handlers +-- ============================================================================ + +test.register_coroutine_test( + "max_pin_code_length: emits maxPinCodeLen event", + function() + -- Set SLGA_MIGRATED so we route to the new handlers + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.attributes.MaxPINCodeLength:build_test_attr_report(mock_device, 8), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.maxPinCodeLen(8, { visibility = { displayed = false } }) + ) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "min_pin_code_length: emits minPinCodeLen event", + function() + -- Set SLGA_MIGRATED so we route to the new handlers + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.attributes.MinPINCodeLength:build_test_attr_report(mock_device, 4), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.minPinCodeLen(4, { visibility = { displayed = false } }) + ) + ) + test.wait_for_events() + end +) + +-- ============================================================================ +-- number_of_pin_users_supported: attribute handler +-- ============================================================================ + +test.register_coroutine_test( + "number_of_pin_users_supported: emits pinUsersSupported and totalUsersSupported", + function() + -- Set SLGA_MIGRATED so we route to the new handlers + mock_device:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, {}) + + test.socket.zigbee:__queue_receive({ + mock_device.id, + DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device, 20), + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockCredentials.pinUsersSupported(20, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.lockUsers.totalUsersSupported(20, { visibility = { displayed = false } }) + ) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "number_of_pin_users_supported: triggers profile migration when device has no lockCodes", + function() + test.socket.zigbee:__queue_receive({ + mock_device_no_lock_codes.id, + DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device_no_lock_codes, 10), + }) + + -- Should trigger profile update to base-lock + mock_device_no_lock_codes:expect_metadata_update({ profile = "base-lock" }) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "number_of_pin_users_supported: zero value does not trigger profile migration", + function() + test.socket.zigbee:__queue_receive({ + mock_device_no_lock_codes.id, + DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device_no_lock_codes, 0), + }) + + -- No profile update should occur (value is 0) + test.wait_for_events() + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_samsungsds.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_samsungsds.lua index 4b5b18a70e..882d8f5efe 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_samsungsds.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_samsungsds.lua @@ -30,6 +30,7 @@ local SAMSUNG_SDS_MFR_CODE = 0x0003 local mock_device = test.mock_device.build_test_zigbee_device( { profile = t_utils.get_profile_definition("lock-without-codes.yml"), + provisioning_state = "TYPED", zigbee_endpoints = { [1] = { id = 1, @@ -49,6 +50,20 @@ end test.set_test_init_function(test_init) +local constants = require "lock_utils.constants" +test.register_coroutine_test( + "Device init function handler", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init"}) + test.socket.capability:__set_channel_ordering("relaxed") + test.wait_for_events() + assert(mock_device:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true, "Device init did not set migrated field to true") + end, + { + min_api_version = 17 + } +) + test.register_coroutine_test( "Configure should configure all necessary attributes", function() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua deleted file mode 100644 index 0b083f8050..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-bad-battery-reporter.lua +++ /dev/null @@ -1,41 +0,0 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - --- Mock out globals -local test = require "integration_test" -local t_utils = require "integration_test.utils" -local zigbee_test_utils = require "integration_test.zigbee_test_utils" - -local clusters = require "st.zigbee.zcl.clusters" -local capabilities = require "st.capabilities" -local PowerConfiguration = clusters.PowerConfiguration - -local mock_device = test.mock_device.build_test_zigbee_device({ profile = t_utils.get_profile_definition("base-lock.yml"), zigbee_endpoints ={ [1] = {id = 1, manufacturer ="Yale", model ="YRD220/240 TSDB", server_clusters = {}} } }) - -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( - "Battery percentage report should be handled", - { - { - channel = "zigbee", - direction = "receive", - message = { mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device, - 55) } - }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.battery.battery(55)) - } - }, - { - min_api_version = 17 - } -) - -test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua deleted file mode 100644 index 4e9c92bafe..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale-fingerprint-lock.lua +++ /dev/null @@ -1,41 +0,0 @@ --- Copyright 2022 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - --- Mock out globals -local test = require "integration_test" -local t_utils = require "integration_test.utils" -local zigbee_test_utils = require "integration_test.zigbee_test_utils" - -local clusters = require "st.zigbee.zcl.clusters" -local capabilities = require "st.capabilities" -local DoorLock = clusters.DoorLock - -local mock_device = test.mock_device.build_test_zigbee_device({ profile = t_utils.get_profile_definition("base-lock.yml"), zigbee_endpoints ={ [1] = {id = 1, manufacturer ="ASSA ABLOY iRevo", model ="iZBModule01", server_clusters = {}} } }) - -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( - "Max user code number report should be handled", - { - { - channel = "zigbee", - direction = "receive", - message = { mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device, - 16) } - }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.lockCodes.maxCodes(30)) - } - }, - { - min_api_version = 17 - } -) - -test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_fingerprint-lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_fingerprint-lock.lua new file mode 100644 index 0000000000..b0f4d1ffcf --- /dev/null +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_fingerprint-lock.lua @@ -0,0 +1,78 @@ +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Mock out globals +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local DoorLock = clusters.DoorLock +local Alarm = clusters.Alarms + +local mock_device = test.mock_device.build_test_zigbee_device({ profile = t_utils.get_profile_definition("base-lock.yml"), zigbee_endpoints ={ [1] = {id = 1, manufacturer ="ASSA ABLOY iRevo", model ="iZBModule01", server_clusters = {}} } }) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device)end + +local function test_init_new_capabilities() + test.mock_device.add_test_device(mock_device) + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "migrate", args = {} } }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.supportedCredentials({"pin"}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockUsers.users({}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.credentials({}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Alarm.attributes.AlarmCount:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.MaxPINCodeLength:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.MinPINCodeLength:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device) }) +end + +test.register_message_test( + "Max user code number report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device, + 16) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lockCodes.maxCodes(30)) + } + }, + { + test_init = test_init, + min_api_version = 17 + } +) + +test.register_message_test( + "Max user code number report should be handled for migrated locks", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device, + 16) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lockCredentials.pinUsersSupported(30)) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lockUsers.totalUsersSupported(30)) + } + }, + { test_init = test_init_new_capabilities } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_legacy.lua similarity index 100% rename from drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua rename to drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_legacy.lua diff --git a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua index a80632bf80..48a6521059 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/can_handle.lua @@ -2,6 +2,9 @@ -- Licensed under the Apache License, Version 2.0 local yale_fingerprint_lock_models = function(opts, driver, device) + local consts = require "lock_utils.constants" + local slga_migrated = device:get_field(consts.DRIVER_STATE.SLGA_MIGRATED) or false + if not slga_migrated then return false end local FINGERPRINTS = require("yale-fingerprint-lock.fingerprints") for _, fingerprint in ipairs(FINGERPRINTS) do if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then diff --git a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua index b78d043784..9965ef181e 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale-fingerprint-lock/init.lua @@ -5,14 +5,16 @@ local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" local LockCluster = clusters.DoorLock -local LockCodes = capabilities.lockCodes +local LockCredentials = capabilities.lockCredentials +local LockUsers = capabilities.lockUsers local YALE_FINGERPRINT_MAX_CODES = 0x1E local handle_max_codes = function(driver, device, value) - device:emit_event(LockCodes.maxCodes(YALE_FINGERPRINT_MAX_CODES), { visibility = { displayed = false } }) + device:emit_event(LockCredentials.pinUsersSupported(YALE_FINGERPRINT_MAX_CODES)) + device:emit_event(LockUsers.totalUsersSupported(YALE_FINGERPRINT_MAX_CODES)) end local yale_fingerprint_lock_driver = { @@ -24,7 +26,7 @@ local yale_fingerprint_lock_driver = { } } }, - can_handle = require("yale-fingerprint-lock.can_handle"), + can_handle = require("yale-fingerprint-lock.can_handle") } return yale_fingerprint_lock_driver diff --git a/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua b/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua deleted file mode 100644 index 4b546979d3..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale/sub_drivers.lua +++ /dev/null @@ -1,8 +0,0 @@ --- 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("yale.yale-bad-battery-reporter"), -} -return sub_drivers diff --git a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua b/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua deleted file mode 100644 index 67169e9268..0000000000 --- a/drivers/SmartThings/zigbee-lock/src/yale/yale-bad-battery-reporter/can_handle.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local is_bad_yale_lock_models = function(opts, driver, device) - local FINGERPRINTS = require("yale.yale-bad-battery-reporter.fingerprints") - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true, require("yale.yale-bad-battery-reporter") - end - end - return false -end - -return is_bad_yale_lock_models From 4dcfe01730600802f2de171304f7213529a484ad Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Fri, 10 Jul 2026 02:30:04 +0900 Subject: [PATCH 434/538] Matter Lock: Switch sub-driver and main-driver to support SLGA migration (#2842) --------- Signed-off-by: Hunsup Jung Co-authored-by: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Co-authored-by: Harrison Carter --- drivers/SmartThings/matter-lock/src/init.lua | 3234 ++++++++++++++--- .../matter-lock/src/lock_utils.lua | 34 + .../src/new-matter-lock/can_handle.lua | 19 - .../src/new-matter-lock/fingerprints.lua | 50 - .../matter-lock/src/new-matter-lock/init.lua | 3049 ---------------- .../matter-lock/src/sub_drivers.lua | 2 +- .../src/test/test_bridged_matter_lock.lua | 4 +- .../matter-lock/src/test/test_matter_lock.lua | 9 +- .../src/test/test_matter_lock_battery.lua | 4 - .../test/test_matter_lock_batteryLevel.lua | 1 + .../src/test/test_matter_lock_codes.lua | 68 +- .../src/test/test_matter_lock_cota.lua | 2 - .../src/test/test_matter_lock_migration.lua | 143 + .../src/test/test_matter_lock_modular.lua | 2 +- .../src/test/test_new_matter_lock.lua | 223 +- .../src/test/test_new_matter_lock_aliro.lua | 693 +++- .../src/test/test_new_matter_lock_battery.lua | 190 + .../src/unmigrated-matter-lock/can_handle.lua | 17 + .../src/unmigrated-matter-lock/init.lua | 758 ++++ 19 files changed, 4750 insertions(+), 3752 deletions(-) delete mode 100644 drivers/SmartThings/matter-lock/src/new-matter-lock/can_handle.lua delete mode 100644 drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua delete mode 100644 drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua create mode 100644 drivers/SmartThings/matter-lock/src/test/test_matter_lock_migration.lua create mode 100644 drivers/SmartThings/matter-lock/src/unmigrated-matter-lock/can_handle.lua create mode 100644 drivers/SmartThings/matter-lock/src/unmigrated-matter-lock/init.lua diff --git a/drivers/SmartThings/matter-lock/src/init.lua b/drivers/SmartThings/matter-lock/src/init.lua index 09f384ee4e..5d82801b67 100755 --- a/drivers/SmartThings/matter-lock/src/init.lua +++ b/drivers/SmartThings/matter-lock/src/init.lua @@ -1,33 +1,490 @@ -- Copyright 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 - 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 utils = require "st.utils" +local lock_utils = require "lock_utils" + +local version = require "version" +if version.api < 10 then + clusters.DoorLock = require "DoorLock" +end local DoorLock = clusters.DoorLock local PowerSource = clusters.PowerSource -local capabilities = require "st.capabilities" -local im = require "st.matter.interaction_model" -local lock_utils = require "lock_utils" +local INITIAL_CREDENTIAL_INDEX = 1 +local ALL_INDEX = 0xFFFE +-- maximum as defined by the Matter specification +local MAX_USER_NAME_LENGTH = 10 + +local RESPONSE_STATUS_MAP = { + [DoorLock.types.DlStatus.SUCCESS] = "success", + [DoorLock.types.DlStatus.FAILURE] = "failure", + [DoorLock.types.DlStatus.DUPLICATE] = "duplicate", + [DoorLock.types.DlStatus.OCCUPIED] = "occupied", + [DoorLock.types.DlStatus.INVALID_FIELD] = "invalidCommand", + [DoorLock.types.DlStatus.RESOURCE_EXHAUSTED] = "resourceExhausted", + [DoorLock.types.DlStatus.NOT_FOUND] = "failure" +} + +local WEEK_DAY_MAP = { + ["Sunday"] = 1, + ["Monday"] = 2, + ["Tuesday"] = 4, + ["Wednesday"] = 8, + ["Thursday"] = 16, + ["Friday"] = 32, + ["Saturday"] = 64, +} -local PROFILE_BASE_NAME = "__profile_base_name" +local ALIRO_KEY_TYPE_TO_CRED_ENUM_MAP = { + ["evictableEndpointKey"] = DoorLock.types.CredentialTypeEnum.ALIRO_EVICTABLE_ENDPOINT_KEY, + ["nonEvictableEndpointKey"] = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY +} -local INITIAL_COTA_INDEX = 1 +local battery_support = { + NO_BATTERY = "NO_BATTERY", + BATTERY_LEVEL = "BATTERY_LEVEL", + BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE" +} --- add this definition for locks to work on older lua libs -local UNLATCHED_STATE = 0x3 +local profiling_data = { + BATTERY_SUPPORT = "__BATTERY_SUPPORT", +} +local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local subscribed_attributes = { - [capabilities.lock.ID] = {DoorLock.attributes.LockState}, - [capabilities.battery.ID] = {PowerSource.attributes.BatPercentRemaining}, - [capabilities.batteryLevel.ID] = {PowerSource.attributes.BatChargeLevel}, + [capabilities.lock.ID] = { + DoorLock.attributes.LockState + }, + [capabilities.remoteControlStatus.ID] = { + DoorLock.attributes.OperatingMode + }, + [capabilities.lockUsers.ID] = { + DoorLock.attributes.NumberOfTotalUsersSupported + }, + [capabilities.lockCredentials.ID] = { + DoorLock.attributes.NumberOfPINUsersSupported, + DoorLock.attributes.MaxPINCodeLength, + DoorLock.attributes.MinPINCodeLength, + DoorLock.attributes.RequirePINforRemoteOperation + }, + [capabilities.lockSchedules.ID] = { + DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser, + DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser + }, + [capabilities.lockAliro.ID] = { + DoorLock.attributes.AliroReaderVerificationKey, + DoorLock.attributes.AliroReaderGroupIdentifier, + DoorLock.attributes.AliroReaderGroupSubIdentifier, + DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions, + DoorLock.attributes.AliroGroupResolvingKey, + DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions, + DoorLock.attributes.AliroBLEAdvertisingVersion, + DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported, + DoorLock.attributes.NumberOfAliroEndpointKeysSupported, + }, + [capabilities.battery.ID] = { + PowerSource.attributes.BatPercentRemaining + }, + [capabilities.batteryLevel.ID] = { + PowerSource.attributes.BatChargeLevel + } } ---- If a device needs a cota credential this function attempts to set the credential ---- at the index provided. The set_credential_response_handler handles all failures ---- and retries with the appropriate index when necessary. +local subscribed_events = { + [capabilities.lock.ID] = { + DoorLock.events.LockOperation + }, + [capabilities.lockAlarm.ID] = { + DoorLock.events.DoorLockAlarm + }, + [capabilities.lockUsers.ID] = { + DoorLock.events.LockUserChange + } +} + +local function find_default_endpoint(device, cluster) + local res = device.MATTER_DEFAULT_ENDPOINT + local eps = device:get_endpoints(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) + return find_default_endpoint(device, clusters.DoorLock.ID) +end + +local function device_init(driver, device) + device:set_component_to_endpoint_fn(component_to_endpoint) + if #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) == 0 then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist = true}) + elseif device:get_field(profiling_data.BATTERY_SUPPORT) == nil then + device:add_subscribed_attribute(clusters.PowerSource.attributes.AttributeList) + end + for cap_id, attributes in pairs(subscribed_attributes) do + if device:supports_capability_by_id(cap_id) then + for _, attr in ipairs(attributes) do + device:add_subscribed_attribute(attr) + end + end + end + device:add_subscribed_attribute(DoorLockFeatureMapAttr) + for cap_id, events in pairs(subscribed_events) do + if device:supports_capability_by_id(cap_id) then + for _, e in ipairs(events) do + device:add_subscribed_event(e) + end + end + end + device:subscribe() + end + +local function device_added(driver, device) + device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) +end + +local function set_reader_config(device) + local reader_config_updated = device:get_field(lock_utils.ALIRO_READER_CONFIG_UPDATED) or nil + if reader_config_updated == "TRUE" or reader_config_updated == "IN_PROGRESS" then return end + + local cmdName = "setReaderConfig" + local groupId = lock_utils.create_group_id_resolving_key() + local groupResolvingKey = nil + local aliro_ble_uwb_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.ALIROBLEUWB}) + if #aliro_ble_uwb_eps > 0 then + groupResolvingKey = lock_utils.create_group_id_resolving_key() + end + local privKey, pubKey = lock_utils.generate_keypair(device) + if not privKey or not pubKey then + local command_result_info = { + commandName = cmdName, + statusCode = "failure" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.VERIFICATION_KEY, pubKey, {persist = true}) + device:set_field(lock_utils.GROUP_ID, groupId, {persist = true}) + device:set_field(lock_utils.GROUP_RESOLVING_KEY, groupResolvingKey, {persist = true}) + + -- Send command + local ep = find_default_endpoint(device, clusters.DoorLock.ID) + device:send( + DoorLock.server.commands.SetAliroReaderConfig( + device, ep, + lock_utils.hex_string_to_octet_string(privKey), + lock_utils.hex_string_to_octet_string(pubKey), + lock_utils.hex_string_to_octet_string(groupId), + lock_utils.hex_string_to_octet_string(groupResolvingKey) + ) + ) + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "IN_PROGRESS", {persist = true}) +end + +local function match_profile_modular(driver, device) + local enabled_optional_component_capability_pairs = {} + local main_component_capabilities = {} + local modular_profile_name = "lock-modular" + for _, device_ep in pairs(device.endpoints) do + for _, ep_cluster in pairs(device_ep.clusters) do + if ep_cluster.cluster_id == DoorLock.ID then + local clus_has_feature = function(feature_bitmap) + return DoorLock.are_features_supported( + feature_bitmap, + lock_utils.get_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, device_ep.endpoint_id) or + ep_cluster.feature_map + ) + end + if clus_has_feature(DoorLock.types.Feature.USER) then + table.insert(main_component_capabilities, capabilities.lockUsers.ID) + end + if clus_has_feature(DoorLock.types.Feature.PIN_CREDENTIAL) then + table.insert(main_component_capabilities, capabilities.lockCredentials.ID) + end + if clus_has_feature(DoorLock.types.Feature.WEEK_DAY_ACCESS_SCHEDULES) or + clus_has_feature(DoorLock.types.Feature.YEAR_DAY_ACCESS_SCHEDULES) then + table.insert(main_component_capabilities, capabilities.lockSchedules.ID) + end + if clus_has_feature(DoorLock.types.Feature.UNBOLT) then + device:emit_event(capabilities.lock.supportedLockValues({"locked", "unlocked", "unlatched", "not fully locked"}, {visibility = {displayed = false}})) + device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) + modular_profile_name = "lock-modular-embedded-unlatch" -- use the embedded config specified in this profile for devices supporting "unlatch" + else + device:emit_event(capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) + device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) + end + if clus_has_feature(DoorLock.types.Feature.ALIRO_PROVISIONING) then + table.insert(main_component_capabilities, capabilities.lockAliro.ID) + end + break + end + end + end + + local supported_battery_type = device:get_field(profiling_data.BATTERY_SUPPORT) + if supported_battery_type == battery_support.BATTERY_LEVEL then + table.insert(main_component_capabilities, capabilities.batteryLevel.ID) + elseif supported_battery_type == battery_support.BATTERY_PERCENTAGE then + table.insert(main_component_capabilities, capabilities.battery.ID) + end + + table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) + if modular_profile_name == "lock-modular-embedded-unlatch" -- the embedded config that may be needed is not checked by an optional capability comparison + or device:supports_capability(capabilities.lockCodes) -- always re-profile a device using the legacy code storage methods + or lock_utils.optional_capabilities_list_changed(enabled_optional_component_capability_pairs, device.profile.components) then + device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) + end +end + +local function match_profile_switch(driver, device) + local user_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.USER}) + local pin_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.PIN_CREDENTIAL}) + local week_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.WEEK_DAY_ACCESS_SCHEDULES}) + local year_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.YEAR_DAY_ACCESS_SCHEDULES}) + local unbolt_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.UNBOLT}) + + local profile_name = "lock" + if #user_eps > 0 then + profile_name = profile_name .. "-user" + if #pin_eps > 0 then + profile_name = profile_name .. "-pin" + end + if #week_schedule_eps + #year_schedule_eps > 0 then + profile_name = profile_name .. "-schedule" + end + end + if #unbolt_eps > 0 then + profile_name = profile_name .. "-unlatch" + device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) + else + device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) + end + + local supported_battery_type = device:get_field(profiling_data.BATTERY_SUPPORT) + if supported_battery_type == battery_support.BATTERY_LEVEL then + profile_name = profile_name .. "-batteryLevel" + elseif supported_battery_type == battery_support.BATTERY_PERCENTAGE then + profile_name = profile_name .. "-battery" + 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 + +--- Deeply compare two values. +--- Handles metatables. Can optionally ignore cycle checking and/or function differences. +--- +--- @param a any +--- @param b any +--- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } +--- @param seen table|nil +--- @return boolean +local function deep_equals(a, b, opts, seen) + if a == b then return true end -- same object + if type(a) ~= type(b) then return false end -- different type + if type(a) == "function" and opts and opts.ignore_functions then return true end + if type(a) ~= "table" then return false end -- same type but not table, thus was already compared + + -- check for cycles in table references and preserve reference topology. + if not (opts and opts.ignore_cycles) then + seen = seen or {} + seen[a] = seen[a] or {} + if seen[a][b] then + return seen[a][b] + end + seen[a][b] = true + end + + -- Compare keys/values from a + for k, v in pairs(a) do + if not deep_equals(v, b[k], opts, seen) then + return false + end + end + + -- Ensure b doesn't have extra keys + for k in pairs(b) do + if a[k] == nil then + return false + end + end + + -- Compare metatables + local mt_a = getmetatable(a) + local mt_b = getmetatable(b) + return deep_equals(mt_a, mt_b, opts, seen) +end + +local function migrate_lock_codes(device) + local lockCodes = device:get_field(lock_utils.LOCK_CODES_FOR_MIGRATION) or {} + local user_table = {} + local credential_table = {} + for _, entry in ipairs(lockCodes) do + local index = tonumber(entry[1]) + table.insert(user_table, {userIndex = index, userType = "guest", userName = entry[2]}) + table.insert(credential_table, {userIndex = index, credentialIndex = index, credentialType = "pin"}) + end + if #user_table > 0 or #credential_table > 0 then + device:emit_event(capabilities.lockUsers.users(user_table, {visibility = {displayed = false}})) + device:emit_event(capabilities.lockCredentials.credentials(credential_table, {visibility = {displayed = false}})) + end +end + +local function info_changed(driver, device, event, args) + if deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then + return + end + for cap_id, attributes in pairs(subscribed_attributes) do + if device:supports_capability_by_id(cap_id) then + for _, attr in ipairs(attributes) do + device:add_subscribed_attribute(attr) + end + end + end + for cap_id, events in pairs(subscribed_events) do + if device:supports_capability_by_id(cap_id) then + for _, e in ipairs(events) do + device:add_subscribed_event(e) + end + end + end + device:subscribe() + if device:supports_capability_by_id(capabilities.lockAliro.ID) then + set_reader_config(device) + end + if device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then + device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) + device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory + end + if device:get_field(lock_utils.LOCK_CODES_COPY_REQUIRED) == true then + migrate_lock_codes(device) + device:set_field(lock_utils.LOCK_CODES_COPY_REQUIRED, false, {persist = true}) + end +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(driver, device, ignore_static_profiling) + if profiling_data_still_required(device) then return end + if version.api >= 15 and version.rpc >= 9 then + match_profile_modular(driver, device) + elseif ignore_static_profiling ~= true then + match_profile_switch(driver, device) + end +end + +local function do_configure(driver, device) + match_profile(driver, device, false) + device.thread:call_with_delay(5, function() + device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) + device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory + end) +end + +local function driver_switched(driver, device) + match_profile(driver, device, false) + device:try_update_metadata({provisioning_state = "PROVISIONED"}) +end + +-- Matter Handler +local function lock_state_handler(driver, device, ib, response) + local LockState = DoorLock.attributes.LockState + local attr = capabilities.lock.lock + local LOCK_STATE = { + [LockState.NOT_FULLY_LOCKED] = attr.not_fully_locked(), + [LockState.LOCKED] = attr.locked(), + [LockState.UNLOCKED] = attr.unlocked(), + [LockState.UNLATCHED] = attr.unlatched() + } + + -- The lock state is usually updated in lock_state_handler and lock_op_event_handler, respectively. + -- In this case, two events occur. To prevent this, when both functions are called, + -- it send the event after 1 second so that no event occurs in the lock_state_handler. + device.thread:call_with_delay(1, function () + if ib.data.value ~= nil then + device:emit_event(LOCK_STATE[ib.data.value]) + else + device.log.warn("Lock State is nil") + end + end) +end + +local function operating_modes_handler(driver, device, ib, response) + local status = capabilities.remoteControlStatus.remoteControlEnabled + local op_type = DoorLock.types.OperatingModeEnum + local opMode_map = { + [op_type.NORMAL] = true, + [op_type.VACATION] = true, + [op_type.PRIVACY] = false, + [op_type.NO_REMOTE_LOCK_UNLOCK] = false, + [op_type.PASSAGE] = false, + } + local result = opMode_map[ib.data.value] + local unbolt_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.UNBOLT}) + if result == true then + device:emit_event(status("true", {visibility = {displayed = true}})) + if #unbolt_eps > 0 then + device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) + else + device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) + end + elseif result == false then + device:emit_event(status("false", {visibility = {displayed = true}})) + device:emit_event(capabilities.lock.supportedLockCommands({}, {visibility = {displayed = false}})) + end +end + +local function total_users_supported_handler(driver, device, ib, response) + device:emit_event(capabilities.lockUsers.totalUsersSupported(ib.data.value, {visibility = {displayed = false}})) +end + +local function pin_users_supported_handler(driver, device, ib, response) + device:emit_event(capabilities.lockCredentials.pinUsersSupported(ib.data.value, {visibility = {displayed = false}})) +end + +local function min_pin_code_len_handler(driver, device, ib, response) + device:emit_event(capabilities.lockCredentials.minPinCodeLen(ib.data.value, {visibility = {displayed = false}})) +end + +local function max_pin_code_len_handler(driver, device, ib, response) + device:emit_event(capabilities.lockCredentials.maxPinCodeLen(ib.data.value, {visibility = {displayed = false}})) +end + local function set_cota_credential(device, credential_index) local eps = device:get_endpoints(DoorLock.ID) local cota_cred = device:get_field(lock_utils.COTA_CRED) @@ -40,7 +497,8 @@ local function set_cota_credential(device, credential_index) return end - if device:get_field(lock_utils.SET_CREDENTIAL) ~= nil then + -- Check Busy State + if lock_utils.is_busy_state_set(device) then device.log.debug("delaying setting COTA credential since a credential is currently being set") device.thread:call_with_delay(2, function(t) set_cota_credential(device, credential_index) @@ -48,50 +506,161 @@ local function set_cota_credential(device, credential_index) return end + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, "addCota") + device:set_field(lock_utils.CRED_INDEX, credential_index) device:set_field(lock_utils.COTA_CRED_INDEX, credential_index, {persist = true}) - local credential = {credential_type = DoorLock.types.DlCredentialType.PIN, credential_index = credential_index} - -- Set the credential to a code - device:set_field(lock_utils.SET_CREDENTIAL, credential_index) + device:set_field(lock_utils.USER_TYPE, "remote") + + -- Send command device.log.info(string.format("Attempting to set COTA credential at index %s", credential_index)) + local credential = { + credential_type = DoorLock.types.CredentialTypeEnum.PIN, + credential_index = credential_index + } device:send(DoorLock.server.commands.SetCredential( device, #eps > 0 and eps[1] or 1, - DoorLock.types.DlDataOperationType.ADD, + DoorLock.types.DataOperationTypeEnum.ADD, credential, device:get_field(lock_utils.COTA_CRED), nil, -- nil user_index creates a new user - DoorLock.types.DlUserStatus.OCCUPIED_ENABLED, - DoorLock.types.DlUserType.REMOTE_ONLY_USER + DoorLock.types.UserStatusEnum.OCCUPIED_ENABLED, + DoorLock.types.UserTypeEnum.REMOTE_ONLY_USER )) end local function generate_cota_cred_for_device(device) - local len = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodeLength.NAME) or 4 + local len = device:get_latest_state("main", capabilities.lockCredentials.ID, capabilities.lockCredentials.maxPinCodeLen.NAME) or 6 local cred_data = math.floor(math.random() * (10 ^ len)) cred_data = string.format("%0" .. tostring(len) .. "d", cred_data) device:set_field(lock_utils.COTA_CRED, cred_data, {persist = true}) end -local function lock_state_handler(driver, device, ib, response) - local LockState = DoorLock.attributes.LockState - local attr = capabilities.lock.lock - local LOCK_STATE = { - [LockState.NOT_FULLY_LOCKED] = attr.not_fully_locked(), - [LockState.LOCKED] = attr.locked(), - [LockState.UNLOCKED] = attr.unlocked(), - [UNLATCHED_STATE] = attr.unlocked(), -- Fully unlocked with latch pulled - } +local function apply_cota_credentials_if_absent(device) + if not device:get_field(lock_utils.COTA_CRED) then + -- Process after all other info blocks have been dispatched to ensure MaxPINCodeLength has been processed + device.thread:call_with_delay(0, function(t) + generate_cota_cred_for_device(device) + -- delay needed to allow test to override the random credential data + device.thread:call_with_delay(0, function(t) + -- Attempt to set cota credential at the lowest index + set_cota_credential(device, INITIAL_CREDENTIAL_INDEX) + end) + end) + end +end + +local function require_remote_pin_handler(driver, device, ib, response) + if ib.data.value then + apply_cota_credentials_if_absent(device) + else + device:set_field(lock_utils.COTA_CRED, false, {persist = true}) + end +end + +local function max_week_schedule_of_user_handler(driver, device, ib, response) + device:emit_event(capabilities.lockSchedules.weekDaySchedulesPerUser(ib.data.value, {visibility = {displayed = false}})) +end +local function max_year_schedule_of_user_handler(driver, device, ib, response) + device:emit_event(capabilities.lockSchedules.yearDaySchedulesPerUser(ib.data.value, {visibility = {displayed = false}})) +end + +local function aliro_reader_verification_key_handler(driver, device, ib, response) if ib.data.value ~= nil then - local event = LOCK_STATE[ib.data.value] - if event ~= nil then - device:emit_event(event) - else - device.log.warn(string.format("Received unknown lock state value (%s), emitting unknown", ib.data.value)) - device:emit_event(attr.unknown()) + device:emit_event(capabilities.lockAliro.readerVerificationKey( + utils.bytes_to_hex_string(ib.data.value), {visibility = {displayed = false}} + )) + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "TRUE", {persist = true}) + end +end + +local function aliro_reader_group_id_handler(driver, device, ib, response) + if ib.data.value ~= nil then + device:emit_event(capabilities.lockAliro.readerGroupIdentifier( + utils.bytes_to_hex_string(ib.data.value), + {visibility = {displayed = false}} + )) + end +end + +local function aliro_group_resolving_key_handler(driver, device, ib, response) + if ib.data.value ~= nil then + device:emit_event(capabilities.lockAliro.groupResolvingKey( + utils.bytes_to_hex_string(ib.data.value), + {visibility = {displayed = false}} + )) + end +end + +local function aliro_protocol_versions_handler(driver, device, ib, response) + if ib.data.elements == nil then + return + end + local protocol_versions = {} + for i, element in ipairs(ib.data.elements) do + local ver = string.format("%s.%s", element.value:byte(1), element.value:byte(2)) + table.insert(protocol_versions, ver); + end + device:emit_event(capabilities.lockAliro.expeditedTransactionProtocolVersions(protocol_versions, {visibility = {displayed = false}})) +end + +local function aliro_supported_ble_uwb_protocol_versions_handler(driver, device, ib, response) + if ib.data.elements == nil then + return + end + local protocol_versions = {} + for i, element in ipairs(ib.data.elements) do + local ver = string.format("%s.%s", element.value:byte(1), element.value:byte(2)) + table.insert(protocol_versions, ver); + end + device:emit_event(capabilities.lockAliro.bleUWBProtocolVersions(protocol_versions, {visibility = {displayed = false}})) +end + +local function aliro_ble_advertising_version_handler(driver, device, ib, response) + if ib.data.value ~= nil then + device:emit_event(capabilities.lockAliro.bleAdvertisingVersion(string.format("%s", ib.data.value), {visibility = {displayed = false}})) + end +end + +local function max_aliro_credential_issuer_key_handler(driver, device, ib, response) + if ib.data.value ~= nil then + device:emit_event(capabilities.lockAliro.maxCredentialIssuerKeys(ib.data.value, {visibility = {displayed = false}})) + end +end + +local function max_aliro_endpoint_key_handler(driver, device, ib, response) + if ib.data.value ~= nil then + device:emit_event(capabilities.lockAliro.maxEndpointKeys(ib.data.value, {visibility = {displayed = false}})) + end +end + +local function door_lock_feature_map_handler(driver, device, ib, response) + if ib.data.value == nil then return end + local feature_map = lock_utils.get_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id) or nil + if feature_map ~= ib.data.value then + lock_utils.set_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id, ib.data.value, { persist = true }) + match_profile(driver, device, true) + end +end + +local function handle_power_source_attribute_list(driver, device, ib, response) + local latest_battery_support = device:get_field(profiling_data.BATTERY_SUPPORT) + for _, attr in ipairs(ib.data.elements or {}) do + if attr.value == clusters.PowerSource.attributes.BatPercentRemaining.ID then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_PERCENTAGE, {persist=true}) + break -- BATTERY_PERCENTAGE is highest priority. break early if found + elseif attr.value == clusters.PowerSource.attributes.BatChargeLevel.ID then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_LEVEL, {persist=true}) end - else - device:emit_event(LOCK_STATE[LockState.NOT_FULLY_LOCKED]) + end + -- in the case that 1) no battery has been set, and 2) the returned ib does not include battery attributes, ignore battery + if latest_battery_support == nil and not device:get_field(profiling_data.BATTERY_SUPPORT) then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist=true}) + end + if latest_battery_support == nil or latest_battery_support ~= device:get_field(profiling_data.BATTERY_SUPPORT) then + match_profile(driver, device, false) end end @@ -99,632 +668,2235 @@ 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 +end + +local function handle_battery_charge_level(driver, device, ib, response) + if ib.data.value == PowerSource.types.BatChargeLevelEnum.OK then + device:emit_event(capabilities.batteryLevel.battery.normal()) + elseif ib.data.value == PowerSource.types.BatChargeLevelEnum.WARNING then + device:emit_event(capabilities.batteryLevel.battery.warning()) + elseif ib.data.value == PowerSource.types.BatChargeLevelEnum.CRITICAL then + device:emit_event(capabilities.batteryLevel.battery.critical()) + end +end + +-- Capability Handler +local function handle_lock(driver, device, command) + local ep = device:component_to_endpoint(command.component) + local cota_cred = device:get_field(lock_utils.COTA_CRED) + if cota_cred then + device:send( + DoorLock.server.commands.LockDoor(device, ep, cota_cred) + ) + else + device:send(DoorLock.server.commands.LockDoor(device, ep)) + end +end + +local function handle_unlock(driver, device, command) + local unbolt_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.UNBOLT}) + local cota_cred = device:get_field(lock_utils.COTA_CRED) + local ep = device:component_to_endpoint(command.component) + + if #unbolt_eps > 0 then + if cota_cred then + device:send( + DoorLock.server.commands.UnboltDoor(device, ep, cota_cred) + ) + else + device:send(DoorLock.server.commands.UnboltDoor(device, ep)) + end + else + if cota_cred then + device:send( + DoorLock.server.commands.UnlockDoor(device, ep, cota_cred) + ) + else + device:send(DoorLock.server.commands.UnlockDoor(device, ep)) + end + end +end + +local function handle_unlatch(driver, device, command) + local ep = device:component_to_endpoint(command.component) + local cota_cred = device:get_field(lock_utils.COTA_CRED) + if cota_cred then + device:send( + DoorLock.server.commands.UnlockDoor(device, ep, cota_cred) + ) + else + device:send(DoorLock.server.commands.UnlockDoor(device, ep)) + end +end + +local function add_user_to_table(device, userIdx, userName, userType) + -- Get latest user table + local user_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockUsers.ID, + capabilities.lockUsers.users.NAME, + {} + )) + + -- Add new entry to table + table.insert(user_table, {userIndex = userIdx, userName = userName, userType = userType}) + device:emit_event(capabilities.lockUsers.users(user_table, {visibility = {displayed = false}})) +end + +local function update_user_in_table(device, userIdx, userName, userType) + -- Get latest user table + local user_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockUsers.ID, + capabilities.lockUsers.users.NAME, + {} + )) + + -- Find user entry + local i = 0 + for index, entry in pairs(user_table) do + if entry.userIndex == userIdx then + i = index + break + end + end + + -- Update user entry + if i ~= 0 then + user_table[i].userType = userType + user_table[i].userName = userName + device:emit_event(capabilities.lockUsers.users(user_table, {visibility = {displayed = false}})) + end +end + +local function delete_user_from_table(device, userIdx) + -- If User Index is ALL_INDEX, remove all entry from the table + if userIdx == ALL_INDEX then + device:emit_event(capabilities.lockUsers.users({}, {visibility = {displayed = false}})) + return + end + + -- Get latest user table + local user_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockUsers.ID, + capabilities.lockUsers.users.NAME, + {} + )) + + -- Remove element from user table + for index, entry in pairs(user_table) do + if entry.userIndex == userIdx then + table.remove(user_table, index) + break + end + end + device:emit_event(capabilities.lockUsers.users(user_table, {visibility = {displayed = false}})) +end + +local function has_credentials(device, userIdx) + -- Get latest credential table + local cred_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockCredentials.ID, + capabilities.lockCredentials.credentials.NAME, + {} + )) + + -- Find credential + for index, entry in pairs(cred_table) do + if entry.userIndex == userIdx then + return true + end + end + + -- Get latest Aliro credential table + local aliro_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockAliro.ID, + capabilities.lockAliro.credentials.NAME, + {} + )) + + -- Find Aliro credential + for index, entry in pairs(aliro_table) do + if entry.userIndex == userIdx then + return true + end + end + + return false +end + +local function add_credential_to_table(device, userIdx, credIdx, credType) + -- Get latest credential table + local cred_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockCredentials.ID, + capabilities.lockCredentials.credentials.NAME, + {} + )) + + -- Add new entry to table + table.insert(cred_table, {userIndex = userIdx, credentialIndex = credIdx, credentialType = credType}) + device:emit_event(capabilities.lockCredentials.credentials(cred_table, {visibility = {displayed = false}})) +end + +local function delete_credential_from_table(device, credIdx) + -- If Credential Index is ALL_INDEX, remove all entries from the table + if credIdx == ALL_INDEX then + device:emit_event(capabilities.lockCredentials.credentials({}, {visibility = {displayed = false}})) + return ALL_INDEX + end + + -- Get latest credential table + local cred_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockCredentials.ID, + capabilities.lockCredentials.credentials.NAME, + {} + )) + + -- Delete an entry from credential table + local userIdx = nil + for index, entry in pairs(cred_table) do + if entry.credentialIndex == credIdx then + table.remove(cred_table, index) + userIdx = entry.userIndex + break + end + end + + device:emit_event(capabilities.lockCredentials.credentials(cred_table, {visibility = {displayed = false}})) + return userIdx +end + +local function delete_credential_from_table_as_user(device, userIdx) + -- If User Index is ALL_INDEX, remove all entry from the table + if userIdx == ALL_INDEX then + device:emit_event(capabilities.lockCredentials.credentials({}, {visibility = {displayed = false}})) + return + end + + -- Get latest credential table + local cred_table = device:get_latest_state( + "main", + capabilities.lockCredentials.ID, + capabilities.lockCredentials.credentials.NAME + ) or {} + local new_cred_table = {} + + -- Re-create credential table + for index, entry in pairs(cred_table) do + if entry.userIndex ~= userIdx then + table.insert(new_cred_table, entry) + end + end + + device:emit_event(capabilities.lockCredentials.credentials(new_cred_table, {visibility = {displayed = false}})) +end + +local function add_week_schedule_to_table(device, userIdx, scheduleIdx, schedule) + -- Get latest week day schedule table + local week_schedule_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockSchedules.ID, + capabilities.lockSchedules.weekDaySchedules.NAME, + {} + )) + + -- Find schedule for specific user + local i = 0 + for index, entry in pairs(week_schedule_table) do + if entry.userIndex == userIdx then + i = index + end + end + + -- Re-create weekDays list + local weekDayList = {} + for _, weekday in ipairs(schedule.weekDays) do + table.insert(weekDayList, weekday) + end + + if i ~= 0 then -- Add schedule for existing user + -- Exclude same scheduleIdx + local new_schedule_table = {} + for index, entry in pairs(week_schedule_table[i].schedules) do + if entry.scheduleIndex ~= scheduleIdx then + table.insert(new_schedule_table, entry) + end + end + -- Add new entry to table + table.insert( + new_schedule_table, + { + scheduleIndex = scheduleIdx, + weekDays = weekDayList, + startHour = schedule.startHour, + startMinute = schedule.startMinute, + endHour = schedule.endHour, + endMinute = schedule.endMinute + } + ) + -- Update schedule for specific user + week_schedule_table[i].schedules = new_schedule_table + else -- Add schedule for new user + table.insert( + week_schedule_table, + { + userIndex = userIdx, + schedules = {{ + scheduleIndex = scheduleIdx, + weekDays = weekDayList, + startHour = schedule.startHour, + startMinute = schedule.startMinute, + endHour = schedule.endHour, + endMinute = schedule.endMinute + }} + } + ) + end + + device:emit_event(capabilities.lockSchedules.weekDaySchedules(week_schedule_table, {visibility = {displayed = false}})) +end + +local function delete_week_schedule_from_table(device, userIdx, scheduleIdx) + -- Get latest week day schedule table + local week_schedule_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockSchedules.ID, + capabilities.lockSchedules.weekDaySchedules.NAME, + {} + )) + + -- Find schedule for specific user + local i = 0 + for index, entry in pairs(week_schedule_table) do + if entry.userIndex == userIdx then + i = index + end + end + + -- When there is no userIndex in the table + if i == 0 then + return + end + + -- Re-create schedule table for the user + local new_schedule_table = {} + for index, entry in pairs(week_schedule_table[i].schedules) do + if entry.scheduleIndex ~= scheduleIdx then + table.insert(new_schedule_table, entry) + end + end + + -- If user has no schedule, remove user from the table + if #new_schedule_table == 0 then + table.remove(week_schedule_table, i) + else + week_schedule_table[i].schedules = new_schedule_table + end + + device:emit_event(capabilities.lockSchedules.weekDaySchedules(week_schedule_table, {visibility = {displayed = false}})) +end + +local function delete_week_schedule_from_table_as_user(device, userIdx) + -- If User Index is ALL_INDEX, remove all entry from the table + if userIdx == ALL_INDEX then + device:emit_event(capabilities.lockSchedules.weekDaySchedules({}, {visibility = {displayed = false}})) + return + end + + -- Get latest week day schedule table + local week_schedule_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockSchedules.ID, + capabilities.lockSchedules.weekDaySchedules.NAME, + {} + )) + + -- Re-create week day schedule table + local new_week_schedule_table = {} + for index, entry in pairs(week_schedule_table) do + if entry.userIndex ~= userIdx then + table.insert(new_week_schedule_table, entry) + end + end + + device:emit_event(capabilities.lockSchedules.weekDaySchedules(new_week_schedule_table, {visibility = {displayed = false}})) +end + +local function add_year_schedule_to_table(device, userIdx, scheduleIdx, sTime, eTime) + -- Get latest year day schedule table + local year_schedule_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockSchedules.ID, + capabilities.lockSchedules.yearDaySchedules.NAME, + {} + )) + + -- Find schedule for specific user + local i = 0 + for index, entry in pairs(year_schedule_table) do + if entry.userIndex == userIdx then + i = index + end + end + + if i ~= 0 then -- Add schedule for existing user + -- Exclude same scheduleIdx + local new_schedule_table = {} + for index, entry in pairs(year_schedule_table[i].schedules) do + if entry.scheduleIndex ~= scheduleIdx then + table.insert(new_schedule_table, entry) + end + end + -- Add new entry to table + table.insert( + new_schedule_table, + { + scheduleIndex = scheduleIdx, + localStartTime = sTime, + localEndTime = eTime + } + ) + -- Update schedule for specific user + year_schedule_table[i].schedules = new_schedule_table + else -- Add schedule for new user + table.insert( + year_schedule_table, + { + userIndex = userIdx, + schedules = {{ + scheduleIndex = scheduleIdx, + localStartTime = sTime, + localEndTime = eTime + }} + } + ) + end + + device:emit_event(capabilities.lockSchedules.yearDaySchedules(year_schedule_table, {visibility = {displayed = false}})) +end + +local function delete_year_schedule_from_table(device, userIdx, scheduleIdx) + -- Get latest year day schedule table + local year_schedule_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockSchedules.ID, + capabilities.lockSchedules.yearDaySchedules.NAME, + {} + )) + + -- Find schedule for specific user + local i = 0 + for index, entry in pairs(year_schedule_table) do + if entry.userIndex == userIdx then + i = index + end + end + + -- When there is no userIndex in the table + if i == 0 then + return + end + + -- Re-create year day schedule table for the user + local new_schedule_table = {} + for index, entry in pairs(year_schedule_table[i].schedules) do + if entry.scheduleIndex ~= scheduleIdx then + table.insert(new_schedule_table, entry) + end + end + + -- If user has no schedule, remove user from the table + if #new_schedule_table == 0 then + table.remove(year_schedule_table, i) + else + year_schedule_table[i].schedules = new_schedule_table + end + + device:emit_event(capabilities.lockSchedules.yearDaySchedules(year_schedule_table, {visibility = {displayed = false}})) +end + +local function delete_year_schedule_from_table_as_user(device, userIdx) + -- If User Index is ALL_INDEX, remove all entry from the table + if userIdx == ALL_INDEX then + device:emit_event(capabilities.lockSchedules.yearDaySchedules({}, {visibility = {displayed = false}})) + return + end + + -- Get latest year day schedule table + local year_schedule_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockSchedules.ID, + capabilities.lockSchedules.yearDaySchedules.NAME, + {} + )) + + -- Re-create year day schedule table + local new_year_schedule_table = {} + for index, entry in pairs(year_schedule_table) do + if entry.userIndex ~= userIdx then + table.insert(new_year_schedule_table, entry) + end + end + + device:emit_event(capabilities.lockSchedules.yearDaySchedules(new_year_schedule_table, {visibility = {displayed = false}})) +end + +local function add_aliro_to_table(device, userIdx, keyIdx, keyType, keyId) + -- Get latest aliro table + local aliro_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockAliro.ID, + capabilities.lockAliro.credentials.NAME, + {} + )) + + -- Add new entry to table + table.insert(aliro_table, {userIndex = userIdx, keyIndex = keyIdx, keyType = keyType, keyId = keyId}) + device:emit_event(capabilities.lockAliro.credentials(aliro_table, {visibility = {displayed = false}})) +end + +local function delete_aliro_from_table(device, userIdx, keyType, keyId) + -- Get latest aliro table + local aliro_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockAliro.ID, + capabilities.lockAliro.credentials.NAME, + {} + )) + + -- Delete an entry from aliro table + if keyType == "issuerKey" then + for i, entry in pairs(aliro_table) do + if entry.userIndex == userIdx then + table.remove(aliro_table, i) + break + end + end + else + for i, entry in pairs(aliro_table) do + if entry.userIndex == userIdx and entry.keyId == keyId then + table.remove(aliro_table, i) + break + end + end + end + device:emit_event(capabilities.lockAliro.credentials(aliro_table, {visibility = {displayed = false}})) +end + +local function delete_aliro_from_table_as_user(device, userIdx) + -- If User Index is ALL_INDEX, remove all entry from the table + if userIdx == ALL_INDEX then + device:emit_event(capabilities.lockAliro.credentials({}, {visibility = {displayed = false}})) + return + end + + -- Get latest credential table + local aliro_table = device:get_latest_state( + "main", + capabilities.lockAliro.ID, + capabilities.lockAliro.credentials.NAME + ) or {} + local new_aliro_table = {} + + -- Re-create credential table + for index, entry in pairs(aliro_table) do + if entry.userIndex ~= userIdx then + table.insert(new_aliro_table, entry) + end + end + + device:emit_event(capabilities.lockAliro.credentials(new_aliro_table, {visibility = {displayed = false}})) +end + +local function handle_add_user(driver, device, command) + -- Get parameters + local cmdName = "addUser" + local userName = command.args.userName + local userType = command.args.userType + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockUsers.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, INITIAL_CREDENTIAL_INDEX, {persist = true}) + device:set_field(lock_utils.USER_NAME, userName, {persist = true}) + device:set_field(lock_utils.USER_TYPE, userType, {persist = true}) + + -- Get available user index + local ep = device:component_to_endpoint(command.component) + device:send(DoorLock.server.commands.GetUser(device, ep, INITIAL_CREDENTIAL_INDEX)) +end + +local function handle_update_user(driver, device, command) + -- Get parameters + local cmdName = "updateUser" + local userIdx = command.args.userIndex + local userName = command.args.userName + local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) + local userType = command.args.userType + local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER + if userType == "guest" then + userTypeMatter = DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER + end + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockUsers.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + device:set_field(lock_utils.USER_NAME, userName, {persist = true}) + device:set_field(lock_utils.USER_TYPE, userType, {persist = true}) + + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send( + DoorLock.server.commands.SetUser( + device, ep, + DoorLock.types.DataOperationTypeEnum.MODIFY, + userIdx, + userNameMatter, + nil, -- Unique ID + nil, -- User Status + userTypeMatter, + nil -- Credential Rule + ) + ) +end + +local function get_user_response_handler(driver, device, ib, response) + local elements = ib.info_block.data.elements + local userIdx = elements.user_index.value + local cmdName = device:get_field(lock_utils.COMMAND_NAME) + local status = "success" + if ib.status == DoorLock.types.DlStatus.FAILURE then + status = "failure" + elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then + status = "invalidCommand" + end + if status ~= "success" then + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + statusCode = status + } + device:emit_event(capabilities.lockUsers.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + return + end + + local ep = find_default_endpoint(device, DoorLock.ID) + local status = elements.user_status.value + local maxUser = device:get_latest_state( + "main", + capabilities.lockUsers.ID, + capabilities.lockUsers.totalUsersSupported.NAME + ) or 10 + -- Found available user index + if status == nil or status == DoorLock.types.UserStatusEnum.AVAILABLE then + local userName = device:get_field(lock_utils.USER_NAME) + local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) + local userType = device:get_field(lock_utils.USER_TYPE) + local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER + if userType == "guest" then + userTypeMatter = DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER + end + + -- Save values to field + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + + -- Send command + device:send( + DoorLock.server.commands.SetUser( + device, ep, + DoorLock.types.DataOperationTypeEnum.ADD, + userIdx, + userNameMatter, + nil, -- Unique ID + nil, -- User Status + userTypeMatter, + nil -- Credential Rule + ) + ) + elseif userIdx >= maxUser then -- There's no available user index + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + statusCode = "resourceExhausted" + } + device:emit_event(capabilities.lockUsers.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + else -- Check next user index + device:send(DoorLock.server.commands.GetUser(device, ep, userIdx + 1)) + end +end + +local function set_user_response_handler(driver, device, ib, response) + -- Get result + local cmdName = device:get_field(lock_utils.COMMAND_NAME) + local userIdx = device:get_field(lock_utils.USER_INDEX) + local userName = device:get_field(lock_utils.USER_NAME) + local userType = device:get_field(lock_utils.USER_TYPE) + local status = "success" + if ib.status == DoorLock.types.DlStatus.FAILURE then + status = "failure" + elseif ib.status == DoorLock.types.DlStatus.OCCUPIED then + status = "occupied" + elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then + status = "invalidCommand" + end + + -- Update User in table + if status == "success" then + if cmdName == "addUser" then + add_user_to_table(device, userIdx, userName, userType) + elseif cmdName == "updateUser" then + update_user_in_table(device, userIdx, userName, userType) + end + else + device.log.warn(string.format("Failed to set user: %s", status)) + end + + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + statusCode = status + } + device:emit_event(capabilities.lockUsers.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) +end + +local function handle_delete_user(driver, device, command) + -- Get parameters + local cmdName = "deleteUser" + local userIdx = command.args.userIndex + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockUsers.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send(DoorLock.server.commands.ClearUser(device, ep, userIdx)) +end + +local function handle_delete_all_users(driver, device, command) + -- Get parameters + local cmdName = "deleteAllUsers" + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockUsers.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, ALL_INDEX, {persist = true}) + + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send(DoorLock.server.commands.ClearUser(device, ep, ALL_INDEX)) +end + +local function clear_user_response_handler(driver, device, ib, response) + -- Get result + local cmdName = device:get_field(lock_utils.COMMAND_NAME) + local userIdx = device:get_field(lock_utils.USER_INDEX) + local status = "success" + if ib.status == DoorLock.types.DlStatus.FAILURE then + status = "failure" + elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then + status = "invalidCommand" + end + + -- Delete User and Credential from table + if status == "success" then + delete_user_from_table(device, userIdx) + delete_credential_from_table_as_user(device, userIdx) + delete_aliro_from_table_as_user(device, userIdx) + delete_week_schedule_from_table_as_user(device, userIdx) + delete_year_schedule_from_table_as_user(device, userIdx) + else + device.log.warn(string.format("Failed to clear user: %s", status)) + end + + -- In the "defaultSchedule" cmd failure path, when a guest user's credentials are set but the scheduling + -- fails during default setup, those credentials should be removed, so we wait to log lock credentials until here. + if cmdName == "defaultSchedule" then + -- note: if clear user succeeds, we'd log credential settings as a "failure" since it's effectively a no-op. + -- If clear user fails, log "success" since the credentials would still be present. + local lock_credential_status = status == "success" and "failure" or "success" + local command_result_info = { + commandName = "addCredential", + userIndex = userIdx, + statusCode = lock_credential_status + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + else + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + statusCode = status + } + device:emit_event(capabilities.lockUsers.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + end + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) +end + +local function handle_add_credential(driver, device, command) + -- Get parameters + local cmdName = "addCredential" + local userIdx = command.args.userIndex + local userType = command.args.userType + local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER + if userType == "guest" then + userTypeMatter = DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER + end + if userIdx == 0 then + userIdx = nil + else + userTypeMatter = nil + end + local credential = { + credential_type = DoorLock.types.CredentialTypeEnum.PIN, + credential_index = INITIAL_CREDENTIAL_INDEX + } + local credData = command.args.credentialData + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + device:set_field(lock_utils.USER_TYPE, userType, {persist = true}) + device:set_field(lock_utils.CRED_INDEX, INITIAL_CREDENTIAL_INDEX, {persist = true}) + device:set_field(lock_utils.CRED_DATA, credData, {persist = true}) + + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send( + DoorLock.server.commands.SetCredential( + device, ep, + DoorLock.types.DataOperationTypeEnum.ADD, -- Data Operation Type: Add(0), Modify(2) + credential, -- Credential + credData, -- Credential Data + userIdx, -- User Index + nil, -- User Status + userTypeMatter -- User Type + ) + ) +end + +local function handle_update_credential(driver, device, command) + -- Get parameters + local cmdName = "updateCredential" + local userIdx = command.args.userIndex + local credIdx = command.args.credentialIndex + local credential = { + credential_type = DoorLock.types.CredentialTypeEnum.PIN, + credential_index = credIdx + } + local credData = command.args.credentialData + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + device:set_field(lock_utils.CRED_INDEX, credIdx, {persist = true}) + device:set_field(lock_utils.USER_TYPE, nil, {persist = true}) + + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send( + DoorLock.server.commands.SetCredential( + device, ep, + DoorLock.types.DataOperationTypeEnum.MODIFY, -- Data Operation Type: Add(0), Modify(2) + credential, -- Credential + credData, -- Credential Data + userIdx, -- User Index + nil, -- User Status + nil -- User Type + ) + ) +end + +local function set_pin_response_handler(driver, device, ib, response) + if ib.status ~= im.InteractionResponse.Status.SUCCESS then + device.log.error("Failed to set credential for device") + return + end + + local cmdName = device:get_field(lock_utils.COMMAND_NAME) + local credData = device:get_field(lock_utils.CRED_DATA) + if cmdName == "addCota" then + credData = device:get_field(lock_utils.COTA_CRED) + end + local userIdx = device:get_field(lock_utils.USER_INDEX) + local userType = device:get_field(lock_utils.USER_TYPE) + local credIdx = device:get_field(lock_utils.CRED_INDEX) + local elements = ib.info_block.data.elements + local status = RESPONSE_STATUS_MAP[elements.status.value] + + if status == "success" then + -- Don't save user and credential for COTA + if cmdName == "addCota" then + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + return + end + + -- If user is added also, update User table + if userIdx == nil then + add_user_to_table(device, elements.user_index.value, nil, userType) + end + + -- Update Credential table + userIdx = elements.user_index.value + if cmdName == "addCredential" then + add_credential_to_table(device, userIdx, credIdx, "pin") + end + + -- If User Type is Guest and device support schedule, add default schedule + local week_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.WEEK_DAY_ACCESS_SCHEDULES}) + local year_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.YEAR_DAY_ACCESS_SCHEDULES}) + if userType == "guest" and (#week_schedule_eps > 0 or #year_schedule_eps > 0) and cmdName ~= "updateCredential" then + local cmdName = "defaultSchedule" + local scheduleIdx = 1 + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + device:set_field(lock_utils.SCHEDULE_INDEX, scheduleIdx, {persist = true}) + + local ep = device:component_to_endpoint("main") + device:send( + DoorLock.server.commands.SetYearDaySchedule( + device, ep, + scheduleIdx, + userIdx, + lock_utils.MIN_EPOCH_S, + lock_utils.MAX_EPOCH_S + ) + ) + else + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + credentialIndex = credIdx, + statusCode = status + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + end + return + end + + -- In the case DlStatus returns Occupied, this means the current credential index is in use, + -- so we must try the next one. If there is not a next index (i.e. it is nil), + -- we should mark this as "resourceExhausted" and stop attempting to set the credentials. + device.log.warn(string.format("Failed to set credential: %s", status)) + if status == "occupied" and elements.next_credential_index.value == nil then + local command_result_info = { + commandName = cmdName, + statusCode = "resourceExhausted" -- No more available credential index + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + elseif status == "occupied" then + -- Get parameters + local credIdx = elements.next_credential_index.value + local credential = { + credential_type = DoorLock.types.CredentialTypeEnum.PIN, + credential_index = credIdx, + } + local userIdx = device:get_field(lock_utils.USER_INDEX) + local userType = device:get_field(lock_utils.USER_TYPE) + local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER + if userIdx ~= nil then + userTypeMatter = nil + elseif userType == "guest" then + userTypeMatter = DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER + elseif userType == "remote" then + userTypeMatter = DoorLock.types.UserTypeEnum.REMOTE_ONLY_USER + end + + device:set_field(lock_utils.CRED_INDEX, credIdx, {persist = true}) + + -- Send command + local ep = find_default_endpoint(device, DoorLock.ID) + device:send( + DoorLock.server.commands.SetCredential( + device, ep, + DoorLock.types.DataOperationTypeEnum.ADD, -- Data Operation Type: Add(0), Modify(2) + credential, -- Credential + credData, -- Credential Data + userIdx, -- User Index + nil, -- User Status + userTypeMatter -- User Type + ) + ) + elseif status == "duplicate" and cmdName == "addCota" then + generate_cota_cred_for_device(device) + device.thread:call_with_delay(0, function(t) set_cota_credential(device, credIdx) end) + else + local command_result_info = { + commandName = cmdName, + statusCode = status + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + end +end + +local function set_issuer_key_response_handler(driver, device, ib, response) + local cmdName = "setIssuerKey" + local userIdx = device:get_field(lock_utils.USER_INDEX) + local userType = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER + local issuerKeyIndex = device:get_field(lock_utils.ISSUER_KEY_INDEX) + local reqId = device:get_field(lock_utils.COMMAND_REQUEST_ID) + local elements = ib.info_block.data.elements + local status = RESPONSE_STATUS_MAP[elements.status.value] + + if status == "success" then + -- Delete field data + device:set_field(lock_utils.ISSUER_KEY, nil, {persist = true}) + + -- If user is added also, update User table + if userIdx == nil then + userIdx = elements.user_index.value + add_user_to_table(device, userIdx, nil, "adminMember") + end + + -- Update Aliro table + add_aliro_to_table(device, userIdx, issuerKeyIndex, "issuerKey", nil) + + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + requestId = reqId, + statusCode = status + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + return + end + + -- In the case DlStatus returns Occupied, this means the current credential index is in use, + -- so we must try the next one. If there is not a next index (i.e. it is nil), + -- we should mark this as "resourceExhausted" and stop attempting to set the credentials. + device.log.warn(string.format("Failed to set credential: %s", status)) + if status == "occupied" and elements.next_credential_index.value == nil then + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + requestId = reqId, + statusCode = "resourceExhausted" -- No more available credential index + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + elseif status == "occupied" then + -- Get parameters + if userIdx ~= nil then + userType = nil + end + local credIdx = elements.next_credential_index.value + local credType = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY + local credData = device:get_field(lock_utils.ISSUER_KEY) + local credential = { + credential_type = credType, + credential_index = credIdx + } + + -- Save values to field + device:set_field(lock_utils.ISSUER_KEY_INDEX, credIdx, {persist = true}) + + -- Send command + local ep = find_default_endpoint(device, DoorLock.ID) + device:send( + DoorLock.server.commands.SetCredential( + device, ep, + DoorLock.types.DataOperationTypeEnum.ADD, + credential, -- Credential + lock_utils.hex_string_to_octet_string(credData), -- Credential Data + userIdx, -- User Index + nil, -- User Status + userType -- User Type + ) + ) + else + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + requestId = reqId, + statusCode = status + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + end +end + +local function set_endpoint_key_response_handler(driver, device, ib, response) + local cmdName = "setEndpointKey" + local userIdx = device:get_field(lock_utils.USER_INDEX) + local userType = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER + local keyId = device:get_field(lock_utils.DEVICE_KEY_ID) + local keyType = device:get_field(lock_utils.ENDPOINT_KEY_TYPE) + local endpointKeyIndex = device:get_field(lock_utils.ENDPOINT_KEY_INDEX) + local reqId = device:get_field(lock_utils.COMMAND_REQUEST_ID) + local elements = ib.info_block.data.elements + local status = RESPONSE_STATUS_MAP[elements.status.value] + + if status == "success" then + -- Delete field data + device:set_field(lock_utils.ENDPOINT_KEY, nil, {persist = true}) + + -- If user is added also, update User table + if userIdx == nil then + userIdx = elements.user_index.value + add_user_to_table(device, userIdx, nil, "adminMember") + end + + -- Update Aliro table + add_aliro_to_table(device, userIdx, endpointKeyIndex, keyType, keyId) + + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + keyId = keyId, + requestId = reqId, + statusCode = status + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + return + end + + -- In the case DlStatus returns Occupied, this means the current credential index is in use, + -- so we must try the next one. If there is not a next index (i.e. it is nil), + -- we should mark this as "resourceExhausted" and stop attempting to set the credentials. + device.log.warn(string.format("Failed to set credential: %s", status)) + + if status == "occupied" and elements.next_credential_index.value == nil then + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + keyId = keyId, + requestId = reqId, + statusCode = "resourceExhausted" -- No more available credential index + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + elseif status == "occupied" then + -- Get parameters + if userIdx ~= nil then + userType = nil + end + local credIdx = elements.next_credential_index.value + local credType = ALIRO_KEY_TYPE_TO_CRED_ENUM_MAP[keyType] + local credData = device:get_field(lock_utils.ENDPOINT_KEY) + local credential = { + credential_type = credType, + credential_index = credIdx + } + + -- Save values to field + device:set_field(lock_utils.ENDPOINT_KEY_INDEX, credIdx, {persist = true}) + + -- Send command + local ep = find_default_endpoint(device, DoorLock.ID) + device:send( + DoorLock.server.commands.SetCredential( + device, ep, + DoorLock.types.DataOperationTypeEnum.ADD, + credential, -- Credential + lock_utils.hex_string_to_octet_string(credData), -- Credential Data + userIdx, -- User Index + nil, -- User Status + userType -- User Type + ) + ) + else + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + keyId = keyId, + requestId = reqId, + statusCode = status + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + end +end + +local function set_credential_response_handler(driver, device, ib, response) + if ib.status ~= im.InteractionResponse.Status.SUCCESS then + device.log.error("Failed to set credential for device") + return + end + local cmdName = device:get_field(lock_utils.COMMAND_NAME) + if cmdName == "addCredential" or cmdName == "updateCredential" or cmdName == "addCota" then + set_pin_response_handler(driver, device, ib, response) + elseif cmdName == "setIssuerKey" then + set_issuer_key_response_handler(driver, device, ib, response) + elseif cmdName == "setEndpointKey" then + set_endpoint_key_response_handler(driver, device, ib, response) + end +end + +local function handle_delete_credential(driver, device, command) + -- Get parameters + local cmdName = "deleteCredential" + local credIdx = command.args.credentialIndex + local credential = { + credential_type = DoorLock.types.CredentialTypeEnum.PIN, + credential_index = credIdx, + } + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.CRED_INDEX, credIdx, {persist = true}) + + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send(DoorLock.server.commands.ClearCredential(device, ep, credential)) +end + +local function handle_delete_all_credentials(driver, device, command) + -- Get parameters + local cmdName = "deleteAllCredentials" + local credential = { + credential_type = DoorLock.types.CredentialTypeEnum.PIN, + credential_index = ALL_INDEX, + } + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.CRED_INDEX, ALL_INDEX, {persist = true}) + + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send(DoorLock.server.commands.ClearCredential(device, ep, credential)) +end + +local function clear_credential_response_handler(driver, device, ib, response) + local cmdName = device:get_field(lock_utils.COMMAND_NAME) + if cmdName ~= "deleteCredential" and cmdName ~= "clearEndpointKey" and + cmdName ~= "clearIssuerKey" and cmdName ~= "deleteAllCredentials" then + return + end + local status = RESPONSE_STATUS_MAP[ib.status] or "success" + local command_result_info = { commandName = cmdName, statusCode = status } -- default command result + local userIdx = device:get_field(lock_utils.USER_INDEX) + local all_user_credentials_removed = false + + if (cmdName == "deleteCredential" or cmdName == "deleteAllCredentials") and status == "success" then + -- Get result from data saved in relevant, associated fields + local credIdx = device:get_field(lock_utils.CRED_INDEX) + + -- find userIdx associated with credIdx, don't use lock utils field in this case + userIdx = delete_credential_from_table(device, credIdx) + if userIdx ~= nil then + all_user_credentials_removed = not has_credentials(device, userIdx) + end + + -- set unique command result fields + command_result_info.userIndex = userIdx + command_result_info.credentialIndex = credIdx + elseif cmdName == "clearIssuerKey" and status == "success" then + -- Get result from data saved in relevant, associated fields + local reqId = device:get_field(lock_utils.COMMAND_REQUEST_ID) + + delete_aliro_from_table(device, userIdx, "issuerKey", nil) + all_user_credentials_removed = not has_credentials(device, userIdx) + + -- set unique command result fields + command_result_info.userIndex = userIdx + command_result_info.requestId = reqId + elseif cmdName == "clearEndpointKey" and status == "success" then + -- Get result from data saved in relevant, associated fields + local deviceKeyId = device:get_field(lock_utils.DEVICE_KEY_ID) + local keyType = device:get_field(lock_utils.ENDPOINT_KEY_TYPE) + local reqId = device:get_field(lock_utils.COMMAND_REQUEST_ID) + + delete_aliro_from_table(device, userIdx, keyType, deviceKeyId) + all_user_credentials_removed = not has_credentials(device, userIdx) + + -- set unique command result fields + command_result_info.userIndex = userIdx + command_result_info.keyId = deviceKeyId + command_result_info.requestId = reqId + end + + -- user data if credentials were removed + if all_user_credentials_removed then + delete_user_from_table(device, userIdx) + delete_week_schedule_from_table_as_user(device, userIdx) + delete_year_schedule_from_table_as_user(device, userIdx) + end -local function handle_battery_charge_level(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()) + -- Update commandResult + if cmdName == "deleteCredential" or cmdName == "deleteAllCredentials" then + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + else + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) end + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) end -local function handle_power_source_attribute_list(driver, device, ib, response) - local support_battery_percentage = false - local support_battery_level = false - 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 - support_battery_percentage = true - elseif attr.value == 0x0E then - support_battery_level = true - end - end - local profile_name = device:get_field(PROFILE_BASE_NAME) - if profile_name ~= nil then - if not support_battery_percentage then - if support_battery_level then - profile_name = profile_name .. "-batteryLevel" - else - profile_name = profile_name .. "-nobattery" - end - end - device.log.info(string.format("Updating device profile to %s.", profile_name)) - device:try_update_metadata({profile = profile_name}) +local function handle_set_week_day_schedule(driver, device, command) + -- Get parameters + local cmdName = "setWeekDaySchedule" + local scheduleIdx = command.args.scheduleIndex + local userIdx = command.args.userIndex + local schedule = command.args.schedule + local wDays = {} + local scheduleBit = 0 + for _, weekDay in ipairs(schedule.weekDays) do + scheduleBit = scheduleBit + WEEK_DAY_MAP[weekDay] + table.insert(wDays, weekDay) end -end + local startHour = schedule.startHour + local startMinute = schedule.startMinute + local endHour = schedule.endHour + local endMinute = schedule.endMinute -local function max_pin_code_len_handler(driver, device, ib, response) - device:emit_event(capabilities.lockCodes.maxCodeLength(ib.data.value, {visibility = {displayed = false}})) -end + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockSchedules.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end -local function min_pin_code_len_handler(driver, device, ib, response) - device:emit_event(capabilities.lockCodes.minCodeLength(ib.data.value, {visibility = {displayed = false}})) -end + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + device:set_field(lock_utils.SCHEDULE_INDEX, scheduleIdx, {persist = true}) + device:set_field(lock_utils.SCHEDULE_WEEK_DAYS, wDays, {persist = true}) + device:set_field(lock_utils.SCHEDULE_START_HOUR, startHour, {persist = true}) + device:set_field(lock_utils.SCHEDULE_START_MINUTE, startMinute, {persist = true}) + device:set_field(lock_utils.SCHEDULE_END_HOUR, endHour, {persist = true}) + device:set_field(lock_utils.SCHEDULE_END_MINUTE, endMinute, {persist = true}) -local function num_pin_users_handler(driver, device, ib, response) - device:set_field(lock_utils.TOTAL_PIN_USERS, ib.data.value) - device:emit_event(capabilities.lockCodes.maxCodes(ib.data.value, {visibility = {displayed = false}})) + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send( + DoorLock.server.commands.SetWeekDaySchedule( + device, ep, + scheduleIdx, -- Week Day Schedule Index + userIdx, -- User Index + scheduleBit, -- Days Mask + startHour, -- Start Hour + startMinute, -- Start Minute + endHour, -- End Hour + endMinute -- End Minute + ) + ) end -local function apply_cota_credentials_if_absent(device) - if not device:get_field(lock_utils.COTA_CRED) then - --Process after all other info blocks have been dispatched to ensure MaxPINCodeLength has been processed - device.thread:call_with_delay(0, function(t) - generate_cota_cred_for_device(device) - -- delay needed to allow test to override the random credential data - device.thread:call_with_delay(0, function(t) - -- Attempt to set cota credential at the lowest index - set_cota_credential(device, INITIAL_COTA_INDEX) - end) - end) +local function set_week_day_schedule_handler(driver, device, ib, response) + -- Get result + local cmdName = device:get_field(lock_utils.COMMAND_NAME) + local userIdx = device:get_field(lock_utils.USER_INDEX) + local scheduleIdx = device:get_field(lock_utils.SCHEDULE_INDEX) + local days = device:get_field(lock_utils.SCHEDULE_WEEK_DAYS) + local sHour = device:get_field(lock_utils.SCHEDULE_START_HOUR) + local sMinute = device:get_field(lock_utils.SCHEDULE_START_MINUTE) + local eHour = device:get_field(lock_utils.SCHEDULE_END_HOUR) + local eMinute = device:get_field(lock_utils.SCHEDULE_END_MINUTE) + local schedule = { + weekDays = days, + startHour = sHour, + startMinute = sMinute, + endHour = eHour, + endMinute = eMinute + } + local status = "success" + if ib.status == DoorLock.types.DlStatus.FAILURE then + status = "failure" + elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then + status = "invalidCommand" end -end -local function require_remote_pin_handler(driver, device, ib, response) - if ib.data.value then - apply_cota_credentials_if_absent(device) + -- Add Week Day Schedule to table + if status == "success" then + add_week_schedule_to_table(device, userIdx, scheduleIdx, schedule) else - device:set_field(lock_utils.COTA_CRED, false, {persist = true}) + device.log.warn(string.format("Failed to set week day schedule: %s", status)) end + + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + scheduleIndex = scheduleIdx, + statusCode = status + } + device:emit_event(capabilities.lockSchedules.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) end -local function clear_credential_response_handler(driver, device, ib, response) - local deleted_code_slot = device:get_field(lock_utils.DELETING_CODE) - if deleted_code_slot == nil and ib.status == im.InteractionResponse.Status.SUCCESS then - device.log.debug("Cleared space in lock credential db for COTA credential") +local function handle_clear_week_day_schedule(driver, device, command) + -- Get parameters + local cmdName = "clearWeekDaySchedules" + local scheduleIdx = command.args.scheduleIndex + local userIdx = command.args.userIndex + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockSchedules.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) return end - if ib.status == im.InteractionResponse.Status.SUCCESS then - lock_utils.lock_codes_event(device, lock_utils.code_deleted(device, tostring(deleted_code_slot))) - --make sure cota credential exists if the user deletes it or if space was created for the COTA cred - if deleted_code_slot == device:get_field(lock_utils.COTA_CRED_INDEX) or - device:get_field(lock_utils.NONFUNCTIONAL) then - set_cota_credential(device, device:get_field(lock_utils.COTA_CRED_INDEX) or INITIAL_COTA_INDEX) - end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.SCHEDULE_INDEX, scheduleIdx, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send(DoorLock.server.commands.ClearWeekDaySchedule(device, ep, scheduleIdx, userIdx)) +end + +local function clear_week_day_schedule_handler(driver, device, ib, response) + -- Get result + local cmdName = device:get_field(lock_utils.COMMAND_NAME) + local scheduleIdx = device:get_field(lock_utils.SCHEDULE_INDEX) + local userIdx = device:get_field(lock_utils.USER_INDEX) + local status = "success" + if ib.status == DoorLock.types.DlStatus.FAILURE then + status = "failure" + elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then + status = "invalidCommand" + end + + -- Delete Week Day Schedule to table + if status == "success" then + delete_week_schedule_from_table(device, userIdx, scheduleIdx) else - device.log.error(string.format("Failed to delete code slot %s", deleted_code_slot)) + device.log.warn(string.format("Failed to clear week day schedule: %s", status)) end - device:set_field(lock_utils.DELETING_CODE, nil) + + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + scheduleIndex = scheduleIdx, + statusCode = status + } + device:emit_event(capabilities.lockSchedules.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) end -local function set_credential_response_handler(driver, device, ib, response) - if ib.status ~= im.InteractionResponse.Status.SUCCESS then - device.log.error("Failed to set code for device") +local function handle_set_year_day_schedule(driver, device, command) + -- Get parameters + local cmdName = "setYearDaySchedule" + local scheduleIdx = command.args.scheduleIndex + local userIdx = command.args.userIndex + local localStartTime = command.args.schedule.localStartTime + local localEndTime = command.args.schedule.localEndTime + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockSchedules.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) return end - local elements = ib.info_block.data.elements - local credential_index = device:get_field(lock_utils.SET_CREDENTIAL) - device:set_field(lock_utils.SET_CREDENTIAL, nil) - if credential_index == nil then - device.log.error("Received unexpected SetCredentialResponse") - return - end - local code_slot = tostring(credential_index) - local status = elements.status.value - if status == DoorLock.types.DlStatus.SUCCESS then - local event = capabilities.lockCodes.codeChanged("", {state_change = true}) - local cota_cred_index = device:get_field(lock_utils.COTA_CRED_INDEX) - local code_name = (credential_index == cota_cred_index and lock_utils.COTA_CODE_NAME) or - lock_utils.get_code_name(device, code_slot) - event.data = {codeName = code_name} - event.value = lock_utils.get_change_type(device, tostring(code_slot)) - local lock_codes = lock_utils.get_lock_codes(device) - lock_codes[code_slot] = event.data.codeName - device:emit_event(event) - if credential_index == cota_cred_index then - device:emit_event( - capabilities.lockCodes.codeChanged( - code_slot .. " renamed", {state_change = true} - ) - ) - end - lock_utils.lock_codes_event(device, lock_codes) - lock_utils.reset_code_state(device, code_slot) - if device:get_field(lock_utils.NONFUNCTIONAL) and cota_cred_index == credential_index then - device.log.info("Successfully set COTA credential after being non-functional") - device:set_field(lock_utils.NONFUNCTIONAL, false, {persist = true}) - local profile_name = "base-lock" - device:set_field(PROFILE_BASE_NAME, profile_name, {persist = true}) - local battery_feature_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) - if #battery_feature_eps == 0 then - profile_name = profile_name .. "-nobattery" - device.log.info(string.format("Updating device profile to %s.", profile_name)) - device:try_update_metadata({profile = profile_name, provisioning_state = "PROVISIONED"}) - else - local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) - req:merge(clusters.PowerSource.attributes.AttributeList:read()) - device:send(req) - end - end - elseif device:get_field(lock_utils.COTA_CRED) and credential_index == device:get_field(lock_utils.COTA_CRED_INDEX) then - -- Handle failure to set a COTA credential - if status == DoorLock.types.DlStatus.OCCUPIED and elements.next_credential_index.value ~= nil then - --This credential index is unavailable, but there is another available - set_cota_credential(device, elements.next_credential_index.value) - elseif status == DoorLock.types.DlStatus.OCCUPIED and - elements.next_credential_index.value == nil and - credential_index == INITIAL_COTA_INDEX then - --There are no credential indices available on the device - device.log.error("Device requires COTA credential, but has no credential indexes available!") - device.log.error("Lock and Unlock commands will no longer work!!") - device:set_field(PROFILE_BASE_NAME, "nonfunctional-lock", {persist = true}) - device:try_update_metadata({profile = "nonfunctional-lock", provisioning_state = "NONFUNCTIONAL"}) - device:set_field(lock_utils.NONFUNCTIONAL, true, {persist = true}) - elseif status == DoorLock.types.DlStatus.OCCUPIED and elements.next_credential_index.value == nil then - --There are no credential indices available, but we must ensure we search all indices. - set_cota_credential(device, INITIAL_COTA_INDEX) - elseif status == DoorLock.types.DlStatus.DUPLICATE then - --The credential we randomly generated already exists - generate_cota_cred_for_device(device) - --delay 0 needed for unit test verification of random value - device.thread:call_with_delay(0, function(t) set_cota_credential(device, credential_index) end) - elseif status == DoorLock.types.DlStatus.INVALID_FIELD then - device.log.error("Invalid SetCredential command sent to set a COTA credential. This is a bug.") - elseif elements.next_credential_index.value ~= nil then - device.log.warn(string.format( - "Received non-success SetCredentialResponse status (%s), but there is a next credential index available", elements.status - )) - set_cota_credential(device, elements.next_credential_index.value) - end - else - device.log.error( - string.format( - "Failed to set user code for device, SetCredential status received: %s", elements.status - ) + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + device:set_field(lock_utils.SCHEDULE_INDEX, scheduleIdx, {persist = true}) + device:set_field(lock_utils.SCHEDULE_LOCAL_START_TIME, localStartTime, {persist = true}) + device:set_field(lock_utils.SCHEDULE_LOCAL_END_TIME, localEndTime, {persist = true}) + + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send( + DoorLock.server.commands.SetYearDaySchedule( + device, ep, + scheduleIdx, + userIdx, + lock_utils.iso8601_to_epoch(localStartTime), + lock_utils.iso8601_to_epoch(localEndTime) ) - end + ) end -local function get_credential_status_response_handler(driver, device, ib, response) - if ib.status ~= im.InteractionResponse.Status.SUCCESS then - device.log.warn("Not taking action on GetCredentialStatusResponse because failed status") +local function set_year_day_schedule_handler(driver, device, ib, response) + -- Get result + local cmdName = device:get_field(lock_utils.COMMAND_NAME) + local userIdx = device:get_field(lock_utils.USER_INDEX) + local scheduleIdx = device:get_field(lock_utils.SCHEDULE_INDEX) + local localStartTime = device:get_field(lock_utils.SCHEDULE_LOCAL_START_TIME) + local localEndTime = device:get_field(lock_utils.SCHEDULE_LOCAL_END_TIME) + local status = "success" + if ib.status == DoorLock.types.DlStatus.FAILURE then + status = "failure" + elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then + status = "invalidCommand" end - local cred_index = device:get_field(lock_utils.CHECKING_CREDENTIAL) - if cred_index == nil then - device.log.warn("Received unexpected CredentialStatusResponse") + + if cmdName == "defaultSchedule" then + local cmdName = "addCredential" + local credIdx = device:get_field(lock_utils.CRED_INDEX) + + if status == "success" then + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + credentialIndex = credIdx, + statusCode = status + } + device:emit_event(capabilities.lockCredentials.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + else + local ep = find_default_endpoint(device, clusters.DoorLock.ID) + device:send(DoorLock.server.commands.ClearUser(device, ep, userIdx)) + end return end - local elements = ib.info_block.data.elements - local credential_exists = elements.credential_exists.value - local next_credential_index = elements.next_credential_index and elements.next_credential_index.value or nil - - local event = capabilities.lockCodes.codeChanged("", {state_change = true}) - local code_slot = tostring(cred_index) - local cota_cred_index = device:get_field(lock_utils.COTA_CRED_INDEX) - local code_name = (cred_index == cota_cred_index and lock_utils.COTA_CODE_NAME) or lock_utils.get_code_name(device, code_slot) - event.data = {codeName = code_name} - if credential_exists then - -- Code slot is occupied - event.value = lock_utils.get_change_type(device, code_slot) - local lock_codes = lock_utils.get_lock_codes(device) - lock_codes[code_slot] = event.data.codeName - device:emit_event(event) - lock_utils.lock_codes_event(device, lock_codes) - lock_utils.reset_code_state(device, code_slot) - else - -- Code slot is unoccupied - if (lock_utils.get_lock_codes(device)[code_slot] ~= nil) then - -- Code has been deleted - lock_utils.lock_codes_event(device, lock_utils.code_deleted(device, code_slot)) - if cred_index == cota_cred_index then --make sure cota credential exists if it was deleted - set_cota_credential(device, INITIAL_COTA_INDEX) - end - else - -- Code is unset - event.value = code_slot .. " unset" - device:emit_event(event) + + if status == "success" then + if cmdName == "setYearDaySchedule" then + add_year_schedule_to_table(device, userIdx, scheduleIdx, localStartTime, localEndTime) + elseif cmdName == "clearYearDaySchedules" then + delete_year_schedule_from_table(device, userIdx, scheduleIdx) end + else + device.log.warn(string.format("Failed to set/clear year day schedule: %s", status)) end - device:set_field(lock_utils.CHECKING_CREDENTIAL, nil) - local is_scanning = device:get_latest_state( - "main", capabilities.lockCodes.ID, capabilities.lockCodes.scanCodes.NAME - ) == "Scanning" - if not is_scanning then + -- Update commandResult + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + scheduleIndex = scheduleIdx, + statusCode = status + } + device:emit_event(capabilities.lockSchedules.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) +end + +local function handle_clear_year_day_schedule(driver, device, command) + -- Get parameters + local cmdName = "clearYearDaySchedules" + local scheduleIdx = command.args.scheduleIndex + local userIdx = command.args.userIndex + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockSchedules.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) return end - if (next_credential_index == nil) then - device:emit_event( - capabilities.lockCodes.scanCodes( - "Complete", {visibility = {displayed = false}} - ) - ) - local lock_codes = lock_utils.get_lock_codes(device) - lock_utils.lock_codes_event(device, lock_codes) - elseif next_credential_index ~= nil then - device:set_field(lock_utils.CHECKING_CREDENTIAL, next_credential_index) - device:send( - DoorLock.server.commands.GetCredentialStatus( - device, - ib.info_block.endpoint_id, - {credential_type = DoorLock.types.DlCredentialType.PIN, credential_index = device:get_field(lock_utils.CHECKING_CREDENTIAL)} - ) + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.SCHEDULE_INDEX, scheduleIdx, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + + -- Send command + -- In SmartThings, Schedule Restrict User is basically allowed to access always. + -- So, if user delete the year day schedule, enter an infinitely long schedule. + local ep = device:component_to_endpoint(command.component) + device:send( + DoorLock.server.commands.SetYearDaySchedule( + device, ep, + scheduleIdx, + userIdx, + lock_utils.MIN_EPOCH_S, + lock_utils.MAX_EPOCH_S ) - end + ) end local function alarm_event_handler(driver, device, ib, response) local DlAlarmCode = DoorLock.types.DlAlarmCode local alarm_code = ib.data.elements.alarm_code - if alarm_code.value == DlAlarmCode.FRONT_ESCEUTCHEON_REMOVED or alarm_code.value - == DlAlarmCode.WRONG_CODE_ENTRY_LIMIT or alarm_code.value == DlAlarmCode.FORCED_USER - or alarm_code.value == DlAlarmCode.DOOR_FORCED_OPEN then - device:emit_event(capabilities.tamperAlert.tamper.detected()) + if alarm_code.value == DlAlarmCode.LOCK_JAMMED then + device:emit_event(capabilities.lockAlarm.alarm.unableToLockTheDoor({state_change = true})) + elseif alarm_code.value == DlAlarmCode.LOCK_FACTORY_RESET then + device:emit_event(capabilities.lockAlarm.alarm.lockFactoryReset({state_change = true})) + elseif alarm_code.value == DlAlarmCode.WRONG_CODE_ENTRY_LIMIT then + device:emit_event(capabilities.lockAlarm.alarm.attemptsExceeded({state_change = true})) + elseif alarm_code.value == DlAlarmCode.FRONT_ESCEUTCHEON_REMOVED then + device:emit_event(capabilities.lockAlarm.alarm.damaged({state_change = true})) + elseif alarm_code.value == DlAlarmCode.DOOR_FORCED_OPEN then + device:emit_event(capabilities.lockAlarm.alarm.forcedOpeningAttempt({state_change = true})) end end local function lock_op_event_handler(driver, device, ib, response) - local tamper_detected = device:get_latest_state( - device:endpoint_to_component(ib.endopint_id), - capabilities.tamperAlert.ID, capabilities.tamperAlert.tamper.NAME - ) - if nil == tamper_detected or tamper_detected == capabilities.tamperAlert.tamper.detected.NAME then - device:emit_event(capabilities.tamperAlert.tamper.clear()) - end -end - -local function lock_user_change_event_handler(driver, device, ib, response) - local event = capabilities.lockCodes.codeChanged("", {state_change = true}) - local elements = ib.data.elements - local data_type_changed = elements.lock_data_type.value - local operation_type = elements.data_operation_type.value - local user_index = elements.user_index.value - local data_index = elements.data_index and elements.data_index.value - local cota_cred_index = device:get_field(lock_utils.COTA_CRED_INDEX) - - if data_type_changed == DoorLock.types.DlLockDataType.PIN then -- pin added or removed - local code_slot = data_index and tostring(data_index) or nil - if (operation_type == DoorLock.types.DlDataOperationType.ADD or operation_type - == DoorLock.types.DlDataOperationType.MODIFY) and code_slot ~= nil then - local change_type = lock_utils.get_change_type(device, code_slot) - event.value = change_type - local code_name = (data_index == cota_cred_index and lock_utils.COTA_CODE_NAME) or lock_utils.get_code_name(device, code_slot) - event.data = {codeName = code_name} - device:emit_event(event) - if string.match(change_type, "%d+ set") ~= nil then - local lock_codes = lock_utils.get_lock_codes(device) - lock_codes[code_slot] = code_name - lock_utils.lock_codes_event(device, lock_codes) - end - elseif operation_type == DoorLock.types.DlDataOperationType.CLEAR and code_slot ~= nil then - lock_utils.lock_codes_event(device, lock_utils.code_deleted(device, tostring(code_slot))) - --make sure cota credential is created if the user deletes it or a space is made for it - if data_index == cota_cred_index or device:get_field(lock_utils.NONFUNCTIONAL) then - set_cota_credential(device, cota_cred_index or INITIAL_COTA_INDEX) - end - else -- invalid event because no credential index - device.log.error( - "Received unhandled LockUserChangeEvent because it didn't affect a PIN credential" - ) - end - elseif data_type_changed == DoorLock.types.DlLockDataType.USER_INDEX and operation_type - == DoorLock.types.DlDataOperationType.CLEAR then - if user_index == 0xFFFE then - device.log.warn("All users were cleared by another fabric") -- we never do this - for cs, _ in pairs(lock_utils.get_lock_codes(device)) do - lock_utils.code_deleted(device, cs) - end - lock_utils.lock_codes_event(device, {}) - if device:get_field(lock_utils.COTA_CRED) ~= nil then set_cota_credential(device, INITIAL_COTA_INDEX) end - else - device.log.info("Not handling LockUserChange event") - end - -- Note when a Lock User is deleted, the credentials associated with that user are also deleted. - -- Change events are created for each credential as well as the user. + local opType = ib.data.elements.lock_operation_type + local opSource = ib.data.elements.operation_source + local userIdx = ib.data.elements.user_index + -- TODO: This handler can check fabric index and exclude other fabric events + + if opType == nil or opSource == nil then + return + end + + local Type = DoorLock.types.LockOperationTypeEnum + local Lock = capabilities.lock.lock + if opType.value == Type.LOCK then + opType = Lock.locked + elseif opType.value == Type.UNLOCK then + opType = Lock.unlocked + elseif opType.value == Type.UNLATCH then + opType = Lock.unlatched else - device.log.info( - string.format( - "Not handling LockUserChange event because the data type (%s) doesn't affect lock codes", - elements.lock_data_type - ) - ) + return end -end -local function handle_refresh(driver, device, command) - -- Note: no endpoint specified indicates a wildcard endpoint - local req = DoorLock.attributes.LockState:read(device) - req:merge(PowerSource.attributes.BatPercentRemaining:read(device)) - device:send(req) + local Source = DoorLock.types.OperationSourceEnum + if opSource.value == Source.UNSPECIFIED then + opSource = nil + elseif opSource.value == Source.MANUAL then + opSource = "manual" + elseif opSource.value == Source.PROPRIETARY_REMOTE then + opSource = "proprietaryRemote" + elseif opSource.value == Source.KEYPAD then + opSource = "keypad" + elseif opSource.value == Source.AUTO then + opSource = "auto" + elseif opSource.value == Source.BUTTON then + opSource = "button" + elseif opSource.value == Source.SCHEDULE then + opSource = nil + elseif opSource.value == Source.REMOTE then + opSource = "command" + elseif opSource.value == Source.RFID then + opSource = "rfid" + elseif opSource.value == Source.BIOMETRIC then + opSource = nil -- It will be updated R2 + elseif opSource.value == Source.ALIRO then + opSource = "digitalKey" + else + opSource = nil + end + + if userIdx ~= nil then + userIdx = userIdx.value + end + + local data_obj = {method = opSource, userIndex = userIdx} + device:emit_event(opType({data = data_obj, state_change = true})) end -local function handle_lock(driver, device, command) - local ep = device:component_to_endpoint(command.component) - local cota_cred = device:get_field(lock_utils.COTA_CRED) - if cota_cred then - device:send( - DoorLock.server.commands.LockDoor(device, ep, cota_cred) - ) +local function handle_refresh(driver, device, command) + if device:get_field(lock_utils.LOCK_CODES_COPY_REQUIRED) == true then + -- ensure battery state remains as it was before the profile migration + if device:supports_capability(capabilities.battery) then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_PERCENTAGE, {persist = true}) + elseif device:supports_capability(capabilities.batteryLevel) then + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_LEVEL, {persist = true}) + else + device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist = true}) + end + match_profile(driver, device, true) else - device:send(DoorLock.server.commands.LockDoor(device, ep)) + device:send(DoorLock.attributes.LockState:read(device)) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) end end -local function handle_unlock(driver, device, command) +local function handle_set_reader_config(driver, device, command) + local cmdName = "setReaderConfig" + local signingKey = command.args.signingKey + local verificationKey = command.args.verificationKey + local groupId = command.args.groupId + local groupResolvingKey = nil + local aliro_ble_uwb_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.ALIROBLEUWB}) + if #aliro_ble_uwb_eps > 0 then + groupResolvingKey = command.args.groupResolvingKey + end + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + statusCode = "busy" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + local reader_config_updated = device:get_field(lock_utils.ALIRO_READER_CONFIG_UPDATED) or nil + if reader_config_updated == "IN_PROGRESS" then + return + elseif reader_config_updated == "TRUE" then + -- Update commandResult + local command_result_info = { + commandName = cmdName, + statusCode = "success" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.VERIFICATION_KEY, verificationKey, {persist = true}) + device:set_field(lock_utils.GROUP_ID, groupId, {persist = true}) + device:set_field(lock_utils.GROUP_RESOLVING_KEY, groupResolvingKey, {persist = true}) + + -- Send command local ep = device:component_to_endpoint(command.component) - local cota_cred = device:get_field(lock_utils.COTA_CRED) - if cota_cred then - device:send( - DoorLock.server.commands.UnlockDoor(device, ep, cota_cred) + device:send( + DoorLock.server.commands.SetAliroReaderConfig( + device, ep, + lock_utils.hex_string_to_octet_string(signingKey), + lock_utils.hex_string_to_octet_string(verificationKey), + lock_utils.hex_string_to_octet_string(groupId), + lock_utils.hex_string_to_octet_string(groupResolvingKey) ) + ) + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "IN_PROGRESS", {persist = true}) +end + +local function set_aliro_reader_config_handler(driver, device, ib, response) + -- Get result + local cmdName = device:get_field(lock_utils.COMMAND_NAME) + local status = "failure" + if ib.status == DoorLock.types.DlStatus.SUCCESS then + status = "success" + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "TRUE", {persist = true}) else - device:send(DoorLock.server.commands.UnlockDoor(device, ep)) + if ib.status == DoorLock.types.DlStatus.INVALID_FIELD then + status = "invalidCommand" + end + device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, nil, {persist = true}) end -end -local function handle_delete_code(driver, device, command) - local endpoint = device:component_to_endpoint(command.component) - device:set_field(lock_utils.DELETING_CODE, command.args.codeSlot) - device:send(DoorLock.server.commands.ClearCredential( - device, - endpoint, - {credential_type = DoorLock.types.DlCredentialType.PIN, credential_index = command.args.codeSlot} + -- Update commandResult + local command_result_info = { + commandName = cmdName, + statusCode = status + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} )) + device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) end -local function handle_reload_all_codes(driver, device, command) - if (device:get_field(lock_utils.CHECKING_CREDENTIAL) == nil) then - lock_utils.lock_codes_event(device, {}) - device:set_field(lock_utils.CHECKING_CREDENTIAL, 1) - else - device.log.info(string.format("Delaying scanning since currently checking credential %d", device:get_field(lock_utils.CHECKING_CREDENTIAL))) - device.thread:call_with_delay(2, function(t) handle_reload_all_codes(driver, device, command) end) - return +local function handle_set_card_id(driver, device, command) + if command.args.cardId ~= nil then + device:emit_event(capabilities.lockAliro.cardId(command.args.cardId, {visibility = {displayed = false}})) end - device.thread:call_with_delay(5, function(t) - device:emit_event(capabilities.lockCodes.scanCodes("Scanning")) - device:send( - clusters.DoorLock.server.commands.GetCredentialStatus( - device, device:component_to_endpoint(command.component), - {credential_type = DoorLock.types.DlCredentialType.PIN, credential_index = device:get_field(lock_utils.CHECKING_CREDENTIAL)} - ) - ) - end) end -local function handle_request_code(driver, device, command) - local endpoint_id = device:component_to_endpoint(command.component) - device:set_field(lock_utils.CHECKING_CREDENTIAL, command.args.codeSlot) +local function handle_set_issuer_key(driver, device, command) + -- Get parameters + local cmdName = "setIssuerKey" + local userIdx = command.args.userIndex + local userType = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER + local issuerKey = command.args.issuerKey + local reqId = command.args.requestId local credential = { - credential_type = DoorLock.types.DlCredentialType.PIN, - credential_index = command.args.codeSlot, + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, + credential_index = INITIAL_CREDENTIAL_INDEX } - device:send(clusters.DoorLock.server.commands.GetCredentialStatus(device, endpoint_id, credential)) -end -local function handle_set_code(driver, device, command) - local endpoint = device:component_to_endpoint(command.component) - if (command.args.codePIN == "") then - driver:inject_capability_command( - device, { - capability = capabilities.lockCodes.ID, - command = capabilities.lockCodes.commands.nameSlot.NAME, - args = {command.args.codeSlot, command.args.codeName}, - } - ) + -- Adjustment + if userIdx == 0 then + userIdx = nil else - local credential = { - credential_type = DoorLock.types.DlCredentialType.PIN, - credential_index = command.args.codeSlot, + userType = nil + end + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + requestId = reqId, + statusCode = "busy" } - device:set_field(lock_utils.SET_CREDENTIAL, command.args.codeSlot) - device:send( - DoorLock.server.commands.SetCredential( - device, endpoint, DoorLock.types.DlDataOperationType.ADD, -- operation_type - credential, command.args.codePIN, -- credential_data - nil, -- nil user_index creates a new user - DoorLock.types.DlUserStatus.OCCUPIED_ENABLED, DoorLock.types.DlUserType.UNRESTRICTED_USER - ) - ) - if (command.args.codeName ~= nil) then - -- wait for confirmation from the lock to commit this to memory - -- Groovy driver has a lot more info passed here as a description string, may need to be investigated - local codeState = device:get_field(lock_utils.CODE_STATE) or {} - codeState["setName" .. command.args.codeSlot] = command.args.codeName - device:set_field(lock_utils.CODE_STATE, codeState, {persist = true}) - end + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return end -end -local function handle_name_slot(driver, device, command) - local code_slot = tostring(command.args.codeSlot) - local lock_codes = lock_utils.get_lock_codes(device) - if (lock_codes[code_slot] ~= nil) then - lock_codes[code_slot] = command.args.codeName - device:emit_event( - capabilities.lockCodes.codeChanged( - code_slot .. " renamed", {state_change = true} - ) + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + device:set_field(lock_utils.ISSUER_KEY, issuerKey, {persist = true}) + device:set_field(lock_utils.ISSUER_KEY_INDEX, INITIAL_CREDENTIAL_INDEX, {persist = true}) + device:set_field(lock_utils.COMMAND_REQUEST_ID, reqId, {persist = true}) + + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send( + DoorLock.server.commands.SetCredential( + device, ep, + DoorLock.types.DataOperationTypeEnum.ADD, -- Data Operation Type: Add(0), Modify(2) + credential, -- Credential + lock_utils.hex_string_to_octet_string(issuerKey), -- Credential Data + userIdx, -- User Index + nil, -- User Status + userType -- User Type ) - lock_utils.lock_codes_event(device, lock_codes) - end + ) end -local function find_default_endpoint(device, cluster) - local res = device.MATTER_DEFAULT_ENDPOINT - local eps = device:get_endpoints(cluster) - table.sort(eps) - for _, v in ipairs(eps) do - if v ~= 0 then --0 is the matter RootNode endpoint - return v +local function handle_clear_issuer_key(driver, device, command) + -- Get parameters + local cmdName = "clearIssuerKey" + local userIdx = command.args.userIndex + local reqId = command.args.requestId + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + requestId = reqId, + statusCode = "busy" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + device:set_field(lock_utils.COMMAND_REQUEST_ID, reqId, {persist = true}) + + -- Get latest aliro table + local aliro_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockAliro.ID, + capabilities.lockAliro.credentials.NAME, + {} + )) + + -- Find issuer key index + for index, entry in pairs(aliro_table) do + if entry.userIndex == userIdx and entry.keyType == "issuerKey" then + -- Set parameters + local credential = { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, + credential_index = entry.keyIndex, + } + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send(DoorLock.server.commands.ClearCredential(device, ep, credential)) + break 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) - return find_default_endpoint(device, clusters.DoorLock.ID) -end +local function handle_set_endpoint_key(driver, device, command) + -- Get parameters + local cmdName = "setEndpointKey" + local userIdx = command.args.userIndex + local userType = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER + local keyId = command.args.keyId + local keyType = command.args.keyType + local endpointKey = command.args.endpointKey + local reqId = command.args.requestId + local dataOpType = DoorLock.types.DataOperationTypeEnum.ADD -- Data Operation Type: Add(0), Modify(2) + local endpointKeyIndex = INITIAL_CREDENTIAL_INDEX -local function info_changed(driver, device, event, args) - if device.profile.id ~= args.old_st_store.profile.id then - device:subscribe() + -- Min user index of commandResult is 1 + -- 0 should convert to nil before busy check + if userIdx == 0 then + userIdx = nil end -end -local function do_configure(driver, device) - -- check if the device is NOT currently profiled as base-lock - -- by ANDing a query for every capability in the base-lock profiles. - -- If it does not use base-lock, it is WWST and does not need re-profiling. - if not (device:supports_capability(capabilities.lock) and - device:supports_capability(capabilities.lockCodes) and - device:supports_capability(capabilities.tamperAlert) and - device:supports_capability(capabilities.battery)) then - return - end - - -- if not fingerprinted, dynamically configure base-lock profile - local profile_name = "base-lock" - device:set_field(PROFILE_BASE_NAME, profile_name, {persist = true}) - local battery_feature_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) - if #battery_feature_eps == 0 then - profile_name = profile_name .. "-nobattery" - device.log.info(string.format("Updating device profile to %s.", profile_name)) - device:try_update_metadata({profile = profile_name}) - else - local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) - req:merge(clusters.PowerSource.attributes.AttributeList:read()) - device:send(req) + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + keyId = keyId, + requestId = reqId, + statusCode = "busy" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return end -end -local function device_init(driver, device) - device:set_component_to_endpoint_fn(component_to_endpoint) - device:subscribe() + -- Adjustment + if userIdx ~= nil then + userType = nil - -- check if we have a missing COTA credential. Only run this if it has not been run before (i.e. in device added), - -- because there is a delay built into the COTA process and we do not want to start two COTA generations at the same time - -- in the event this was triggered on add. - if not device:get_field(lock_utils.COTA_READ_INITIALIZED) or not device:get_field(lock_utils.COTA_CRED) then - local eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.DoorLockFeature.CREDENTIALSOTA | DoorLock.types.DoorLockFeature.PIN_CREDENTIALS}) - if #eps == 0 then - device.log.debug("Device will not require PIN for remote operation") - device:set_field(lock_utils.COTA_CRED, false, {persist = true}) - else - device:send(DoorLock.attributes.RequirePINforRemoteOperation:read(device, eps[1])) - device:set_field(lock_utils.COTA_READ_INITIALIZED, true, {persist = true}) + -- Get latest aliro table + local aliro_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockAliro.ID, + capabilities.lockAliro.credentials.NAME, + {} + )) + + -- Find existing endpoint key + for index, entry in pairs(aliro_table) do + if (entry.keyType == "evictableEndpointKey" or entry.keyType == "nonEvictableEndpointKey") and entry.keyId == keyId then + dataOpType = DoorLock.types.DataOperationTypeEnum.MODIFY + endpointKeyIndex = entry.keyIndex + delete_aliro_from_table(device, userIdx, keyType, keyId) + break + end end end + + local credential = { + credential_type = ALIRO_KEY_TYPE_TO_CRED_ENUM_MAP[keyType], + credential_index = endpointKeyIndex + } + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + device:set_field(lock_utils.DEVICE_KEY_ID, keyId, {persist = true}) + device:set_field(lock_utils.ENDPOINT_KEY_TYPE, keyType, {persist = true}) + device:set_field(lock_utils.ENDPOINT_KEY, endpointKey, {persist = true}) + device:set_field(lock_utils.ENDPOINT_KEY_INDEX, endpointKeyIndex, {persist = true}) + device:set_field(lock_utils.COMMAND_REQUEST_ID, reqId, {persist = true}) + + -- Send command + local ep = device:component_to_endpoint(command.component) + device:send( + DoorLock.server.commands.SetCredential( + device, ep, + dataOpType, -- Data Operation Type: Add(0), Modify(2) + credential, -- Credential + lock_utils.hex_string_to_octet_string(endpointKey), -- Credential Data + userIdx, -- User Index + nil, -- User Status + userType -- User Type + ) + ) end -local function device_added(driver, device) - --Note: May want to write OperatingMode to NORMAL, to attempt to ensure remote operation works - --Note: May want to write RequirePINForRemoteOperation, to avoid cota cases if possible. - device:emit_event(capabilities.tamperAlert.tamper.clear()) - local eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.DoorLockFeature.PIN_CREDENTIALS}) - if #eps == 0 then - if device:supports_capability_by_id(capabilities.tamperAlert.ID) then - device.log.debug("Device does not support lockCodes. Switching profile.") - local profile_name = "lock-without-codes" - device:set_field(PROFILE_BASE_NAME, profile_name, {persist = true}) - local battery_feature_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) - if #battery_feature_eps == 0 then - profile_name = profile_name .. "-nobattery" - end - device.log.info(string.format("Updating device profile to %s.", profile_name)) - device:try_update_metadata({profile = profile_name}) - else - device.log.debug("Device supports neither lock codes nor tamper. Unable to switch profile.") - end +local function handle_clear_endpoint_key(driver, device, command) + -- Get parameters + local cmdName = "clearEndpointKey" + local userIdx = command.args.userIndex + local keyId = command.args.keyId + local keyType = command.args.keyType + local reqId = command.args.requestId + + -- Check busy state + if lock_utils.is_busy_state_set(device) then + local command_result_info = { + commandName = cmdName, + userIndex = userIdx, + keyId = keyId, + requestId = reqId, + statusCode = "busy" + } + device:emit_event(capabilities.lockAliro.commandResult( + command_result_info, {state_change = true, visibility = {displayed = false}} + )) + return + end + + -- Save values to field + device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) + device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) + device:set_field(lock_utils.DEVICE_KEY_ID, keyId, {persist = true}) + device:set_field(lock_utils.ENDPOINT_KEY_TYPE, keyType, {persist = true}) + device:set_field(lock_utils.COMMAND_REQUEST_ID, reqId, {persist = true}) + + -- Get latest aliro table + local aliro_table = utils.deep_copy(device:get_latest_state( + "main", + capabilities.lockAliro.ID, + capabilities.lockAliro.credentials.NAME, + {} + )) + + local ep = device:component_to_endpoint(command.component) + if keyId == nil then + return else - local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) - req:merge(DoorLock.attributes.MaxPINCodeLength:read(device, eps[1])) - req:merge(DoorLock.attributes.MinPINCodeLength:read(device, eps[1])) - req:merge(DoorLock.attributes.NumberOfPINUsersSupported:read(device, eps[1])) - driver:inject_capability_command(device, { - capability = capabilities.lockCodes.ID, - command = capabilities.lockCodes.commands.reloadAllCodes.NAME, - args = {} - }) - - --Device may require pin for remote operation if it supports COTA and PIN features. - eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.DoorLockFeature.CREDENTIALSOTA | DoorLock.types.DoorLockFeature.PIN_CREDENTIALS}) - if #eps == 0 then - device.log.debug("Device will not require PIN for remote operation") - device:set_field(lock_utils.COTA_CRED, false, {persist = true}) - else - req:merge(DoorLock.attributes.RequirePINforRemoteOperation:read(device, eps[1])) - device:set_field(lock_utils.COTA_READ_INITIALIZED, true, {persist = true}) + -- Find aliro credential + for index, entry in pairs(aliro_table) do + if entry.userIndex == userIdx and entry.keyId == keyId and entry.keyType == keyType then + -- Set parameters + local credential = { + credential_type = ALIRO_KEY_TYPE_TO_CRED_ENUM_MAP[keyType], + credential_index = entry.keyIndex, + } + -- Send command + device:send(DoorLock.server.commands.ClearCredential(device, ep, credential)) + break + end end - device:send(req) end end local matter_lock_driver = { + NAME = "Matter Lock Driver", + lifecycle_handlers = { + init = device_init, + added = device_added, + doConfigure = do_configure, + infoChanged = info_changed, + driverSwitched = driver_switched + }, + shared_device_thread_enabled = true, matter_handlers = { attr = { [DoorLock.ID] = { [DoorLock.attributes.LockState.ID] = lock_state_handler, - [DoorLock.attributes.MaxPINCodeLength.ID] = max_pin_code_len_handler, + [DoorLock.attributes.OperatingMode.ID] = operating_modes_handler, + [DoorLock.attributes.NumberOfTotalUsersSupported.ID] = total_users_supported_handler, + [DoorLock.attributes.NumberOfPINUsersSupported.ID] = pin_users_supported_handler, [DoorLock.attributes.MinPINCodeLength.ID] = min_pin_code_len_handler, - [DoorLock.attributes.NumberOfPINUsersSupported.ID] = num_pin_users_handler, + [DoorLock.attributes.MaxPINCodeLength.ID] = max_pin_code_len_handler, [DoorLock.attributes.RequirePINforRemoteOperation.ID] = require_remote_pin_handler, + [DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser.ID] = max_week_schedule_of_user_handler, + [DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser.ID] = max_year_schedule_of_user_handler, + [DoorLock.attributes.AliroReaderVerificationKey.ID] = aliro_reader_verification_key_handler, + [DoorLock.attributes.AliroReaderGroupIdentifier.ID] = aliro_reader_group_id_handler, + [DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions.ID] = aliro_protocol_versions_handler, + [DoorLock.attributes.AliroGroupResolvingKey.ID] = aliro_group_resolving_key_handler, + [DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions.ID] = aliro_supported_ble_uwb_protocol_versions_handler, + [DoorLock.attributes.AliroBLEAdvertisingVersion.ID] = aliro_ble_advertising_version_handler, + [DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported.ID] = max_aliro_credential_issuer_key_handler, + [DoorLock.attributes.NumberOfAliroEndpointKeysSupported.ID] = max_aliro_endpoint_key_handler, + [DoorLockFeatureMapAttr.ID] = door_lock_feature_map_handler, }, [PowerSource.ID] = { [PowerSource.attributes.AttributeList.ID] = handle_power_source_attribute_list, [PowerSource.attributes.BatPercentRemaining.ID] = handle_battery_percent_remaining, [PowerSource.attributes.BatChargeLevel.ID] = handle_battery_charge_level, - }, + } }, event = { [DoorLock.ID] = { [DoorLock.events.DoorLockAlarm.ID] = alarm_event_handler, [DoorLock.events.LockOperation.ID] = lock_op_event_handler, - [DoorLock.events.LockUserChange.ID] = lock_user_change_event_handler, }, }, cmd_response = { [DoorLock.ID] = { + [DoorLock.server.commands.SetUser.ID] = set_user_response_handler, + [DoorLock.client.commands.GetUserResponse.ID] = get_user_response_handler, + [DoorLock.server.commands.ClearUser.ID] = clear_user_response_handler, [DoorLock.client.commands.SetCredentialResponse.ID] = set_credential_response_handler, - [DoorLock.client.commands.GetCredentialStatusResponse.ID] = get_credential_status_response_handler, [DoorLock.server.commands.ClearCredential.ID] = clear_credential_response_handler, + [DoorLock.server.commands.SetWeekDaySchedule.ID] = set_week_day_schedule_handler, + [DoorLock.server.commands.ClearWeekDaySchedule.ID] = clear_week_day_schedule_handler, + [DoorLock.server.commands.SetYearDaySchedule.ID] = set_year_day_schedule_handler, + [DoorLock.server.commands.SetAliroReaderConfig.ID] = set_aliro_reader_config_handler, }, }, }, subscribed_attributes = subscribed_attributes, - subscribed_events = { - [capabilities.tamperAlert.ID] = {DoorLock.events.DoorLockAlarm, DoorLock.events.LockOperation}, - [capabilities.lockAlarm.ID] = {DoorLock.events.DoorLockAlarm}, - [capabilities.lockCodes.ID] = {DoorLock.events.LockUserChange}, - }, + subscribed_events = subscribed_events, capability_handlers = { - [capabilities.refresh.ID] = {[capabilities.refresh.commands.refresh.NAME] = handle_refresh}, [capabilities.lock.ID] = { [capabilities.lock.commands.lock.NAME] = handle_lock, [capabilities.lock.commands.unlock.NAME] = handle_unlock, + [capabilities.lock.commands.unlatch.NAME] = handle_unlatch + }, + [capabilities.lockUsers.ID] = { + [capabilities.lockUsers.commands.addUser.NAME] = handle_add_user, + [capabilities.lockUsers.commands.updateUser.NAME] = handle_update_user, + [capabilities.lockUsers.commands.deleteUser.NAME] = handle_delete_user, + [capabilities.lockUsers.commands.deleteAllUsers.NAME] = handle_delete_all_users, + }, + [capabilities.lockCredentials.ID] = { + [capabilities.lockCredentials.commands.addCredential.NAME] = handle_add_credential, + [capabilities.lockCredentials.commands.updateCredential.NAME] = handle_update_credential, + [capabilities.lockCredentials.commands.deleteCredential.NAME] = handle_delete_credential, + [capabilities.lockCredentials.commands.deleteAllCredentials.NAME] = handle_delete_all_credentials, }, - [capabilities.lockCodes.ID] = { - [capabilities.lockCodes.commands.deleteCode.NAME] = handle_delete_code, - [capabilities.lockCodes.commands.reloadAllCodes.NAME] = handle_reload_all_codes, - [capabilities.lockCodes.commands.requestCode.NAME] = handle_request_code, - [capabilities.lockCodes.commands.setCode.NAME] = handle_set_code, - [capabilities.lockCodes.commands.nameSlot.NAME] = handle_name_slot, + [capabilities.lockSchedules.ID] = { + [capabilities.lockSchedules.commands.setWeekDaySchedule.NAME] = handle_set_week_day_schedule, + [capabilities.lockSchedules.commands.clearWeekDaySchedules.NAME] = handle_clear_week_day_schedule, + [capabilities.lockSchedules.commands.setYearDaySchedule.NAME] = handle_set_year_day_schedule, + [capabilities.lockSchedules.commands.clearYearDaySchedules.NAME] = handle_clear_year_day_schedule, }, + [capabilities.lockAliro.ID] = { + [capabilities.lockAliro.commands.setReaderConfig.NAME] = handle_set_reader_config, + [capabilities.lockAliro.commands.setCardId.NAME] = handle_set_card_id, + [capabilities.lockAliro.commands.setIssuerKey.NAME] = handle_set_issuer_key, + [capabilities.lockAliro.commands.clearIssuerKey.NAME] = handle_clear_issuer_key, + [capabilities.lockAliro.commands.setEndpointKey.NAME] = handle_set_endpoint_key, + [capabilities.lockAliro.commands.clearEndpointKey.NAME] = handle_clear_endpoint_key, + }, + [capabilities.refresh.ID] = {[capabilities.refresh.commands.refresh.NAME] = handle_refresh} }, supported_capabilities = { capabilities.lock, - capabilities.lockCodes, - capabilities.tamperAlert, + capabilities.lockUsers, + capabilities.lockCredentials, + capabilities.lockSchedules, capabilities.battery, - capabilities.batteryLevel, + capabilities.batteryLevel }, sub_drivers = require("sub_drivers"), - lifecycle_handlers = { - init = device_init, - added = device_added, - doConfigure = do_configure, - infoChanged = info_changed, - }, - shared_device_thread_enabled = true, } ------------------------------------------------------------------------------------------------------------------------------ --- Driver Initialization ------------------------------------------------------------------------------------------------------------------------------ local matter_driver = MatterDriver("matter-lock", matter_lock_driver) matter_driver:run() diff --git a/drivers/SmartThings/matter-lock/src/lock_utils.lua b/drivers/SmartThings/matter-lock/src/lock_utils.lua index ed804363b6..3836669115 100644 --- a/drivers/SmartThings/matter-lock/src/lock_utils.lua +++ b/drivers/SmartThings/matter-lock/src/lock_utils.lua @@ -46,11 +46,17 @@ local lock_utils = { MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED", ALIRO_READER_CONFIG_UPDATED = "aliroReaderConfigUpdated", LATEST_DOOR_LOCK_FEATURE_MAP = "latestDoorLockFeatureMap", + LOCK_CODES_COPY_REQUIRED = "lockCodesCopyRequired", + LOCK_CODES_FOR_MIGRATION = "lockCodesForMigration", } local capabilities = require "st.capabilities" local json = require "st.json" local utils = require "st.utils" +lock_utils.MIN_EPOCH_S = 0 +lock_utils.MAX_EPOCH_S = 0xffffffff +lock_utils.THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 + lock_utils.get_lock_codes = function(device) local lc = device:get_field(lock_utils.LOCK_CODES) return lc ~= nil and lc or {} @@ -170,6 +176,34 @@ function lock_utils.is_busy_state_set(device) end end +-- This type represents an offset, in seconds, from 0 hours, 0 minutes, 0 seconds, on the 1st of January, 2000 UTC +function lock_utils.iso8601_to_epoch(iso_str) + local pattern = "^(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)" + local year, month, day, hour, min, sec = iso_str:match(pattern) + if not year then + return nil + end + local epoch_s = os.time({ + year = tonumber(year), + month = tonumber(month), + day = tonumber(day), + hour = tonumber(hour), + min = tonumber(min), + sec = tonumber(sec), + }) + + -- The os.time() is based on 1970. Thirty years must be subtracted for calculations from 2000. + epoch_s = epoch_s - lock_utils.THIRTY_YEARS_S + + if epoch_s < lock_utils.MIN_EPOCH_S then + return lock_utils.MIN_EPOCH_S + elseif epoch_s > lock_utils.MAX_EPOCH_S then + return lock_utils.MAX_EPOCH_S + else + return epoch_s + end +end + function lock_utils.hex_string_to_octet_string(hex_string) if hex_string == nil then return nil diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/can_handle.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/can_handle.lua deleted file mode 100644 index 7bbb45ed44..0000000000 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/can_handle.lua +++ /dev/null @@ -1,19 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function is_new_matter_lock_products(opts, driver, device) - local device_lib = require "st.device" - if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then - return false - end - local FINGERPRINTS = require("new-matter-lock.fingerprints") - for _, p in ipairs(FINGERPRINTS) do - if device.manufacturer_info.vendor_id == p[1] and - device.manufacturer_info.product_id == p[2] then - return true, require("new-matter-lock") - end - end - return false -end - -return is_new_matter_lock_products diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua deleted file mode 100644 index fef7746990..0000000000 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/fingerprints.lua +++ /dev/null @@ -1,50 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -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 - {0x115f, 0x2805}, -- Aqara Smart Lock J200 Set - {0x115f, 0x280e}, -- AQARA Smart Gate Lock U500 - {0x115F, 0x280F}, -- Aqara Smart Lock U500 Rim - {0x115F, 0x2810}, -- Aqara Smart Lock U500 Glass Door - {0x115F, 0x284A}, -- Aqara Smart Lock U600 - {0x115F, 0x280D}, -- Aqara Smart Lock J200 - {0x147F, 0x0001}, -- U-tec - {0x147F, 0x0007}, -- ULTRALOQ Bolt Pro Smart Matter Door Lock - {0x147F, 0x0008}, -- Ultraloq, Bolt Smart Matter Door Lock - {0x147F, 0x0010}, -- ULTRALOQ Latch 5 Pro Smart Matter Door Lock - {0x144F, 0x4002}, -- Yale, Linus Smart Lock L2 - {0x101D, 0x8110}, -- Yale, New Lock - {0x1533, 0x0001}, -- eufy, E31 - {0x1533, 0x0002}, -- eufy, E30 - {0x1533, 0x0003}, -- eufy, C34 - {0x1533, 0x000F}, -- eufy, FamiLock S3 Max - {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 - {0x1533, 0x001F}, -- eufy, EAP - {0x135D, 0x00B1}, -- Nuki, Smart Lock Pro - {0x135D, 0x00B2}, -- Nuki, Smart Lock - {0x135D, 0x00C1}, -- Nuki, Smart Lock - {0x135D, 0x00A1}, -- Nuki, Smart Lock - {0x135D, 0x00B0}, -- Nuki, Smart Lock - {0x15F2, 0x0001}, -- Viomi, AiSafety Smart Lock E100 - {0x158B, 0x0001}, -- Deasino, DS-MT01 - {0x10E1, 0x2002}, -- VDA - {0x1421, 0x0041}, -- Kwikset Halo Select Plus - {0x1421, 0x0042}, -- Kwikset Halo Select Plus - {0x1421, 0x0081}, -- Kwikset Aura Reach - {0x1236, 0xa538}, -- Schlage Sense Pro - {0x1236, 0x3800}, -- Schlage - {0x1236, 0xA738}, -- Schlage - {0x152C, 0x9500}, -- Dreame NAVO Smart Lock A10 - {0x152C, 0x9501} -- Dreame NAVO Smart Lock E10 -} - -return NEW_MATTER_LOCK_PRODUCTS diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua deleted file mode 100644 index 3850d71380..0000000000 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ /dev/null @@ -1,3049 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local capabilities = require "st.capabilities" -local clusters = require "st.matter.clusters" -local im = require "st.matter.interaction_model" -local utils = require "st.utils" -local lock_utils = require "lock_utils" - -local version = require "version" -if version.api < 10 then - clusters.DoorLock = require "DoorLock" -end - -local DoorLock = clusters.DoorLock -local PowerSource = clusters.PowerSource - -local INITIAL_CREDENTIAL_INDEX = 1 -local ALL_INDEX = 0xFFFE --- maximum as defined by the Matter specification -local MAX_USER_NAME_LENGTH = 10 -local MIN_EPOCH_S = 0 -local MAX_EPOCH_S = 0xffffffff -local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 - -local RESPONSE_STATUS_MAP = { - [DoorLock.types.DlStatus.SUCCESS] = "success", - [DoorLock.types.DlStatus.FAILURE] = "failure", - [DoorLock.types.DlStatus.DUPLICATE] = "duplicate", - [DoorLock.types.DlStatus.OCCUPIED] = "occupied", - [DoorLock.types.DlStatus.INVALID_FIELD] = "invalidCommand", - [DoorLock.types.DlStatus.RESOURCE_EXHAUSTED] = "resourceExhausted", - [DoorLock.types.DlStatus.NOT_FOUND] = "failure" -} - -local WEEK_DAY_MAP = { - ["Sunday"] = 1, - ["Monday"] = 2, - ["Tuesday"] = 4, - ["Wednesday"] = 8, - ["Thursday"] = 16, - ["Friday"] = 32, - ["Saturday"] = 64, -} - -local ALIRO_KEY_TYPE_TO_CRED_ENUM_MAP = { - ["evictableEndpointKey"] = DoorLock.types.CredentialTypeEnum.ALIRO_EVICTABLE_ENDPOINT_KEY, - ["nonEvictableEndpointKey"] = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY -} - -local battery_support = { - NO_BATTERY = "NO_BATTERY", - BATTERY_LEVEL = "BATTERY_LEVEL", - BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE" -} - -local profiling_data = { - BATTERY_SUPPORT = "__BATTERY_SUPPORT", -} - -local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} -local subscribed_attributes = { - [capabilities.lock.ID] = { - DoorLock.attributes.LockState - }, - [capabilities.remoteControlStatus.ID] = { - DoorLock.attributes.OperatingMode - }, - [capabilities.lockUsers.ID] = { - DoorLock.attributes.NumberOfTotalUsersSupported - }, - [capabilities.lockCredentials.ID] = { - DoorLock.attributes.NumberOfPINUsersSupported, - DoorLock.attributes.MaxPINCodeLength, - DoorLock.attributes.MinPINCodeLength, - DoorLock.attributes.RequirePINforRemoteOperation - }, - [capabilities.lockSchedules.ID] = { - DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser, - DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser - }, - [capabilities.lockAliro.ID] = { - DoorLock.attributes.AliroReaderVerificationKey, - DoorLock.attributes.AliroReaderGroupIdentifier, - DoorLock.attributes.AliroReaderGroupSubIdentifier, - DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions, - DoorLock.attributes.AliroGroupResolvingKey, - DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions, - DoorLock.attributes.AliroBLEAdvertisingVersion, - DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported, - DoorLock.attributes.NumberOfAliroEndpointKeysSupported, - }, - [capabilities.battery.ID] = { - PowerSource.attributes.BatPercentRemaining - }, - [capabilities.batteryLevel.ID] = { - PowerSource.attributes.BatChargeLevel - } -} - -local subscribed_events = { - [capabilities.lock.ID] = { - DoorLock.events.LockOperation - }, - [capabilities.lockAlarm.ID] = { - DoorLock.events.DoorLockAlarm - }, - [capabilities.lockUsers.ID] = { - DoorLock.events.LockUserChange - } -} - -local function find_default_endpoint(device, cluster) - local res = device.MATTER_DEFAULT_ENDPOINT - local eps = device:get_endpoints(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) - return find_default_endpoint(device, clusters.DoorLock.ID) -end - -local function device_init(driver, device) - device:set_component_to_endpoint_fn(component_to_endpoint) - if #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) == 0 then - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist = true}) - elseif device:get_field(profiling_data.BATTERY_SUPPORT) == nil then - device:add_subscribed_attribute(clusters.PowerSource.attributes.AttributeList) - end - for cap_id, attributes in pairs(subscribed_attributes) do - if device:supports_capability_by_id(cap_id) then - for _, attr in ipairs(attributes) do - device:add_subscribed_attribute(attr) - end - end - end - device:add_subscribed_attribute(DoorLockFeatureMapAttr) - for cap_id, events in pairs(subscribed_events) do - if device:supports_capability_by_id(cap_id) then - for _, e in ipairs(events) do - device:add_subscribed_event(e) - end - end - end - device:subscribe() - end - -local function device_added(driver, device) - device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) -end - -local function set_reader_config(device) - local reader_config_updated = device:get_field(lock_utils.ALIRO_READER_CONFIG_UPDATED) or nil - if reader_config_updated == "TRUE" or reader_config_updated == "IN_PROGRESS" then return end - - local cmdName = "setReaderConfig" - local groupId = lock_utils.create_group_id_resolving_key() - local groupResolvingKey = nil - local aliro_ble_uwb_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.ALIROBLEUWB}) - if #aliro_ble_uwb_eps > 0 then - groupResolvingKey = lock_utils.create_group_id_resolving_key() - end - local privKey, pubKey = lock_utils.generate_keypair(device) - if not privKey or not pubKey then - local command_result_info = { - commandName = cmdName, - statusCode = "failure" - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.VERIFICATION_KEY, pubKey, {persist = true}) - device:set_field(lock_utils.GROUP_ID, groupId, {persist = true}) - device:set_field(lock_utils.GROUP_RESOLVING_KEY, groupResolvingKey, {persist = true}) - - -- Send command - local ep = find_default_endpoint(device, clusters.DoorLock.ID) - device:send( - DoorLock.server.commands.SetAliroReaderConfig( - device, ep, - lock_utils.hex_string_to_octet_string(privKey), - lock_utils.hex_string_to_octet_string(pubKey), - lock_utils.hex_string_to_octet_string(groupId), - lock_utils.hex_string_to_octet_string(groupResolvingKey) - ) - ) - device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "IN_PROGRESS", {persist = true}) -end - -local function match_profile_modular(driver, device) - local enabled_optional_component_capability_pairs = {} - local main_component_capabilities = {} - local modular_profile_name = "lock-modular" - for _, device_ep in pairs(device.endpoints) do - for _, ep_cluster in pairs(device_ep.clusters) do - if ep_cluster.cluster_id == DoorLock.ID then - local clus_has_feature = function(feature_bitmap) - return DoorLock.are_features_supported( - feature_bitmap, - lock_utils.get_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, device_ep.endpoint_id) or - ep_cluster.feature_map - ) - end - if clus_has_feature(DoorLock.types.Feature.USER) then - table.insert(main_component_capabilities, capabilities.lockUsers.ID) - end - if clus_has_feature(DoorLock.types.Feature.PIN_CREDENTIAL) then - table.insert(main_component_capabilities, capabilities.lockCredentials.ID) - end - if clus_has_feature(DoorLock.types.Feature.WEEK_DAY_ACCESS_SCHEDULES) or - clus_has_feature(DoorLock.types.Feature.YEAR_DAY_ACCESS_SCHEDULES) then - table.insert(main_component_capabilities, capabilities.lockSchedules.ID) - end - if clus_has_feature(DoorLock.types.Feature.UNBOLT) then - device:emit_event(capabilities.lock.supportedLockValues({"locked", "unlocked", "unlatched", "not fully locked"}, {visibility = {displayed = false}})) - device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) - modular_profile_name = "lock-modular-embedded-unlatch" -- use the embedded config specified in this profile for devices supporting "unlatch" - else - device:emit_event(capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) - device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) - end - if clus_has_feature(DoorLock.types.Feature.ALIRO_PROVISIONING) then - table.insert(main_component_capabilities, capabilities.lockAliro.ID) - end - break - end - end - end - - local supported_battery_type = device:get_field(profiling_data.BATTERY_SUPPORT) - if supported_battery_type == battery_support.BATTERY_LEVEL then - table.insert(main_component_capabilities, capabilities.batteryLevel.ID) - elseif supported_battery_type == battery_support.BATTERY_PERCENTAGE then - table.insert(main_component_capabilities, capabilities.battery.ID) - end - - table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) - if modular_profile_name == "lock-modular-embedded-unlatch" -- the embedded config that may be needed is not checked by an optional capability comparison - or lock_utils.optional_capabilities_list_changed(enabled_optional_component_capability_pairs, device.profile.components) then - device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) - end -end - -local function match_profile_switch(driver, device) - local user_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.USER}) - local pin_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.PIN_CREDENTIAL}) - local week_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.WEEK_DAY_ACCESS_SCHEDULES}) - local year_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.YEAR_DAY_ACCESS_SCHEDULES}) - local unbolt_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.UNBOLT}) - - local profile_name = "lock" - if #user_eps > 0 then - profile_name = profile_name .. "-user" - if #pin_eps > 0 then - profile_name = profile_name .. "-pin" - end - if #week_schedule_eps + #year_schedule_eps > 0 then - profile_name = profile_name .. "-schedule" - end - end - if #unbolt_eps > 0 then - profile_name = profile_name .. "-unlatch" - device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) - else - device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) - end - - local supported_battery_type = device:get_field(profiling_data.BATTERY_SUPPORT) - if supported_battery_type == battery_support.BATTERY_LEVEL then - profile_name = profile_name .. "-batteryLevel" - elseif supported_battery_type == battery_support.BATTERY_PERCENTAGE then - profile_name = profile_name .. "-battery" - 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 - ---- Deeply compare two values. ---- Handles metatables. Can optionally ignore cycle checking and/or function differences. ---- ---- @param a any ---- @param b any ---- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } ---- @param seen table|nil ---- @return boolean -local function deep_equals(a, b, opts, seen) - if a == b then return true end -- same object - if type(a) ~= type(b) then return false end -- different type - if type(a) == "function" and opts and opts.ignore_functions then return true end - if type(a) ~= "table" then return false end -- same type but not table, thus was already compared - - -- check for cycles in table references and preserve reference topology. - if not (opts and opts.ignore_cycles) then - seen = seen or {} - seen[a] = seen[a] or {} - if seen[a][b] then - return seen[a][b] - end - seen[a][b] = true - end - - -- Compare keys/values from a - for k, v in pairs(a) do - if not deep_equals(v, b[k], opts, seen) then - return false - end - end - - -- Ensure b doesn't have extra keys - for k in pairs(b) do - if a[k] == nil then - return false - end - end - - -- Compare metatables - local mt_a = getmetatable(a) - local mt_b = getmetatable(b) - return deep_equals(mt_a, mt_b, opts, seen) -end - -local function info_changed(driver, device, event, args) - if deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then - return - end - for cap_id, attributes in pairs(subscribed_attributes) do - if device:supports_capability_by_id(cap_id) then - for _, attr in ipairs(attributes) do - device:add_subscribed_attribute(attr) - end - end - end - for cap_id, events in pairs(subscribed_events) do - if device:supports_capability_by_id(cap_id) then - for _, e in ipairs(events) do - device:add_subscribed_event(e) - end - end - end - device:subscribe() - if device:supports_capability_by_id(capabilities.lockAliro.ID) then - set_reader_config(device) - end - if device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then - device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) - device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory - end -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(driver, device, ignore_static_profiling) - if profiling_data_still_required(device) then return end - if version.api >= 15 and version.rpc >= 9 then - match_profile_modular(driver, device) - elseif ignore_static_profiling ~= true then - match_profile_switch(driver, device) - end -end - -local function do_configure(driver, device) - match_profile(driver, device, false) - device.thread:call_with_delay(5, function() - device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true})) - device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is mandatory - end) -end - -local function driver_switched(driver, device) - match_profile(driver, device, false) - device:try_update_metadata({provisioning_state = "PROVISIONED"}) -end - --- Matter Handler ----------------- --- Lock State -- ----------------- -local function lock_state_handler(driver, device, ib, response) - local LockState = DoorLock.attributes.LockState - local attr = capabilities.lock.lock - local LOCK_STATE = { - [LockState.NOT_FULLY_LOCKED] = attr.not_fully_locked(), - [LockState.LOCKED] = attr.locked(), - [LockState.UNLOCKED] = attr.unlocked(), - [LockState.UNLATCHED] = attr.unlatched() - } - - -- The lock state is usually updated in lock_state_handler and lock_op_event_handler, respectively. - -- In this case, two events occur. To prevent this, when both functions are called, - -- it send the event after 1 second so that no event occurs in the lock_state_handler. - device.thread:call_with_delay(1, function () - if ib.data.value ~= nil then - device:emit_event(LOCK_STATE[ib.data.value]) - else - device.log.warn("Lock State is nil") - end - end) -end - ---------------------- --- Operating Modes -- ---------------------- -local function operating_modes_handler(driver, device, ib, response) - local status = capabilities.remoteControlStatus.remoteControlEnabled - local op_type = DoorLock.types.OperatingModeEnum - local opMode_map = { - [op_type.NORMAL] = true, - [op_type.VACATION] = true, - [op_type.PRIVACY] = false, - [op_type.NO_REMOTE_LOCK_UNLOCK] = false, - [op_type.PASSAGE] = false, - } - local result = opMode_map[ib.data.value] - local unbolt_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.UNBOLT}) - if result == true then - device:emit_event(status("true", {visibility = {displayed = true}})) - if #unbolt_eps > 0 then - device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) - else - device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) - end - elseif result == false then - device:emit_event(status("false", {visibility = {displayed = true}})) - device:emit_event(capabilities.lock.supportedLockCommands({}, {visibility = {displayed = false}})) - end -end - -------------------------------------- --- Number Of Total Users Supported -- -------------------------------------- -local function total_users_supported_handler(driver, device, ib, response) - device:emit_event(capabilities.lockUsers.totalUsersSupported(ib.data.value, {visibility = {displayed = false}})) -end - ----------------------------------- --- Number Of PIN Users Supported -- ----------------------------------- -local function pin_users_supported_handler(driver, device, ib, response) - device:emit_event(capabilities.lockCredentials.pinUsersSupported(ib.data.value, {visibility = {displayed = false}})) -end - -------------------------- --- Min PIN Code Length -- -------------------------- -local function min_pin_code_len_handler(driver, device, ib, response) - device:emit_event(capabilities.lockCredentials.minPinCodeLen(ib.data.value, {visibility = {displayed = false}})) -end - -------------------------- --- Max PIN Code Length -- -------------------------- -local function max_pin_code_len_handler(driver, device, ib, response) - device:emit_event(capabilities.lockCredentials.maxPinCodeLen(ib.data.value, {visibility = {displayed = false}})) -end - --------------------------------------- --- Require PIN For Remote Operation -- --------------------------------------- -local function set_cota_credential(device, credential_index) - local eps = device:get_endpoints(DoorLock.ID) - local cota_cred = device:get_field(lock_utils.COTA_CRED) - if cota_cred == nil then - -- Shouldn't happen but defensive to try to figure out if we need the cota cred and set it. - device:send(DoorLock.attributes.RequirePINforRemoteOperation:read(device, #eps > 0 and eps[1] or 1)) - return - elseif cota_cred == false then - device.log.debug("Device does not require PIN for remote operation. Not setting COTA credential") - return - end - - -- Check Busy State - if lock_utils.is_busy_state_set(device) then - device.log.debug("delaying setting COTA credential since a credential is currently being set") - device.thread:call_with_delay(2, function(t) - set_cota_credential(device, credential_index) - end) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, "addCota") - device:set_field(lock_utils.CRED_INDEX, credential_index) - device:set_field(lock_utils.COTA_CRED_INDEX, credential_index, {persist = true}) - device:set_field(lock_utils.USER_TYPE, "remote") - - -- Send command - device.log.info(string.format("Attempting to set COTA credential at index %s", credential_index)) - local credential = { - credential_type = DoorLock.types.CredentialTypeEnum.PIN, - credential_index = credential_index - } - device:send(DoorLock.server.commands.SetCredential( - device, - #eps > 0 and eps[1] or 1, - DoorLock.types.DataOperationTypeEnum.ADD, - credential, - device:get_field(lock_utils.COTA_CRED), - nil, -- nil user_index creates a new user - DoorLock.types.UserStatusEnum.OCCUPIED_ENABLED, - DoorLock.types.UserTypeEnum.REMOTE_ONLY_USER - )) -end - -local function generate_cota_cred_for_device(device) - local len = device:get_latest_state("main", capabilities.lockCredentials.ID, capabilities.lockCredentials.maxPinCodeLen.NAME) or 6 - local cred_data = math.floor(math.random() * (10 ^ len)) - cred_data = string.format("%0" .. tostring(len) .. "d", cred_data) - device:set_field(lock_utils.COTA_CRED, cred_data, {persist = true}) -end - -local function apply_cota_credentials_if_absent(device) - if not device:get_field(lock_utils.COTA_CRED) then - -- Process after all other info blocks have been dispatched to ensure MaxPINCodeLength has been processed - device.thread:call_with_delay(0, function(t) - generate_cota_cred_for_device(device) - -- delay needed to allow test to override the random credential data - device.thread:call_with_delay(0, function(t) - -- Attempt to set cota credential at the lowest index - set_cota_credential(device, INITIAL_CREDENTIAL_INDEX) - end) - end) - end -end - -local function require_remote_pin_handler(driver, device, ib, response) - if ib.data.value then - apply_cota_credentials_if_absent(device) - else - device:set_field(lock_utils.COTA_CRED, false, {persist = true}) - end -end - ------------------------------------------------------ --- Number Of Week Day Schedules Supported Per User -- ------------------------------------------------------ -local function max_week_schedule_of_user_handler(driver, device, ib, response) - device:emit_event(capabilities.lockSchedules.weekDaySchedulesPerUser(ib.data.value, {visibility = {displayed = false}})) -end - ------------------------------------------------------ --- Number Of Year Day Schedules Supported Per User -- ------------------------------------------------------ -local function max_year_schedule_of_user_handler(driver, device, ib, response) - device:emit_event(capabilities.lockSchedules.yearDaySchedulesPerUser(ib.data.value, {visibility = {displayed = false}})) -end - ------------------------------------ --- Aliro Reader Verification Key -- ------------------------------------ -local function aliro_reader_verification_key_handler(driver, device, ib, response) - if ib.data.value ~= nil then - device:emit_event(capabilities.lockAliro.readerVerificationKey( - utils.bytes_to_hex_string(ib.data.value), {visibility = {displayed = false}} - )) - device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "TRUE", {persist = true}) - end -end - ------------------------------------ --- Aliro Reader Group Identifier -- ------------------------------------ -local function aliro_reader_group_id_handler(driver, device, ib, response) - if ib.data.value ~= nil then - device:emit_event(capabilities.lockAliro.readerGroupIdentifier( - utils.bytes_to_hex_string(ib.data.value), - {visibility = {displayed = false}} - )) - end -end - -------------------------------------------------------------- --- Aliro Expedited Transaction Supported Protocol Versions -- -------------------------------------------------------------- -local function aliro_group_resolving_key_handler(driver, device, ib, response) - if ib.data.value ~= nil then - device:emit_event(capabilities.lockAliro.groupResolvingKey( - utils.bytes_to_hex_string(ib.data.value), - {visibility = {displayed = false}} - )) - end -end - -------------------------------- --- Aliro Group Resolving Key -- -------------------------------- -local function aliro_protocol_versions_handler(driver, device, ib, response) - if ib.data.elements == nil then - return - end - local protocol_versions = {} - for i, element in ipairs(ib.data.elements) do - local version = string.format("%s.%s", element.value:byte(1), element.value:byte(2)) - table.insert(protocol_versions, version); - end - device:emit_event(capabilities.lockAliro.expeditedTransactionProtocolVersions(protocol_versions, {visibility = {displayed = false}})) -end - ------------------------------------------------ --- Aliro Supported BLE UWB Protocol Versions -- ------------------------------------------------ -local function aliro_supported_ble_uwb_protocol_versions_handler(driver, device, ib, response) - if ib.data.elements == nil then - return - end - local protocol_versions = {} - for i, element in ipairs(ib.data.elements) do - local version = string.format("%s.%s", element.value:byte(1), element.value:byte(2)) - table.insert(protocol_versions, version); - end - device:emit_event(capabilities.lockAliro.bleUWBProtocolVersions(protocol_versions, {visibility = {displayed = false}})) -end - ------------------------------------ --- Aliro BLE Advertising Version -- ------------------------------------ -local function aliro_ble_advertising_version_handler(driver, device, ib, response) - if ib.data.value ~= nil then - device:emit_event(capabilities.lockAliro.bleAdvertisingVersion(string.format("%s", ib.data.value), {visibility = {displayed = false}})) - end -end - ------------------------------------------------------- --- Number Of Aliro Credential Issuer Keys Supported -- ------------------------------------------------------- -local function max_aliro_credential_issuer_key_handler(driver, device, ib, response) - if ib.data.value ~= nil then - device:emit_event(capabilities.lockAliro.maxCredentialIssuerKeys(ib.data.value, {visibility = {displayed = false}})) - end -end - ---------------------------------------------- --- Number Of Aliro Endpoint Keys Supported -- ---------------------------------------------- -local function max_aliro_endpoint_key_handler(driver, device, ib, response) - if ib.data.value ~= nil then - device:emit_event(capabilities.lockAliro.maxEndpointKeys(ib.data.value, {visibility = {displayed = false}})) - end -end - ------------------------------- --- Feature Map of Door Lock -- ------------------------------- -local function door_lock_feature_map_handler(driver, device, ib, response) - if ib.data.value == nil then return end - local feature_map = lock_utils.get_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id) or nil - if feature_map ~= ib.data.value then - lock_utils.set_field_for_endpoint(device, lock_utils.LATEST_DOOR_LOCK_FEATURE_MAP, ib.endpoint_id, ib.data.value, { persist = true }) - match_profile(driver, device, true) - end -end - ---------------------------------- --- Power Source Attribute List -- ---------------------------------- -local function handle_power_source_attribute_list(driver, device, ib, response) - local latest_battery_support = device:get_field(profiling_data.BATTERY_SUPPORT) - for _, attr in ipairs(ib.data.elements or {}) do - if attr.value == clusters.PowerSource.attributes.BatPercentRemaining.ID then - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_PERCENTAGE, {persist=true}) - break -- BATTERY_PERCENTAGE is highest priority. break early if found - elseif attr.value == clusters.PowerSource.attributes.BatChargeLevel.ID then - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_LEVEL, {persist=true}) - end - end - -- in the case that 1) no battery has been set, and 2) the returned ib does not include battery attributes, ignore battery - if latest_battery_support == nil and not device:get_field(profiling_data.BATTERY_SUPPORT) then - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, {persist=true}) - end - if latest_battery_support == nil or latest_battery_support ~= device:get_field(profiling_data.BATTERY_SUPPORT) then - match_profile(driver, device, false) - end -end - -------------------------------- --- Battery Percent Remaining -- -------------------------------- -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 - --------------------------- --- Battery Charge Level -- --------------------------- -local function handle_battery_charge_level(driver, device, ib, response) - if ib.data.value == PowerSource.types.BatChargeLevelEnum.OK then - device:emit_event(capabilities.batteryLevel.battery.normal()) - elseif ib.data.value == PowerSource.types.BatChargeLevelEnum.WARNING then - device:emit_event(capabilities.batteryLevel.battery.warning()) - elseif ib.data.value == PowerSource.types.BatChargeLevelEnum.CRITICAL then - device:emit_event(capabilities.batteryLevel.battery.critical()) - end -end - --- Capability Handler ------------------ --- Lock/Unlock -- ------------------ -local function handle_lock(driver, device, command) - local ep = device:component_to_endpoint(command.component) - local cota_cred = device:get_field(lock_utils.COTA_CRED) - if cota_cred then - device:send( - DoorLock.server.commands.LockDoor(device, ep, cota_cred) - ) - else - device:send(DoorLock.server.commands.LockDoor(device, ep)) - end -end - -local function handle_unlock(driver, device, command) - local unbolt_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.UNBOLT}) - local cota_cred = device:get_field(lock_utils.COTA_CRED) - local ep = device:component_to_endpoint(command.component) - - if #unbolt_eps > 0 then - if cota_cred then - device:send( - DoorLock.server.commands.UnboltDoor(device, ep, cota_cred) - ) - else - device:send(DoorLock.server.commands.UnboltDoor(device, ep)) - end - else - if cota_cred then - device:send( - DoorLock.server.commands.UnlockDoor(device, ep, cota_cred) - ) - else - device:send(DoorLock.server.commands.UnlockDoor(device, ep)) - end - end -end - -local function handle_unlatch(driver, device, command) - local ep = device:component_to_endpoint(command.component) - local cota_cred = device:get_field(lock_utils.COTA_CRED) - if cota_cred then - device:send( - DoorLock.server.commands.UnlockDoor(device, ep, cota_cred) - ) - else - device:send(DoorLock.server.commands.UnlockDoor(device, ep)) - end -end - ----------------- --- User Table -- ----------------- -local function add_user_to_table(device, userIdx, userName, userType) - -- Get latest user table - local user_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockUsers.ID, - capabilities.lockUsers.users.NAME, - {} - )) - - -- Add new entry to table - table.insert(user_table, {userIndex = userIdx, userName = userName, userType = userType}) - device:emit_event(capabilities.lockUsers.users(user_table, {visibility = {displayed = false}})) -end - -local function update_user_in_table(device, userIdx, userName, userType) - -- Get latest user table - local user_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockUsers.ID, - capabilities.lockUsers.users.NAME, - {} - )) - - -- Find user entry - local i = 0 - for index, entry in pairs(user_table) do - if entry.userIndex == userIdx then - i = index - break - end - end - - -- Update user entry - if i ~= 0 then - user_table[i].userType = userType - user_table[i].userName = userName - device:emit_event(capabilities.lockUsers.users(user_table, {visibility = {displayed = false}})) - end -end - -local function delete_user_from_table(device, userIdx) - -- If User Index is ALL_INDEX, remove all entry from the table - if userIdx == ALL_INDEX then - device:emit_event(capabilities.lockUsers.users({}, {visibility = {displayed = false}})) - return - end - - -- Get latest user table - local user_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockUsers.ID, - capabilities.lockUsers.users.NAME, - {} - )) - - -- Remove element from user table - for index, entry in pairs(user_table) do - if entry.userIndex == userIdx then - table.remove(user_table, index) - break - end - end - device:emit_event(capabilities.lockUsers.users(user_table, {visibility = {displayed = false}})) -end - ----------------------- --- Credential Table -- ----------------------- -local function has_credentials(device, userIdx) - -- Get latest credential table - local cred_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockCredentials.ID, - capabilities.lockCredentials.credentials.NAME, - {} - )) - - -- Find credential - for index, entry in pairs(cred_table) do - if entry.userIndex == userIdx then - return true - end - end - - -- Get latest Aliro credential table - local aliro_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockAliro.ID, - capabilities.lockAliro.credentials.NAME, - {} - )) - - -- Find Aliro credential - for index, entry in pairs(aliro_table) do - if entry.userIndex == userIdx then - return true - end - end - - return false -end - -local function add_credential_to_table(device, userIdx, credIdx, credType) - -- Get latest credential table - local cred_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockCredentials.ID, - capabilities.lockCredentials.credentials.NAME, - {} - )) - - -- Add new entry to table - table.insert(cred_table, {userIndex = userIdx, credentialIndex = credIdx, credentialType = credType}) - device:emit_event(capabilities.lockCredentials.credentials(cred_table, {visibility = {displayed = false}})) -end - -local function delete_credential_from_table(device, credIdx) - -- If Credential Index is ALL_INDEX, remove all entries from the table - if credIdx == ALL_INDEX then - device:emit_event(capabilities.lockCredentials.credentials({}, {visibility = {displayed = false}})) - return ALL_INDEX - end - - -- Get latest credential table - local cred_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockCredentials.ID, - capabilities.lockCredentials.credentials.NAME, - {} - )) - - -- Delete an entry from credential table - local userIdx = nil - for index, entry in pairs(cred_table) do - if entry.credentialIndex == credIdx then - table.remove(cred_table, index) - userIdx = entry.userIndex - break - end - end - - device:emit_event(capabilities.lockCredentials.credentials(cred_table, {visibility = {displayed = false}})) - return userIdx -end - -local function delete_credential_from_table_as_user(device, userIdx) - -- If User Index is ALL_INDEX, remove all entry from the table - if userIdx == ALL_INDEX then - device:emit_event(capabilities.lockCredentials.credentials({}, {visibility = {displayed = false}})) - return - end - - -- Get latest credential table - local cred_table = device:get_latest_state( - "main", - capabilities.lockCredentials.ID, - capabilities.lockCredentials.credentials.NAME - ) or {} - local new_cred_table = {} - - -- Re-create credential table - for index, entry in pairs(cred_table) do - if entry.userIndex ~= userIdx then - table.insert(new_cred_table, entry) - end - end - - device:emit_event(capabilities.lockCredentials.credentials(new_cred_table, {visibility = {displayed = false}})) -end - ------------------------------ --- Week Day Schedule Table -- ------------------------------ -local function add_week_schedule_to_table(device, userIdx, scheduleIdx, schedule) - -- Get latest week day schedule table - local week_schedule_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockSchedules.ID, - capabilities.lockSchedules.weekDaySchedules.NAME, - {} - )) - - -- Find schedule for specific user - local i = 0 - for index, entry in pairs(week_schedule_table) do - if entry.userIndex == userIdx then - i = index - end - end - - -- Re-create weekDays list - local weekDayList = {} - for _, weekday in ipairs(schedule.weekDays) do - table.insert(weekDayList, weekday) - end - - if i ~= 0 then -- Add schedule for existing user - -- Exclude same scheduleIdx - local new_schedule_table = {} - for index, entry in pairs(week_schedule_table[i].schedules) do - if entry.scheduleIndex ~= scheduleIdx then - table.insert(new_schedule_table, entry) - end - end - -- Add new entry to table - table.insert( - new_schedule_table, - { - scheduleIndex = scheduleIdx, - weekDays = weekDayList, - startHour = schedule.startHour, - startMinute = schedule.startMinute, - endHour = schedule.endHour, - endMinute = schedule.endMinute - } - ) - -- Update schedule for specific user - week_schedule_table[i].schedules = new_schedule_table - else -- Add schedule for new user - table.insert( - week_schedule_table, - { - userIndex = userIdx, - schedules = {{ - scheduleIndex = scheduleIdx, - weekDays = weekDayList, - startHour = schedule.startHour, - startMinute = schedule.startMinute, - endHour = schedule.endHour, - endMinute = schedule.endMinute - }} - } - ) - end - - device:emit_event(capabilities.lockSchedules.weekDaySchedules(week_schedule_table, {visibility = {displayed = false}})) -end - -local function delete_week_schedule_from_table(device, userIdx, scheduleIdx) - -- Get latest week day schedule table - local week_schedule_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockSchedules.ID, - capabilities.lockSchedules.weekDaySchedules.NAME, - {} - )) - - -- Find schedule for specific user - local i = 0 - for index, entry in pairs(week_schedule_table) do - if entry.userIndex == userIdx then - i = index - end - end - - -- When there is no userIndex in the table - if i == 0 then - return - end - - -- Re-create schedule table for the user - local new_schedule_table = {} - for index, entry in pairs(week_schedule_table[i].schedules) do - if entry.scheduleIndex ~= scheduleIdx then - table.insert(new_schedule_table, entry) - end - end - - -- If user has no schedule, remove user from the table - if #new_schedule_table == 0 then - table.remove(week_schedule_table, i) - else - week_schedule_table[i].schedules = new_schedule_table - end - - device:emit_event(capabilities.lockSchedules.weekDaySchedules(week_schedule_table, {visibility = {displayed = false}})) -end - -local function delete_week_schedule_from_table_as_user(device, userIdx) - -- If User Index is ALL_INDEX, remove all entry from the table - if userIdx == ALL_INDEX then - device:emit_event(capabilities.lockSchedules.weekDaySchedules({}, {visibility = {displayed = false}})) - return - end - - -- Get latest week day schedule table - local week_schedule_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockSchedules.ID, - capabilities.lockSchedules.weekDaySchedules.NAME, - {} - )) - - -- Re-create week day schedule table - local new_week_schedule_table = {} - for index, entry in pairs(week_schedule_table) do - if entry.userIndex ~= userIdx then - table.insert(new_week_schedule_table, entry) - end - end - - device:emit_event(capabilities.lockSchedules.weekDaySchedules(new_week_schedule_table, {visibility = {displayed = false}})) -end - ------------------------------ --- Year Day Schedule Table -- ------------------------------ -local function add_year_schedule_to_table(device, userIdx, scheduleIdx, sTime, eTime) - -- Get latest year day schedule table - local year_schedule_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockSchedules.ID, - capabilities.lockSchedules.yearDaySchedules.NAME, - {} - )) - - -- Find schedule for specific user - local i = 0 - for index, entry in pairs(year_schedule_table) do - if entry.userIndex == userIdx then - i = index - end - end - - if i ~= 0 then -- Add schedule for existing user - -- Exclude same scheduleIdx - local new_schedule_table = {} - for index, entry in pairs(year_schedule_table[i].schedules) do - if entry.scheduleIndex ~= scheduleIdx then - table.insert(new_schedule_table, entry) - end - end - -- Add new entry to table - table.insert( - new_schedule_table, - { - scheduleIndex = scheduleIdx, - localStartTime = sTime, - localEndTime = eTime - } - ) - -- Update schedule for specific user - year_schedule_table[i].schedules = new_schedule_table - else -- Add schedule for new user - table.insert( - year_schedule_table, - { - userIndex = userIdx, - schedules = {{ - scheduleIndex = scheduleIdx, - localStartTime = sTime, - localEndTime = eTime - }} - } - ) - end - - device:emit_event(capabilities.lockSchedules.yearDaySchedules(year_schedule_table, {visibility = {displayed = false}})) -end - -local function delete_year_schedule_from_table(device, userIdx, scheduleIdx) - -- Get latest year day schedule table - local year_schedule_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockSchedules.ID, - capabilities.lockSchedules.yearDaySchedules.NAME, - {} - )) - - -- Find schedule for specific user - local i = 0 - for index, entry in pairs(year_schedule_table) do - if entry.userIndex == userIdx then - i = index - end - end - - -- When there is no userIndex in the table - if i == 0 then - return - end - - -- Re-create year day schedule table for the user - local new_schedule_table = {} - for index, entry in pairs(year_schedule_table[i].schedules) do - if entry.scheduleIndex ~= scheduleIdx then - table.insert(new_schedule_table, entry) - end - end - - -- If user has no schedule, remove user from the table - if #new_schedule_table == 0 then - table.remove(year_schedule_table, i) - else - year_schedule_table[i].schedules = new_schedule_table - end - - device:emit_event(capabilities.lockSchedules.yearDaySchedules(year_schedule_table, {visibility = {displayed = false}})) -end - -local function delete_year_schedule_from_table_as_user(device, userIdx) - -- If User Index is ALL_INDEX, remove all entry from the table - if userIdx == ALL_INDEX then - device:emit_event(capabilities.lockSchedules.yearDaySchedules({}, {visibility = {displayed = false}})) - return - end - - -- Get latest year day schedule table - local year_schedule_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockSchedules.ID, - capabilities.lockSchedules.yearDaySchedules.NAME, - {} - )) - - -- Re-create year day schedule table - local new_year_schedule_table = {} - for index, entry in pairs(year_schedule_table) do - if entry.userIndex ~= userIdx then - table.insert(new_year_schedule_table, entry) - end - end - - device:emit_event(capabilities.lockSchedules.yearDaySchedules(new_year_schedule_table, {visibility = {displayed = false}})) -end - ----------------------------- --- Aliro Credential Table -- ----------------------------- -local function add_aliro_to_table(device, userIdx, keyIdx, keyType, keyId) - -- Get latest aliro table - local aliro_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockAliro.ID, - capabilities.lockAliro.credentials.NAME, - {} - )) - - -- Add new entry to table - table.insert(aliro_table, {userIndex = userIdx, keyIndex = keyIdx, keyType = keyType, keyId = keyId}) - device:emit_event(capabilities.lockAliro.credentials(aliro_table, {visibility = {displayed = false}})) -end - -local function delete_aliro_from_table(device, userIdx, keyType, keyId) - -- Get latest aliro table - local aliro_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockAliro.ID, - capabilities.lockAliro.credentials.NAME, - {} - )) - - -- Delete an entry from aliro table - if keyType == "issuerKey" then - for i, entry in pairs(aliro_table) do - if entry.userIndex == userIdx then - table.remove(aliro_table, i) - break - end - end - else - for i, entry in pairs(aliro_table) do - if entry.userIndex == userIdx and entry.keyId == keyId then - table.remove(aliro_table, i) - break - end - end - end - device:emit_event(capabilities.lockAliro.credentials(aliro_table, {visibility = {displayed = false}})) -end - -local function delete_aliro_from_table_as_user(device, userIdx) - -- If User Index is ALL_INDEX, remove all entry from the table - if userIdx == ALL_INDEX then - device:emit_event(capabilities.lockAliro.credentials({}, {visibility = {displayed = false}})) - return - end - - -- Get latest credential table - local aliro_table = device:get_latest_state( - "main", - capabilities.lockAliro.ID, - capabilities.lockAliro.credentials.NAME - ) or {} - local new_aliro_table = {} - - -- Re-create credential table - for index, entry in pairs(aliro_table) do - if entry.userIndex ~= userIdx then - table.insert(new_aliro_table, entry) - end - end - - device:emit_event(capabilities.lockAliro.credentials(new_aliro_table, {visibility = {displayed = false}})) -end - --------------- --- Add User -- --------------- -local function handle_add_user(driver, device, command) - -- Get parameters - local cmdName = "addUser" - local userName = command.args.userName - local userType = command.args.userType - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockUsers.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, INITIAL_CREDENTIAL_INDEX, {persist = true}) - device:set_field(lock_utils.USER_NAME, userName, {persist = true}) - device:set_field(lock_utils.USER_TYPE, userType, {persist = true}) - - -- Get available user index - local ep = device:component_to_endpoint(command.component) - device:send(DoorLock.server.commands.GetUser(device, ep, INITIAL_CREDENTIAL_INDEX)) -end - ------------------ --- Update User -- ------------------ -local function handle_update_user(driver, device, command) - -- Get parameters - local cmdName = "updateUser" - local userIdx = command.args.userIndex - local userName = command.args.userName - local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) - local userType = command.args.userType - local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER - if userType == "guest" then - userTypeMatter = DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER - end - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockUsers.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - device:set_field(lock_utils.USER_NAME, userName, {persist = true}) - device:set_field(lock_utils.USER_TYPE, userType, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send( - DoorLock.server.commands.SetUser( - device, ep, - DoorLock.types.DataOperationTypeEnum.MODIFY, - userIdx, - userNameMatter, - nil, -- Unique ID - nil, -- User Status - userTypeMatter, - nil -- Credential Rule - ) - ) -end - ------------------------ --- Get User Response -- ------------------------ -local function get_user_response_handler(driver, device, ib, response) - local elements = ib.info_block.data.elements - local userIdx = elements.user_index.value - local cmdName = device:get_field(lock_utils.COMMAND_NAME) - local status = "success" - if ib.status == DoorLock.types.DlStatus.FAILURE then - status = "failure" - elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then - status = "invalidCommand" - end - if status ~= "success" then - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - statusCode = status - } - device:emit_event(capabilities.lockUsers.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - end - - local ep = find_default_endpoint(device, DoorLock.ID) - local status = elements.user_status.value - local maxUser = device:get_latest_state( - "main", - capabilities.lockUsers.ID, - capabilities.lockUsers.totalUsersSupported.NAME - ) or 10 - - -- Found available user index - if status == nil or status == DoorLock.types.UserStatusEnum.AVAILABLE then - local userName = device:get_field(lock_utils.USER_NAME) - local userNameMatter = string.sub(userName, 1, MAX_USER_NAME_LENGTH) - local userType = device:get_field(lock_utils.USER_TYPE) - local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER - if userType == "guest" then - userTypeMatter = DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER - end - - -- Save values to field - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - - -- Send command - device:send( - DoorLock.server.commands.SetUser( - device, ep, - DoorLock.types.DataOperationTypeEnum.ADD, - userIdx, - userNameMatter, - nil, -- Unique ID - nil, -- User Status - userTypeMatter, - nil -- Credential Rule - ) - ) - elseif userIdx >= maxUser then -- There's no available user index - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - statusCode = "resourceExhausted" - } - device:emit_event(capabilities.lockUsers.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - else -- Check next user index - device:send(DoorLock.server.commands.GetUser(device, ep, userIdx + 1)) - end -end - ------------------------ --- Set User Response -- ------------------------ -local function set_user_response_handler(driver, device, ib, response) - -- Get result - local cmdName = device:get_field(lock_utils.COMMAND_NAME) - local userIdx = device:get_field(lock_utils.USER_INDEX) - local userName = device:get_field(lock_utils.USER_NAME) - local userType = device:get_field(lock_utils.USER_TYPE) - local status = "success" - if ib.status == DoorLock.types.DlStatus.FAILURE then - status = "failure" - elseif ib.status == DoorLock.types.DlStatus.OCCUPIED then - status = "occupied" - elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then - status = "invalidCommand" - end - - -- Update User in table - if status == "success" then - if cmdName == "addUser" then - add_user_to_table(device, userIdx, userName, userType) - elseif cmdName == "updateUser" then - update_user_in_table(device, userIdx, userName, userType) - end - else - device.log.warn(string.format("Failed to set user: %s", status)) - end - - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - statusCode = status - } - device:emit_event(capabilities.lockUsers.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) -end - ------------------ --- Delete User -- ------------------ -local function handle_delete_user(driver, device, command) - -- Get parameters - local cmdName = "deleteUser" - local userIdx = command.args.userIndex - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockUsers.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send(DoorLock.server.commands.ClearUser(device, ep, userIdx)) -end - ----------------------- --- Delete All Users -- ----------------------- -local function handle_delete_all_users(driver, device, command) - -- Get parameters - local cmdName = "deleteAllUsers" - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockUsers.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, ALL_INDEX, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send(DoorLock.server.commands.ClearUser(device, ep, ALL_INDEX)) -end - -------------------------- --- Clear User Response -- -------------------------- -local function clear_user_response_handler(driver, device, ib, response) - -- Get result - local cmdName = device:get_field(lock_utils.COMMAND_NAME) - local userIdx = device:get_field(lock_utils.USER_INDEX) - local status = "success" - if ib.status == DoorLock.types.DlStatus.FAILURE then - status = "failure" - elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then - status = "invalidCommand" - end - - -- Delete User and Credential from table - if status == "success" then - delete_user_from_table(device, userIdx) - delete_credential_from_table_as_user(device, userIdx) - delete_aliro_from_table_as_user(device, userIdx) - delete_week_schedule_from_table_as_user(device, userIdx) - delete_year_schedule_from_table_as_user(device, userIdx) - else - device.log.warn(string.format("Failed to clear user: %s", status)) - end - - -- In the "defaultSchedule" cmd failure path, when a guest user's credentials are set but the scheduling - -- fails during default setup, those credentials should be removed, so we wait to log lock credentials until here. - if cmdName == "defaultSchedule" then - -- note: if clear user succeeds, we'd log credential settings as a "failure" since it's effectively a no-op. - -- If clear user fails, log "success" since the credentials would still be present. - local lock_credential_status = status == "success" and "failure" or "success" - local command_result_info = { - commandName = "addCredential", - userIndex = userIdx, - statusCode = lock_credential_status - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - else - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - statusCode = status - } - device:emit_event(capabilities.lockUsers.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - end - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) -end - --------------------- --- Add Credential -- --------------------- -local function handle_add_credential(driver, device, command) - -- Get parameters - local cmdName = "addCredential" - local userIdx = command.args.userIndex - local userType = command.args.userType - local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER - if userType == "guest" then - userTypeMatter = DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER - end - if userIdx == 0 then - userIdx = nil - else - userTypeMatter = nil - end - local credential = { - credential_type = DoorLock.types.CredentialTypeEnum.PIN, - credential_index = INITIAL_CREDENTIAL_INDEX - } - local credData = command.args.credentialData - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - device:set_field(lock_utils.USER_TYPE, userType, {persist = true}) - device:set_field(lock_utils.CRED_INDEX, INITIAL_CREDENTIAL_INDEX, {persist = true}) - device:set_field(lock_utils.CRED_DATA, credData, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send( - DoorLock.server.commands.SetCredential( - device, ep, - DoorLock.types.DataOperationTypeEnum.ADD, -- Data Operation Type: Add(0), Modify(2) - credential, -- Credential - credData, -- Credential Data - userIdx, -- User Index - nil, -- User Status - userTypeMatter -- User Type - ) - ) -end - ------------------------ --- Update Credential -- ------------------------ -local function handle_update_credential(driver, device, command) - -- Get parameters - local cmdName = "updateCredential" - local userIdx = command.args.userIndex - local credIdx = command.args.credentialIndex - local credential = { - credential_type = DoorLock.types.CredentialTypeEnum.PIN, - credential_index = credIdx - } - local credData = command.args.credentialData - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - device:set_field(lock_utils.CRED_INDEX, credIdx, {persist = true}) - device:set_field(lock_utils.USER_TYPE, nil, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send( - DoorLock.server.commands.SetCredential( - device, ep, - DoorLock.types.DataOperationTypeEnum.MODIFY, -- Data Operation Type: Add(0), Modify(2) - credential, -- Credential - credData, -- Credential Data - userIdx, -- User Index - nil, -- User Status - nil -- User Type - ) - ) -end - ---------------------------------- --- Set Pin Credential Response -- ---------------------------------- -local function set_pin_response_handler(driver, device, ib, response) - if ib.status ~= im.InteractionResponse.Status.SUCCESS then - device.log.error("Failed to set credential for device") - return - end - - local cmdName = device:get_field(lock_utils.COMMAND_NAME) - local credData = device:get_field(lock_utils.CRED_DATA) - if cmdName == "addCota" then - credData = device:get_field(lock_utils.COTA_CRED) - end - local userIdx = device:get_field(lock_utils.USER_INDEX) - local userType = device:get_field(lock_utils.USER_TYPE) - local credIdx = device:get_field(lock_utils.CRED_INDEX) - local elements = ib.info_block.data.elements - local status = RESPONSE_STATUS_MAP[elements.status.value] - - if status == "success" then - -- Don't save user and credential for COTA - if cmdName == "addCota" then - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - return - end - - -- If user is added also, update User table - if userIdx == nil then - add_user_to_table(device, elements.user_index.value, nil, userType) - end - - -- Update Credential table - userIdx = elements.user_index.value - if cmdName == "addCredential" then - add_credential_to_table(device, userIdx, credIdx, "pin") - end - - -- If User Type is Guest and device support schedule, add default schedule - local week_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.WEEK_DAY_ACCESS_SCHEDULES}) - local year_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.YEAR_DAY_ACCESS_SCHEDULES}) - if userType == "guest" and (#week_schedule_eps > 0 or #year_schedule_eps > 0) and cmdName ~= "updateCredential" then - local cmdName = "defaultSchedule" - local scheduleIdx = 1 - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - device:set_field(lock_utils.SCHEDULE_INDEX, scheduleIdx, {persist = true}) - - local ep = device:component_to_endpoint("main") - device:send( - DoorLock.server.commands.SetYearDaySchedule( - device, ep, - scheduleIdx, - userIdx, - MIN_EPOCH_S, - MAX_EPOCH_S - ) - ) - else - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - credentialIndex = credIdx, - statusCode = status - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - end - return - end - - -- In the case DlStatus returns Occupied, this means the current credential index is in use, - -- so we must try the next one. If there is not a next index (i.e. it is nil), - -- we should mark this as "resourceExhausted" and stop attempting to set the credentials. - device.log.warn(string.format("Failed to set credential: %s", status)) - if status == "occupied" and elements.next_credential_index.value == nil then - local command_result_info = { - commandName = cmdName, - statusCode = "resourceExhausted" -- No more available credential index - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - elseif status == "occupied" then - -- Get parameters - local credIdx = elements.next_credential_index.value - local credential = { - credential_type = DoorLock.types.CredentialTypeEnum.PIN, - credential_index = credIdx, - } - local userIdx = device:get_field(lock_utils.USER_INDEX) - local userType = device:get_field(lock_utils.USER_TYPE) - local userTypeMatter = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER - if userIdx ~= nil then - userTypeMatter = nil - elseif userType == "guest" then - userTypeMatter = DoorLock.types.UserTypeEnum.SCHEDULE_RESTRICTED_USER - elseif userType == "remote" then - userTypeMatter = DoorLock.types.UserTypeEnum.REMOTE_ONLY_USER - end - - device:set_field(lock_utils.CRED_INDEX, credIdx, {persist = true}) - - -- Send command - local ep = find_default_endpoint(device, DoorLock.ID) - device:send( - DoorLock.server.commands.SetCredential( - device, ep, - DoorLock.types.DataOperationTypeEnum.ADD, -- Data Operation Type: Add(0), Modify(2) - credential, -- Credential - credData, -- Credential Data - userIdx, -- User Index - nil, -- User Status - userTypeMatter -- User Type - ) - ) - elseif status == "duplicate" and cmdName == "addCota" then - generate_cota_cred_for_device(device) - device.thread:call_with_delay(0, function(t) set_cota_credential(device, credIdx) end) - else - local command_result_info = { - commandName = cmdName, - statusCode = status - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - end -end - ------------------------------------ --- Set Aliro Credential Response -- ------------------------------------ -local function set_issuer_key_response_handler(driver, device, ib, response) - local cmdName = "setIssuerKey" - local userIdx = device:get_field(lock_utils.USER_INDEX) - local userType = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER - local issuerKeyIndex = device:get_field(lock_utils.ISSUER_KEY_INDEX) - local reqId = device:get_field(lock_utils.COMMAND_REQUEST_ID) - local elements = ib.info_block.data.elements - local status = RESPONSE_STATUS_MAP[elements.status.value] - - if status == "success" then - -- Delete field data - device:set_field(lock_utils.ISSUER_KEY, nil, {persist = true}) - - -- If user is added also, update User table - if userIdx == nil then - userIdx = elements.user_index.value - add_user_to_table(device, userIdx, nil, "adminMember") - end - - -- Update Aliro table - add_aliro_to_table(device, userIdx, issuerKeyIndex, "issuerKey", nil) - - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - requestId = reqId, - statusCode = status - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - return - end - - -- In the case DlStatus returns Occupied, this means the current credential index is in use, - -- so we must try the next one. If there is not a next index (i.e. it is nil), - -- we should mark this as "resourceExhausted" and stop attempting to set the credentials. - device.log.warn(string.format("Failed to set credential: %s", status)) - if status == "occupied" and elements.next_credential_index.value == nil then - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - requestId = reqId, - statusCode = "resourceExhausted" -- No more available credential index - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - elseif status == "occupied" then - -- Get parameters - if userIdx ~= nil then - userType = nil - end - local credIdx = elements.next_credential_index.value - local credType = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY - local credData = device:get_field(lock_utils.ISSUER_KEY) - local credential = { - credential_type = credType, - credential_index = credIdx - } - - -- Save values to field - device:set_field(lock_utils.ISSUER_KEY_INDEX, credIdx, {persist = true}) - - -- Send command - local ep = find_default_endpoint(device, DoorLock.ID) - device:send( - DoorLock.server.commands.SetCredential( - device, ep, - DoorLock.types.DataOperationTypeEnum.ADD, - credential, -- Credential - lock_utils.hex_string_to_octet_string(credData), -- Credential Data - userIdx, -- User Index - nil, -- User Status - userType -- User Type - ) - ) - else - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - requestId = reqId, - statusCode = status - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - end -end - -local function set_endpoint_key_response_handler(driver, device, ib, response) - local cmdName = "setEndpointKey" - local userIdx = device:get_field(lock_utils.USER_INDEX) - local userType = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER - local keyId = device:get_field(lock_utils.DEVICE_KEY_ID) - local keyType = device:get_field(lock_utils.ENDPOINT_KEY_TYPE) - local endpointKeyIndex = device:get_field(lock_utils.ENDPOINT_KEY_INDEX) - local reqId = device:get_field(lock_utils.COMMAND_REQUEST_ID) - local elements = ib.info_block.data.elements - local status = RESPONSE_STATUS_MAP[elements.status.value] - - if status == "success" then - -- Delete field data - device:set_field(lock_utils.ENDPOINT_KEY, nil, {persist = true}) - - -- If user is added also, update User table - if userIdx == nil then - userIdx = elements.user_index.value - add_user_to_table(device, userIdx, nil, "adminMember") - end - - -- Update Aliro table - add_aliro_to_table(device, userIdx, endpointKeyIndex, keyType, keyId) - - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - keyId = keyId, - requestId = reqId, - statusCode = status - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - return - end - - -- In the case DlStatus returns Occupied, this means the current credential index is in use, - -- so we must try the next one. If there is not a next index (i.e. it is nil), - -- we should mark this as "resourceExhausted" and stop attempting to set the credentials. - device.log.warn(string.format("Failed to set credential: %s", status)) - - if status == "occupied" and elements.next_credential_index.value == nil then - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - keyId = keyId, - requestId = reqId, - statusCode = "resourceExhausted" -- No more available credential index - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - elseif status == "occupied" then - -- Get parameters - if userIdx ~= nil then - userType = nil - end - local credIdx = elements.next_credential_index.value - local credType = ALIRO_KEY_TYPE_TO_CRED_ENUM_MAP[keyType] - local credData = device:get_field(lock_utils.ENDPOINT_KEY) - local credential = { - credential_type = credType, - credential_index = credIdx - } - - -- Save values to field - device:set_field(lock_utils.ENDPOINT_KEY_INDEX, credIdx, {persist = true}) - - -- Send command - local ep = find_default_endpoint(device, DoorLock.ID) - device:send( - DoorLock.server.commands.SetCredential( - device, ep, - DoorLock.types.DataOperationTypeEnum.ADD, - credential, -- Credential - lock_utils.hex_string_to_octet_string(credData), -- Credential Data - userIdx, -- User Index - nil, -- User Status - userType -- User Type - ) - ) - else - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - keyId = keyId, - requestId = reqId, - statusCode = status - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - end -end - -local function set_credential_response_handler(driver, device, ib, response) - if ib.status ~= im.InteractionResponse.Status.SUCCESS then - device.log.error("Failed to set credential for device") - return - end - local cmdName = device:get_field(lock_utils.COMMAND_NAME) - if cmdName == "addCredential" or cmdName == "updateCredential" or cmdName == "addCota" then - set_pin_response_handler(driver, device, ib, response) - elseif cmdName == "setIssuerKey" then - set_issuer_key_response_handler(driver, device, ib, response) - elseif cmdName == "setEndpointKey" then - set_endpoint_key_response_handler(driver, device, ib, response) - end -end - ------------------------ --- Delete Credential -- ------------------------ -local function handle_delete_credential(driver, device, command) - -- Get parameters - local cmdName = "deleteCredential" - local credIdx = command.args.credentialIndex - local credential = { - credential_type = DoorLock.types.CredentialTypeEnum.PIN, - credential_index = credIdx, - } - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.CRED_INDEX, credIdx, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send(DoorLock.server.commands.ClearCredential(device, ep, credential)) -end - ----------------------------- --- Delete All Credentials -- ----------------------------- -local function handle_delete_all_credentials(driver, device, command) - -- Get parameters - local cmdName = "deleteAllCredentials" - local credential = { - credential_type = DoorLock.types.CredentialTypeEnum.PIN, - credential_index = ALL_INDEX, - } - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.CRED_INDEX, ALL_INDEX, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send(DoorLock.server.commands.ClearCredential(device, ep, credential)) -end - -------------------------------- --- Clear Credential Response -- -------------------------------- -local function clear_credential_response_handler(driver, device, ib, response) - local cmdName = device:get_field(lock_utils.COMMAND_NAME) - if cmdName ~= "deleteCredential" and cmdName ~= "clearEndpointKey" and - cmdName ~= "clearIssuerKey" and cmdName ~= "deleteAllCredentials" then - return - end - local status = RESPONSE_STATUS_MAP[ib.status] or "success" - local command_result_info = { commandName = cmdName, statusCode = status } -- default command result - local userIdx = device:get_field(lock_utils.USER_INDEX) - local all_user_credentials_removed = false - - if (cmdName == "deleteCredential" or cmdName == "deleteAllCredentials") and status == "success" then - -- Get result from data saved in relevant, associated fields - local credIdx = device:get_field(lock_utils.CRED_INDEX) - - -- find userIdx associated with credIdx, don't use lock utils field in this case - userIdx = delete_credential_from_table(device, credIdx) - if userIdx ~= nil then - all_user_credentials_removed = not has_credentials(device, userIdx) - end - - -- set unique command result fields - command_result_info.userIndex = userIdx - command_result_info.credentialIndex = credIdx - elseif cmdName == "clearIssuerKey" and status == "success" then - -- Get result from data saved in relevant, associated fields - local reqId = device:get_field(lock_utils.COMMAND_REQUEST_ID) - - delete_aliro_from_table(device, userIdx, "issuerKey", nil) - all_user_credentials_removed = not has_credentials(device, userIdx) - - -- set unique command result fields - command_result_info.userIndex = userIdx - command_result_info.requestId = reqId - elseif cmdName == "clearEndpointKey" and status == "success" then - -- Get result from data saved in relevant, associated fields - local deviceKeyId = device:get_field(lock_utils.DEVICE_KEY_ID) - local keyType = device:get_field(lock_utils.ENDPOINT_KEY_TYPE) - local reqId = device:get_field(lock_utils.COMMAND_REQUEST_ID) - - delete_aliro_from_table(device, userIdx, keyType, deviceKeyId) - all_user_credentials_removed = not has_credentials(device, userIdx) - - -- set unique command result fields - command_result_info.userIndex = userIdx - command_result_info.keyId = deviceKeyId - command_result_info.requestId = reqId - end - - -- user data if credentials were removed - if all_user_credentials_removed then - delete_user_from_table(device, userIdx) - delete_week_schedule_from_table_as_user(device, userIdx) - delete_year_schedule_from_table_as_user(device, userIdx) - end - - -- Update commandResult - if cmdName == "deleteCredential" or cmdName == "deleteAllCredentials" then - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - else - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - end - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) -end - ---------------------------- --- Set Week Day Schedule -- ---------------------------- -local function handle_set_week_day_schedule(driver, device, command) - -- Get parameters - local cmdName = "setWeekDaySchedule" - local scheduleIdx = command.args.scheduleIndex - local userIdx = command.args.userIndex - local schedule = command.args.schedule - local wDays = {} - local scheduleBit = 0 - for _, weekDay in ipairs(schedule.weekDays) do - scheduleBit = scheduleBit + WEEK_DAY_MAP[weekDay] - table.insert(wDays, weekDay) - end - local startHour = schedule.startHour - local startMinute = schedule.startMinute - local endHour = schedule.endHour - local endMinute = schedule.endMinute - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockSchedules.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - device:set_field(lock_utils.SCHEDULE_INDEX, scheduleIdx, {persist = true}) - device:set_field(lock_utils.SCHEDULE_WEEK_DAYS, wDays, {persist = true}) - device:set_field(lock_utils.SCHEDULE_START_HOUR, startHour, {persist = true}) - device:set_field(lock_utils.SCHEDULE_START_MINUTE, startMinute, {persist = true}) - device:set_field(lock_utils.SCHEDULE_END_HOUR, endHour, {persist = true}) - device:set_field(lock_utils.SCHEDULE_END_MINUTE, endMinute, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send( - DoorLock.server.commands.SetWeekDaySchedule( - device, ep, - scheduleIdx, -- Week Day Schedule Index - userIdx, -- User Index - scheduleBit, -- Days Mask - startHour, -- Start Hour - startMinute, -- Start Minute - endHour, -- End Hour - endMinute -- End Minute - ) - ) -end - ------------------------------------- --- Set Week Day Schedule Response -- ------------------------------------- -local function set_week_day_schedule_handler(driver, device, ib, response) - -- Get result - local cmdName = device:get_field(lock_utils.COMMAND_NAME) - local userIdx = device:get_field(lock_utils.USER_INDEX) - local scheduleIdx = device:get_field(lock_utils.SCHEDULE_INDEX) - local days = device:get_field(lock_utils.SCHEDULE_WEEK_DAYS) - local sHour = device:get_field(lock_utils.SCHEDULE_START_HOUR) - local sMinute = device:get_field(lock_utils.SCHEDULE_START_MINUTE) - local eHour = device:get_field(lock_utils.SCHEDULE_END_HOUR) - local eMinute = device:get_field(lock_utils.SCHEDULE_END_MINUTE) - local schedule = { - weekDays = days, - startHour = sHour, - startMinute = sMinute, - endHour = eHour, - endMinute = eMinute - } - local status = "success" - if ib.status == DoorLock.types.DlStatus.FAILURE then - status = "failure" - elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then - status = "invalidCommand" - end - - -- Add Week Day Schedule to table - if status == "success" then - add_week_schedule_to_table(device, userIdx, scheduleIdx, schedule) - else - device.log.warn(string.format("Failed to set week day schedule: %s", status)) - end - - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - scheduleIndex = scheduleIdx, - statusCode = status - } - device:emit_event(capabilities.lockSchedules.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) -end - ------------------------------ --- Clear Week Day Schedule -- ------------------------------ -local function handle_clear_week_day_schedule(driver, device, command) - -- Get parameters - local cmdName = "clearWeekDaySchedules" - local scheduleIdx = command.args.scheduleIndex - local userIdx = command.args.userIndex - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockSchedules.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.SCHEDULE_INDEX, scheduleIdx, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send(DoorLock.server.commands.ClearWeekDaySchedule(device, ep, scheduleIdx, userIdx)) -end - ------------------------------------- --- Clear Week Day Schedule Response -- ------------------------------------- -local function clear_week_day_schedule_handler(driver, device, ib, response) - -- Get result - local cmdName = device:get_field(lock_utils.COMMAND_NAME) - local scheduleIdx = device:get_field(lock_utils.SCHEDULE_INDEX) - local userIdx = device:get_field(lock_utils.USER_INDEX) - local status = "success" - if ib.status == DoorLock.types.DlStatus.FAILURE then - status = "failure" - elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then - status = "invalidCommand" - end - - -- Delete Week Day Schedule to table - if status == "success" then - delete_week_schedule_from_table(device, userIdx, scheduleIdx) - else - device.log.warn(string.format("Failed to clear week day schedule: %s", status)) - end - - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - scheduleIndex = scheduleIdx, - statusCode = status - } - device:emit_event(capabilities.lockSchedules.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) -end - --- This type represents an offset, in seconds, from 0 hours, 0 minutes, 0 seconds, on the 1st of January, 2000 UTC -local function iso8601_to_epoch(iso_str) - local pattern = "^(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)" - local year, month, day, hour, min, sec = iso_str:match(pattern) - if not year then - return nil - end - local epoch_s = os.time({ - year = tonumber(year), - month = tonumber(month), - day = tonumber(day), - hour = tonumber(hour), - min = tonumber(min), - sec = tonumber(sec), - }) - - -- The os.time() is based on 1970. Thirty years must be subtracted for calculations from 2000. - epoch_s = epoch_s - THIRTY_YEARS_S - - if epoch_s < MIN_EPOCH_S then - return MIN_EPOCH_S - elseif epoch_s > MAX_EPOCH_S then - return MAX_EPOCH_S - else - return epoch_s - end -end - ---------------------------- --- Set Year Day Schedule -- ---------------------------- -local function handle_set_year_day_schedule(driver, device, command) - -- Get parameters - local cmdName = "setYearDaySchedule" - local scheduleIdx = command.args.scheduleIndex - local userIdx = command.args.userIndex - local localStartTime = command.args.schedule.localStartTime - local localEndTime = command.args.schedule.localEndTime - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockSchedules.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - device:set_field(lock_utils.SCHEDULE_INDEX, scheduleIdx, {persist = true}) - device:set_field(lock_utils.SCHEDULE_LOCAL_START_TIME, localStartTime, {persist = true}) - device:set_field(lock_utils.SCHEDULE_LOCAL_END_TIME, localEndTime, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send( - DoorLock.server.commands.SetYearDaySchedule( - device, ep, - scheduleIdx, - userIdx, - iso8601_to_epoch(localStartTime), - iso8601_to_epoch(localEndTime) - ) - ) -end - ------------------------------------- --- Set Year Day Schedule Response -- ------------------------------------- -local function set_year_day_schedule_handler(driver, device, ib, response) - -- Get result - local cmdName = device:get_field(lock_utils.COMMAND_NAME) - local userIdx = device:get_field(lock_utils.USER_INDEX) - local scheduleIdx = device:get_field(lock_utils.SCHEDULE_INDEX) - local localStartTime = device:get_field(lock_utils.SCHEDULE_LOCAL_START_TIME) - local localEndTime = device:get_field(lock_utils.SCHEDULE_LOCAL_END_TIME) - local status = "success" - if ib.status == DoorLock.types.DlStatus.FAILURE then - status = "failure" - elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then - status = "invalidCommand" - end - - if cmdName == "defaultSchedule" then - local cmdName = "addCredential" - local credIdx = device:get_field(lock_utils.CRED_INDEX) - - if status == "success" then - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - credentialIndex = credIdx, - statusCode = status - } - device:emit_event(capabilities.lockCredentials.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - else - local ep = find_default_endpoint(device, clusters.DoorLock.ID) - device:send(DoorLock.server.commands.ClearUser(device, ep, userIdx)) - end - return - end - - if status == "success" then - if cmdName == "setYearDaySchedule" then - add_year_schedule_to_table(device, userIdx, scheduleIdx, localStartTime, localEndTime) - elseif cmdName == "clearYearDaySchedules" then - delete_year_schedule_from_table(device, userIdx, scheduleIdx) - end - else - device.log.warn(string.format("Failed to set/clear year day schedule: %s", status)) - end - - -- Update commandResult - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - scheduleIndex = scheduleIdx, - statusCode = status - } - device:emit_event(capabilities.lockSchedules.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) -end - ------------------------------ --- Clear Year Day Schedule -- ------------------------------ -local function handle_clear_year_day_schedule(driver, device, command) - -- Get parameters - local cmdName = "clearYearDaySchedules" - local scheduleIdx = command.args.scheduleIndex - local userIdx = command.args.userIndex - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockSchedules.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.SCHEDULE_INDEX, scheduleIdx, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - - -- Send command - -- In SmartThings, Schedule Restrict User is basically allowed to access always. - -- So, if user delete the year day schedule, enter an infinitely long schedule. - local ep = device:component_to_endpoint(command.component) - device:send( - DoorLock.server.commands.SetYearDaySchedule( - device, ep, - scheduleIdx, - userIdx, - MIN_EPOCH_S, - MAX_EPOCH_S - ) - ) -end - ----------------- --- Lock Alarm -- ----------------- -local function alarm_event_handler(driver, device, ib, response) - local DlAlarmCode = DoorLock.types.DlAlarmCode - local alarm_code = ib.data.elements.alarm_code - if alarm_code.value == DlAlarmCode.LOCK_JAMMED then - device:emit_event(capabilities.lockAlarm.alarm.unableToLockTheDoor({state_change = true})) - elseif alarm_code.value == DlAlarmCode.LOCK_FACTORY_RESET then - device:emit_event(capabilities.lockAlarm.alarm.lockFactoryReset({state_change = true})) - elseif alarm_code.value == DlAlarmCode.WRONG_CODE_ENTRY_LIMIT then - device:emit_event(capabilities.lockAlarm.alarm.attemptsExceeded({state_change = true})) - elseif alarm_code.value == DlAlarmCode.FRONT_ESCEUTCHEON_REMOVED then - device:emit_event(capabilities.lockAlarm.alarm.damaged({state_change = true})) - elseif alarm_code.value == DlAlarmCode.DOOR_FORCED_OPEN then - device:emit_event(capabilities.lockAlarm.alarm.forcedOpeningAttempt({state_change = true})) - end -end - --------------------- --- Lock Operation -- --------------------- -local function lock_op_event_handler(driver, device, ib, response) - local opType = ib.data.elements.lock_operation_type - local opSource = ib.data.elements.operation_source - local userIdx = ib.data.elements.user_index - -- TODO: This handler can check fabric index and exclude other fabric events - - if opType == nil or opSource == nil then - return - end - - local Type = DoorLock.types.LockOperationTypeEnum - local Lock = capabilities.lock.lock - if opType.value == Type.LOCK then - opType = Lock.locked - elseif opType.value == Type.UNLOCK then - opType = Lock.unlocked - elseif opType.value == Type.UNLATCH then - opType = Lock.unlatched - else - return - end - - local Source = DoorLock.types.OperationSourceEnum - if opSource.value == Source.UNSPECIFIED then - opSource = nil - elseif opSource.value == Source.MANUAL then - opSource = "manual" - elseif opSource.value == Source.PROPRIETARY_REMOTE then - opSource = "proprietaryRemote" - elseif opSource.value == Source.KEYPAD then - opSource = "keypad" - elseif opSource.value == Source.AUTO then - opSource = "auto" - elseif opSource.value == Source.BUTTON then - opSource = "button" - elseif opSource.value == Source.SCHEDULE then - opSource = nil - elseif opSource.value == Source.REMOTE then - opSource = "command" - elseif opSource.value == Source.RFID then - opSource = "rfid" - elseif opSource.value == Source.BIOMETRIC then - opSource = nil -- It will be updated R2 - elseif opSource.value == Source.ALIRO then - opSource = "digitalKey" - else - opSource = nil - end - - if userIdx ~= nil then - userIdx = userIdx.value - end - - local data_obj = {method = opSource, userIndex = userIdx} - device:emit_event(opType({data = data_obj, state_change = true})) -end - -local function handle_refresh(driver, device, command) - local req = DoorLock.attributes.LockState:read(device) - device:send(req) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) -end - -local function handle_set_reader_config(driver, device, command) - local cmdName = "setReaderConfig" - local signingKey = command.args.signingKey - local verificationKey = command.args.verificationKey - local groupId = command.args.groupId - local groupResolvingKey = nil - local aliro_ble_uwb_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.ALIROBLEUWB}) - if #aliro_ble_uwb_eps > 0 then - groupResolvingKey = command.args.groupResolvingKey - end - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - statusCode = "busy" - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - local reader_config_updated = device:get_field(lock_utils.ALIRO_READER_CONFIG_UPDATED) or nil - if reader_config_updated == "IN_PROGRESS" then - return - elseif reader_config_updated == "TRUE" then - -- Update commandResult - local command_result_info = { - commandName = cmdName, - statusCode = "success" - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.VERIFICATION_KEY, verificationKey, {persist = true}) - device:set_field(lock_utils.GROUP_ID, groupId, {persist = true}) - device:set_field(lock_utils.GROUP_RESOLVING_KEY, groupResolvingKey, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send( - DoorLock.server.commands.SetAliroReaderConfig( - device, ep, - lock_utils.hex_string_to_octet_string(signingKey), - lock_utils.hex_string_to_octet_string(verificationKey), - lock_utils.hex_string_to_octet_string(groupId), - lock_utils.hex_string_to_octet_string(groupResolvingKey) - ) - ) - device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "IN_PROGRESS", {persist = true}) -end - -local function set_aliro_reader_config_handler(driver, device, ib, response) - -- Get result - local cmdName = device:get_field(lock_utils.COMMAND_NAME) - local status = "failure" - if ib.status == DoorLock.types.DlStatus.SUCCESS then - status = "success" - device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, "TRUE", {persist = true}) - else - if ib.status == DoorLock.types.DlStatus.INVALID_FIELD then - status = "invalidCommand" - end - device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, nil, {persist = true}) - end - - -- Update commandResult - local command_result_info = { - commandName = cmdName, - statusCode = status - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) -end - -local function handle_set_card_id(driver, device, command) - if command.args.cardId ~= nil then - device:emit_event(capabilities.lockAliro.cardId(command.args.cardId, {visibility = {displayed = false}})) - end -end - -local function handle_set_issuer_key(driver, device, command) - -- Get parameters - local cmdName = "setIssuerKey" - local userIdx = command.args.userIndex - local userType = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER - local issuerKey = command.args.issuerKey - local reqId = command.args.requestId - local credential = { - credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, - credential_index = INITIAL_CREDENTIAL_INDEX - } - - -- Adjustment - if userIdx == 0 then - userIdx = nil - else - userType = nil - end - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - requestId = reqId, - statusCode = "busy" - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - device:set_field(lock_utils.ISSUER_KEY, issuerKey, {persist = true}) - device:set_field(lock_utils.ISSUER_KEY_INDEX, INITIAL_CREDENTIAL_INDEX, {persist = true}) - device:set_field(lock_utils.COMMAND_REQUEST_ID, reqId, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send( - DoorLock.server.commands.SetCredential( - device, ep, - DoorLock.types.DataOperationTypeEnum.ADD, -- Data Operation Type: Add(0), Modify(2) - credential, -- Credential - lock_utils.hex_string_to_octet_string(issuerKey), -- Credential Data - userIdx, -- User Index - nil, -- User Status - userType -- User Type - ) - ) -end - -local function handle_clear_issuer_key(driver, device, command) - -- Get parameters - local cmdName = "clearIssuerKey" - local userIdx = command.args.userIndex - local reqId = command.args.requestId - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - requestId = reqId, - statusCode = "busy" - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - device:set_field(lock_utils.COMMAND_REQUEST_ID, reqId, {persist = true}) - - -- Get latest aliro table - local aliro_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockAliro.ID, - capabilities.lockAliro.credentials.NAME, - {} - )) - - -- Find issuer key index - for index, entry in pairs(aliro_table) do - if entry.userIndex == userIdx and entry.keyType == "issuerKey" then - -- Set parameters - local credential = { - credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, - credential_index = entry.keyIndex, - } - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send(DoorLock.server.commands.ClearCredential(device, ep, credential)) - break - end - end -end - -local function handle_set_endpoint_key(driver, device, command) - -- Get parameters - local cmdName = "setEndpointKey" - local userIdx = command.args.userIndex - local userType = DoorLock.types.UserTypeEnum.UNRESTRICTED_USER - local keyId = command.args.keyId - local keyType = command.args.keyType - local endpointKey = command.args.endpointKey - local reqId = command.args.requestId - local dataOpType = DoorLock.types.DataOperationTypeEnum.ADD -- Data Operation Type: Add(0), Modify(2) - local endpointKeyIndex = INITIAL_CREDENTIAL_INDEX - - -- Min user index of commandResult is 1 - -- 0 should convert to nil before busy check - if userIdx == 0 then - userIdx = nil - end - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - keyId = keyId, - requestId = reqId, - statusCode = "busy" - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Adjustment - if userIdx ~= nil then - userType = nil - - -- Get latest aliro table - local aliro_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockAliro.ID, - capabilities.lockAliro.credentials.NAME, - {} - )) - - -- Find existing endpoint key - for index, entry in pairs(aliro_table) do - if (entry.keyType == "evictableEndpointKey" or entry.keyType == "nonEvictableEndpointKey") and entry.keyId == keyId then - dataOpType = DoorLock.types.DataOperationTypeEnum.MODIFY - endpointKeyIndex = entry.keyIndex - delete_aliro_from_table(device, userIdx, keyType, keyId) - break - end - end - end - - local credential = { - credential_type = ALIRO_KEY_TYPE_TO_CRED_ENUM_MAP[keyType], - credential_index = endpointKeyIndex - } - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - device:set_field(lock_utils.DEVICE_KEY_ID, keyId, {persist = true}) - device:set_field(lock_utils.ENDPOINT_KEY_TYPE, keyType, {persist = true}) - device:set_field(lock_utils.ENDPOINT_KEY, endpointKey, {persist = true}) - device:set_field(lock_utils.ENDPOINT_KEY_INDEX, endpointKeyIndex, {persist = true}) - device:set_field(lock_utils.COMMAND_REQUEST_ID, reqId, {persist = true}) - - -- Send command - local ep = device:component_to_endpoint(command.component) - device:send( - DoorLock.server.commands.SetCredential( - device, ep, - dataOpType, -- Data Operation Type: Add(0), Modify(2) - credential, -- Credential - lock_utils.hex_string_to_octet_string(endpointKey), -- Credential Data - userIdx, -- User Index - nil, -- User Status - userType -- User Type - ) - ) -end - -local function handle_clear_endpoint_key(driver, device, command) - -- Get parameters - local cmdName = "clearEndpointKey" - local userIdx = command.args.userIndex - local keyId = command.args.keyId - local keyType = command.args.keyType - local reqId = command.args.requestId - - -- Check busy state - if lock_utils.is_busy_state_set(device) then - local command_result_info = { - commandName = cmdName, - userIndex = userIdx, - keyId = keyId, - requestId = reqId, - statusCode = "busy" - } - device:emit_event(capabilities.lockAliro.commandResult( - command_result_info, {state_change = true, visibility = {displayed = false}} - )) - return - end - - -- Save values to field - device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true}) - device:set_field(lock_utils.USER_INDEX, userIdx, {persist = true}) - device:set_field(lock_utils.DEVICE_KEY_ID, keyId, {persist = true}) - device:set_field(lock_utils.ENDPOINT_KEY_TYPE, keyType, {persist = true}) - device:set_field(lock_utils.COMMAND_REQUEST_ID, reqId, {persist = true}) - - -- Get latest aliro table - local aliro_table = utils.deep_copy(device:get_latest_state( - "main", - capabilities.lockAliro.ID, - capabilities.lockAliro.credentials.NAME, - {} - )) - - local ep = device:component_to_endpoint(command.component) - if keyId == nil then - return - else - -- Find aliro credential - for index, entry in pairs(aliro_table) do - if entry.userIndex == userIdx and entry.keyId == keyId and entry.keyType == keyType then - -- Set parameters - local credential = { - credential_type = ALIRO_KEY_TYPE_TO_CRED_ENUM_MAP[keyType], - credential_index = entry.keyIndex, - } - -- Send command - device:send(DoorLock.server.commands.ClearCredential(device, ep, credential)) - break - end - end - end -end - -local new_matter_lock_handler = { - NAME = "New Matter Lock Handler", - lifecycle_handlers = { - init = device_init, - added = device_added, - doConfigure = do_configure, - infoChanged = info_changed, - driverSwitched = driver_switched - }, - matter_handlers = { - attr = { - [DoorLock.ID] = { - [DoorLock.attributes.LockState.ID] = lock_state_handler, - [DoorLock.attributes.OperatingMode.ID] = operating_modes_handler, - [DoorLock.attributes.NumberOfTotalUsersSupported.ID] = total_users_supported_handler, - [DoorLock.attributes.NumberOfPINUsersSupported.ID] = pin_users_supported_handler, - [DoorLock.attributes.MinPINCodeLength.ID] = min_pin_code_len_handler, - [DoorLock.attributes.MaxPINCodeLength.ID] = max_pin_code_len_handler, - [DoorLock.attributes.RequirePINforRemoteOperation.ID] = require_remote_pin_handler, - [DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser.ID] = max_week_schedule_of_user_handler, - [DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser.ID] = max_year_schedule_of_user_handler, - [DoorLock.attributes.AliroReaderVerificationKey.ID] = aliro_reader_verification_key_handler, - [DoorLock.attributes.AliroReaderGroupIdentifier.ID] = aliro_reader_group_id_handler, - [DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions.ID] = aliro_protocol_versions_handler, - [DoorLock.attributes.AliroGroupResolvingKey.ID] = aliro_group_resolving_key_handler, - [DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions.ID] = aliro_supported_ble_uwb_protocol_versions_handler, - [DoorLock.attributes.AliroBLEAdvertisingVersion.ID] = aliro_ble_advertising_version_handler, - [DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported.ID] = max_aliro_credential_issuer_key_handler, - [DoorLock.attributes.NumberOfAliroEndpointKeysSupported.ID] = max_aliro_endpoint_key_handler, - [DoorLockFeatureMapAttr.ID] = door_lock_feature_map_handler, - }, - [PowerSource.ID] = { - [PowerSource.attributes.AttributeList.ID] = handle_power_source_attribute_list, - [PowerSource.attributes.BatPercentRemaining.ID] = handle_battery_percent_remaining, - [PowerSource.attributes.BatChargeLevel.ID] = handle_battery_charge_level, - } - }, - event = { - [DoorLock.ID] = { - [DoorLock.events.DoorLockAlarm.ID] = alarm_event_handler, - [DoorLock.events.LockOperation.ID] = lock_op_event_handler, - }, - }, - cmd_response = { - [DoorLock.ID] = { - [DoorLock.server.commands.SetUser.ID] = set_user_response_handler, - [DoorLock.client.commands.GetUserResponse.ID] = get_user_response_handler, - [DoorLock.server.commands.ClearUser.ID] = clear_user_response_handler, - [DoorLock.client.commands.SetCredentialResponse.ID] = set_credential_response_handler, - [DoorLock.server.commands.ClearCredential.ID] = clear_credential_response_handler, - [DoorLock.server.commands.SetWeekDaySchedule.ID] = set_week_day_schedule_handler, - [DoorLock.server.commands.ClearWeekDaySchedule.ID] = clear_week_day_schedule_handler, - [DoorLock.server.commands.SetYearDaySchedule.ID] = set_year_day_schedule_handler, - [DoorLock.server.commands.SetAliroReaderConfig.ID] = set_aliro_reader_config_handler, - }, - }, - }, - subscribed_attributes = subscribed_attributes, - subscribed_events = subscribed_events, - capability_handlers = { - [capabilities.lock.ID] = { - [capabilities.lock.commands.lock.NAME] = handle_lock, - [capabilities.lock.commands.unlock.NAME] = handle_unlock, - [capabilities.lock.commands.unlatch.NAME] = handle_unlatch - }, - [capabilities.lockUsers.ID] = { - [capabilities.lockUsers.commands.addUser.NAME] = handle_add_user, - [capabilities.lockUsers.commands.updateUser.NAME] = handle_update_user, - [capabilities.lockUsers.commands.deleteUser.NAME] = handle_delete_user, - [capabilities.lockUsers.commands.deleteAllUsers.NAME] = handle_delete_all_users, - }, - [capabilities.lockCredentials.ID] = { - [capabilities.lockCredentials.commands.addCredential.NAME] = handle_add_credential, - [capabilities.lockCredentials.commands.updateCredential.NAME] = handle_update_credential, - [capabilities.lockCredentials.commands.deleteCredential.NAME] = handle_delete_credential, - [capabilities.lockCredentials.commands.deleteAllCredentials.NAME] = handle_delete_all_credentials, - }, - [capabilities.lockSchedules.ID] = { - [capabilities.lockSchedules.commands.setWeekDaySchedule.NAME] = handle_set_week_day_schedule, - [capabilities.lockSchedules.commands.clearWeekDaySchedules.NAME] = handle_clear_week_day_schedule, - [capabilities.lockSchedules.commands.setYearDaySchedule.NAME] = handle_set_year_day_schedule, - [capabilities.lockSchedules.commands.clearYearDaySchedules.NAME] = handle_clear_year_day_schedule, - }, - [capabilities.lockAliro.ID] = { - [capabilities.lockAliro.commands.setReaderConfig.NAME] = handle_set_reader_config, - [capabilities.lockAliro.commands.setCardId.NAME] = handle_set_card_id, - [capabilities.lockAliro.commands.setIssuerKey.NAME] = handle_set_issuer_key, - [capabilities.lockAliro.commands.clearIssuerKey.NAME] = handle_clear_issuer_key, - [capabilities.lockAliro.commands.setEndpointKey.NAME] = handle_set_endpoint_key, - [capabilities.lockAliro.commands.clearEndpointKey.NAME] = handle_clear_endpoint_key, - }, - [capabilities.refresh.ID] = {[capabilities.refresh.commands.refresh.NAME] = handle_refresh} - }, - supported_capabilities = { - capabilities.lock, - capabilities.lockUsers, - capabilities.lockCredentials, - capabilities.lockSchedules, - capabilities.battery, - capabilities.batteryLevel - }, - can_handle = require("new-matter-lock.can_handle"), -} - -return new_matter_lock_handler diff --git a/drivers/SmartThings/matter-lock/src/sub_drivers.lua b/drivers/SmartThings/matter-lock/src/sub_drivers.lua index 0f5d1f76d2..563c428945 100644 --- a/drivers/SmartThings/matter-lock/src/sub_drivers.lua +++ b/drivers/SmartThings/matter-lock/src/sub_drivers.lua @@ -3,6 +3,6 @@ local lazy_load_if_possible = require "lazy_load_subdriver" local sub_drivers = { - lazy_load_if_possible("new-matter-lock"), + lazy_load_if_possible("unmigrated-matter-lock"), } return sub_drivers diff --git a/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua index bb7454d27d..2f3566eb6c 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua @@ -70,17 +70,15 @@ local function test_init() test.socket.matter:__expect_send({mock_device.id, req}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) local subscribe_request = clusters.DoorLock.attributes.LockState:subscribe(mock_device) - subscribe_request:merge(clusters.DoorLock.events.DoorLockAlarm:subscribe(mock_device)) subscribe_request:merge(clusters.DoorLock.events.LockOperation:subscribe(mock_device)) - subscribe_request:merge(clusters.DoorLock.events.LockUserChange:subscribe(mock_device)) test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) - test.mock_device.add_test_device(mock_device_level) test.socket.device_lifecycle:__queue_receive({ mock_device_level.id, "added" }) test.socket.device_lifecycle:__queue_receive({ mock_device_level.id, "init" }) local subscribe_request_level = clusters.DoorLock.attributes.LockState:subscribe(mock_device_level) + subscribe_request_level:merge(clusters.DoorLock.events.LockOperation:subscribe(mock_device_level)) test.socket["matter"]:__expect_send({mock_device_level.id, subscribe_request_level}) end test.set_test_init_function(test_init) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua index 9693289951..58185f2ec8 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua @@ -42,7 +42,6 @@ local function test_init() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) local subscribe_request = clusters.DoorLock.attributes.LockState:subscribe(mock_device) subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device)) - subscribe_request:merge(clusters.DoorLock.events.DoorLockAlarm:subscribe(mock_device)) subscribe_request:merge(clusters.DoorLock.events.LockOperation:subscribe(mock_device)) test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) @@ -384,10 +383,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) ) -end, -{ - min_api_version = 17 -} + end, + { + min_api_version = 17 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua index 18bf34d3b9..e43e075baf 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua @@ -67,9 +67,7 @@ local function test_init() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) local subscribe_request = clusters.DoorLock.attributes.LockState:subscribe(mock_device) subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device)) - subscribe_request:merge(clusters.DoorLock.events.DoorLockAlarm:subscribe(mock_device)) subscribe_request:merge(clusters.DoorLock.events.LockOperation:subscribe(mock_device)) - subscribe_request:merge(clusters.DoorLock.events.LockUserChange:subscribe(mock_device)) 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" }) @@ -88,9 +86,7 @@ local function test_init_no_battery() test.socket.device_lifecycle:__queue_receive({ mock_device_no_battery.id, "init" }) local subscribe_request = clusters.DoorLock.attributes.LockState:subscribe(mock_device_no_battery) subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device_no_battery)) - subscribe_request:merge(clusters.DoorLock.events.DoorLockAlarm:subscribe(mock_device_no_battery)) subscribe_request:merge(clusters.DoorLock.events.LockOperation:subscribe(mock_device_no_battery)) - subscribe_request:merge(clusters.DoorLock.events.LockUserChange:subscribe(mock_device_no_battery)) test.socket["matter"]:__expect_send({mock_device_no_battery.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_no_battery.id, "doConfigure" }) mock_device_no_battery:expect_metadata_update({ profile = "base-lock-nobattery" }) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua index 8bb78f3fce..f1b3c2f182 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua @@ -39,6 +39,7 @@ local function test_init() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) local subscribe_request = clusters.DoorLock.attributes.LockState:subscribe(mock_device) subscribe_request:merge(clusters.PowerSource.attributes.BatChargeLevel:subscribe(mock_device)) + subscribe_request:merge(clusters.DoorLock.events.LockOperation:subscribe(mock_device)) 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" }) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua index a0a603c6b3..c2e86aae29 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua @@ -45,9 +45,7 @@ local function test_init() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device)) - subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) - subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) 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 = "base-lock-nobattery" }) @@ -910,4 +908,70 @@ test.register_coroutine_test( } ) +local enabled_optional_component_capability_pairs = {{ + "main", + { + capabilities.lockUsers.ID, + capabilities.lockCredentials.ID, + capabilities.battery.ID, + } +}} + +test.register_coroutine_test( + "Lock codes stored during migration", + function() + init_code_slot(1, "Code 1", mock_device) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.lockCodes.lockCodes( + json.encode({["1"] = "Code 1"}), {visibility = {displayed = false}} + ) + ) + ) + test.wait_for_events() + init_code_slot(2, "Code 2", mock_device) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.lockCodes.lockCodes( + json.encode({["1"] = "Code 1", ["2"] = "Code 2"}), {visibility = {displayed = false}} + ) + ) + ) + test.wait_for_events() + init_code_slot(3, "Code 3", mock_device) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.lockCodes.lockCodes( + json.encode({["1"] = "Code 1", ["2"] = "Code 2", ["3"] = "Code 3"}), + {visibility = {displayed = false}} + ) + ) + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockCodes.ID, + command = "migrate", + args = {} + }, + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) + ) + mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = enabled_optional_component_capability_pairs }) + + end, + { + min_api_version = 17 + } +) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua index 72d905d1db..39066776f9 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua @@ -44,9 +44,7 @@ local function test_init() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device)) - subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) - subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) test.socket.matter:__expect_send({mock_device.id, DoorLock.attributes.RequirePINforRemoteOperation:read(mock_device, 10)}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_migration.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_migration.lua new file mode 100644 index 0000000000..b94b31e105 --- /dev/null +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_migration.lua @@ -0,0 +1,143 @@ +-- Copyright 2026 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 DoorLock = clusters.DoorLock +local lock_utils = require "lock_utils" + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + manufacturer_info = { + vendor_id = 0x135D, + product_id = 0x00C1, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x0101, -- PIN & USR + } + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + +local function test_init() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) + -- mock_device:set_field(lock_utils.LOCK_CODES_FOR_MIGRATION, { {1, "ST Remote Operation Code"}, {2, "Guest1"}, {3, "Guest2"} }, {persist=true}) +end + +test.set_test_init_function(test_init) + +local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) +subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.NumberOfPINUsersSupported:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.MaxPINCodeLength:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.MinPINCodeLength:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device)) +subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) +subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) +subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) + +local enabled_optional_component_capability_pairs = {{ + "main", + { + capabilities.lockUsers.ID, + capabilities.lockCredentials.ID, + capabilities.battery.ID, + } +}} + +test.register_coroutine_test( + "Migration completed and User and Credential values restored", + function() + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + {capability = capabilities.lockCodes.ID, command = "migrate", args = {}} + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true)) + ) + -- at this point, refresh is injected in migration. + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) + ) + mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = enabled_optional_component_capability_pairs }) + + + test.wait_for_events() + -- assume this was set prior + mock_device:set_field(lock_utils.LOCK_CODES_FOR_MIGRATION, { {1, "ST Remote Operation Code"}, {2, "Guest1"}, {3, "Guest2"} }, {persist=true}) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed( + {profile = {id = "00000000-1111-2222-3333-000000000010", components = { main = {capabilities={ + ["lock"]= {id="lock", version=1}, ["lockAlarm"] = {id="lockAlarm", version=1}, ["remoteControlStatus"] = {id="remoteControlStatus", version=1}, + ["lockUsers"] = {id="lockUsers", version=1}, ["lockCredentials"] = {id="lockCredentials", version=1}, ["firmwareUpdate"] = {id="firmwareUpdate", version=1}, + ["refresh"] = {id="refresh", version=1}}}}}} + )) + test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users( + { + {userIndex=1, userName="ST Remote Operation Code", userType="guest"}, + {userIndex=2, userName="Guest1", userType="guest"}, + {userIndex=3, userName="Guest2", userType="guest"} + }, + {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials( + { + {credentialIndex=1, credentialType="pin", userIndex=1}, + {credentialIndex=2, credentialType="pin", userIndex=2}, + {credentialIndex=3, credentialType="pin", userIndex=3} + }, + {visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index 4b875fd1bc..745e769f9e 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -625,7 +625,7 @@ test.register_coroutine_test( local mock_nuki_smart_lock_ultra = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("lock-nocodes-notamper.yml"), + profile = t_utils.get_profile_definition("lock-unlatch.yml"), manufacturer_info = { vendor_id = 0x135D, product_id = 0x00A1, diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 492a3c2805..35acbfbff6 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -1,7 +1,7 @@ -- Copyright 2023 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 - +-- These are test units designed to validate User, Credential, and Schedule features. local test = require "integration_test" test.set_rpc_version(0) local capabilities = require "st.capabilities" @@ -13,7 +13,7 @@ local types = DoorLock.types local lock_utils = require "lock_utils" local mock_device = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("lock-user-pin-schedule.yml"), + profile = t_utils.get_profile_definition("lock.yml"), manufacturer_info = { vendor_id = 0x115f, product_id = 0x2802, @@ -48,6 +48,12 @@ local mock_device = test.mock_device.build_test_matter_device({ local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local function test_init() test.disable_startup_messages() + -- device_added + test.mock_device.add_test_device(mock_device) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) -- subscribe request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) @@ -62,27 +68,23 @@ local function test_init() subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) - -- add test device, handle initial subscribe - test.mock_device.add_test_device(mock_device) test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) - -- actual onboarding flow - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) - ) + -- device_init test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) - test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) + -- do_configure test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) ) mock_device:expect_metadata_update({ profile = "lock-user-pin-schedule" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + -- info_changed + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ profile = t_utils.get_profile_definition("lock-user-pin-schedule.yml")})) + test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) end test.set_test_init_function(test_init) - test.register_coroutine_test( "Handle driverSwitched event", function() @@ -977,23 +979,6 @@ test.register_message_test( } ) -test.register_coroutine_test( - "Added lifecycle event lock nocodes nobattery", - function() - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockAlarm.alarm.clear({state_change = true}) - ) - ) - end, - { - min_api_version = 17 - } -) - test.register_coroutine_test( "Handle Add User command received from SmartThings.", function() @@ -2208,6 +2193,187 @@ test.register_coroutine_test( } ) +local LOCAL_START_TIME = "2025-01-15T09:00:00" +local LOCAL_END_TIME = "2025-01-15T17:00:00" + +test.register_coroutine_test( + "Handle Add Year Day Schedule command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockSchedules.ID, + command = "setYearDaySchedule", + args = {1, 1, {localStartTime=LOCAL_START_TIME, localEndTime=LOCAL_END_TIME}} + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetYearDaySchedule( + mock_device, 1, -- endpoint + 1, -- Schedule Index + 1, -- User Index + lock_utils.iso8601_to_epoch(LOCAL_START_TIME), -- Start Time (epoch) + lock_utils.iso8601_to_epoch(LOCAL_END_TIME) -- End Time (epoch) + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.SetYearDaySchedule:build_test_command_response( + mock_device, 1 + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.yearDaySchedules( + {{ + userIndex=1, + schedules={{ + scheduleIndex = 1, + localStartTime = LOCAL_START_TIME, + localEndTime = LOCAL_END_TIME + }}, + }}, + {visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.commandResult( + {commandName="setYearDaySchedule", userIndex=1, scheduleIndex=1, statusCode="success"}, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Add Year Day Schedule command received from SmartThings and send busy state", + function() + mock_device:set_field(lock_utils.BUSY_STATE, os.time(), {persist = true}) + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockSchedules.ID, + command = "setYearDaySchedule", + args = {1, 1, {localStartTime=LOCAL_START_TIME, localEndTime=LOCAL_END_TIME}} + }, + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.commandResult( + {commandName="setYearDaySchedule", statusCode="busy"}, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Handle Clear Year Day Schedule command received from SmartThings.", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockSchedules.ID, + command = "clearYearDaySchedules", + args = { + 1, -- user index + 1, -- schedule index + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetYearDaySchedule( + mock_device, 1, -- endpoint + 1, -- year_day_index + 1, -- user_index + 0, -- local_start_time + lock_utils.MAX_EPOCH_S -- local_end_time + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.server.commands.SetYearDaySchedule:build_test_command_response( + mock_device, 1 + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.commandResult( + {commandName="clearYearDaySchedules", userIndex=1, scheduleIndex=1, statusCode="success"}, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Clear Year Day Schedule command received from SmartThings and send busy state", + function() + mock_device:set_field(lock_utils.BUSY_STATE, os.time(), {persist = true}) + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockSchedules.ID, + command = "clearYearDaySchedules", + args = { + 1, -- user index + 1, -- schedule index + } + }, + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockSchedules.commandResult( + {commandName="clearYearDaySchedules", statusCode="busy"}, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + test.register_coroutine_test( "Add Guest User and failure response ", function() @@ -2447,5 +2613,4 @@ test.register_coroutine_test( } ) - test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua index 166fa7110a..250fcb8af5 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua @@ -12,6 +12,30 @@ -- See the License for the specific language governing permissions and -- limitations under the License. +local FIXTURE_DER_B64 = + "MHcCAQEEIHlEZiE0cRiQ+Jp+RAGQ/Rq8miEBQXfRQNeSlyNR0Cv1oAoGCCqGSM49AwEH" .. + "oUQDQgAEeBuCTahXpt/rGLvVnOxjTlbmNYdKQF3vlHZYMK/LtISNUXJsJ1BfBX9nGgwY" .. + "WlJ775K5woV3zzMW7X4dVV5C7Q==" +local EXPECTED_PRIV_HEX = "7944662134711890f89a7e440190fd1abc9a21014177d140d792972351d02bf5" +local EXPECTED_PUB_HEX = "04781b824da857a6dfeb18bbd59cec634e56e635874a405def94765830afcbb48" .. + "48d51726c27505f057f671a0c185a527bef92b9c28577cf3316ed7e1d555e42ed" +local EXPECTED_GROUP_ID_HEX = "64c8cce93255c4478d7aa05d83f3eaa2" + +local base64 = require "base64" +local function default_generate_self_signed_cert(_opts) + local der = base64.decode(FIXTURE_DER_B64) + return { + cert_pem = "-----BEGIN CERTIFICATE-----\nfake\n-----END CERTIFICATE-----", + key_pem = "-----BEGIN EC PRIVATE KEY-----\nfake\n-----END EC PRIVATE KEY-----", + cert_der = der, + key_der = der, + } +end +local security_stub = { + generate_self_signed_cert = default_generate_self_signed_cert, +} +package.loaded["st.security"] = security_stub + local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" @@ -19,6 +43,14 @@ local clusters = require "st.matter.clusters" local cluster_base = require "st.matter.cluster_base" local DoorLock = clusters.DoorLock local OctetString1 = require "st.matter.data_types.OctetString1" +local lock_utils = require "lock_utils" +lock_utils.create_group_id_resolving_key = function() + return EXPECTED_GROUP_ID_HEX +end + +local key_id = "vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=" +local endpoint_key = "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809" +local request_id = "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2" local enabled_optional_component_capability_pairs = {{ "main", @@ -65,6 +97,25 @@ local mock_device = test.mock_device.build_test_matter_device({ }) local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} +local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) +subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.AliroReaderVerificationKey:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.AliroReaderGroupIdentifier:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.AliroReaderGroupSubIdentifier:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.AliroGroupResolvingKey:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.AliroBLEAdvertisingVersion:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.NumberOfAliroEndpointKeysSupported:subscribe(mock_device)) +subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) +subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) +subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) +subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) + local function test_init() test.disable_startup_messages() test.mock_device.add_test_device(mock_device) @@ -73,26 +124,7 @@ local function test_init() mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) - local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) - subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroReaderVerificationKey:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroReaderGroupIdentifier:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroReaderGroupSubIdentifier:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroExpeditedTransactionSupportedProtocolVersions:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroGroupResolvingKey:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroSupportedBLEUWBProtocolVersions:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.AliroBLEAdvertisingVersion:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported:subscribe(mock_device)) - subscribe_request:merge(DoorLock.attributes.NumberOfAliroEndpointKeysSupported:subscribe(mock_device)) - subscribe_request:merge(cluster_base.subscribe(mock_device, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) - subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) - subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) - subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) @@ -113,17 +145,15 @@ test.register_coroutine_test( mock_device.id, DoorLock.attributes.AliroReaderVerificationKey:build_test_report_data( mock_device, 1, - "\x04\xA9\xCB\xE4\x18\xEB\x09\x66\x16\x43\xE2\xA4\xA8\x46\xB8\xED\xFE\x27\x86\x98\x30\x2E\x9F\xB4\x3E\x9B\xFF\xD3\xE3\x10\xCC\x2C\x2C\x7F\xF4\x02\xE0\x6E\x40\xEA\x3C\xE1\x29\x43\x52\x73\x36\x68\x3F\xC5\xB1\xCB\x0C\x6A\x7C\x3F\x0B\x5A\xFF\x78\x35\xDF\x21\xC6\x24" + lock_utils.hex_string_to_octet_string(EXPECTED_PUB_HEX) ), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( "main", - capabilities.lockAliro.readerVerificationKey( - "04a9cbe418eb09661643e2a4a846b8edfe278698302e9fb43e9bffd3e310cc2c2c7ff402e06e40ea3ce12943527336683fc5b1cb0c6a7c3f0b5aff7835df21c624", - {visibility = {displayed = false}}) - ) + capabilities.lockAliro.readerVerificationKey(EXPECTED_PUB_HEX, {visibility = {displayed = false}}) + ) ) end ) @@ -136,17 +166,15 @@ test.register_coroutine_test( mock_device.id, DoorLock.attributes.AliroReaderGroupIdentifier:build_test_report_data( mock_device, 1, - "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8" + lock_utils.hex_string_to_octet_string(EXPECTED_GROUP_ID_HEX) ), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( "main", - capabilities.lockAliro.readerGroupIdentifier( - "e24f1b205ba923b32cd13dc009e993a8", - {visibility = {displayed = false}}) - ) + capabilities.lockAliro.readerGroupIdentifier(EXPECTED_GROUP_ID_HEX, {visibility = {displayed = false}}) + ) ) end ) @@ -228,17 +256,15 @@ test.register_coroutine_test( mock_device.id, DoorLock.attributes.AliroGroupResolvingKey:build_test_report_data( mock_device, 1, - "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8" + lock_utils.hex_string_to_octet_string(EXPECTED_GROUP_ID_HEX) ), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( "main", - capabilities.lockAliro.groupResolvingKey( - "e24f1b205ba923b32cd13dc009e993a8", - {visibility = {displayed = false}}) - ) + capabilities.lockAliro.groupResolvingKey(EXPECTED_GROUP_ID_HEX, {visibility = {displayed = false}}) + ) ) end ) @@ -321,9 +347,9 @@ test.register_coroutine_test( capability = capabilities.lockAliro.ID, command = "setReaderConfig", args = { - "1a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac", - "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", - "e24f1b205ba923b32cd13dc009e993a8", + EXPECTED_PRIV_HEX, + EXPECTED_PUB_HEX, + EXPECTED_GROUP_ID_HEX, nil } }, @@ -334,9 +360,9 @@ test.register_coroutine_test( mock_device.id, DoorLock.server.commands.SetAliroReaderConfig( mock_device, 1, -- endpoint - "\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC", - "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", - "\xE2\x4F\x1B\x20\x5B\xA9\x23\xB3\x2C\xD1\x3D\xC0\x09\xE9\x93\xA8", + lock_utils.hex_string_to_octet_string(EXPECTED_PRIV_HEX), + lock_utils.hex_string_to_octet_string(EXPECTED_PUB_HEX), + lock_utils.hex_string_to_octet_string(EXPECTED_GROUP_ID_HEX), nil ), } @@ -373,11 +399,11 @@ test.register_coroutine_test( capability = capabilities.lockAliro.ID, command = "setEndpointKey", args = { - 0, - "vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + 0, -- user index + key_id, "nonEvictableEndpointKey", - "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", - "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2" + endpoint_key, + request_id } }, } @@ -394,7 +420,7 @@ test.register_coroutine_test( credential_index = 1 } ), -- credential - "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", -- credential_data + lock_utils.hex_string_to_octet_string(endpoint_key), -- credential_data nil, -- user_index nil, -- user_status DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type @@ -427,7 +453,7 @@ test.register_coroutine_test( "main", capabilities.lockAliro.credentials( {{ - keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + keyId=key_id, keyIndex=1, keyType="nonEvictableEndpointKey", userIndex=1 @@ -442,8 +468,8 @@ test.register_coroutine_test( capabilities.lockAliro.commandResult( { commandName="setEndpointKey", - keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", - requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", + keyId=key_id, + requestId=request_id, statusCode="success", userIndex=1 }, @@ -458,7 +484,7 @@ test.register_coroutine_test( { capability = capabilities.lockAliro.ID, command = "clearEndpointKey", - args = {1, "vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", "nonEvictableEndpointKey"} + args = {1, key_id, "nonEvictableEndpointKey"} }, } ) @@ -512,7 +538,7 @@ test.register_coroutine_test( capabilities.lockAliro.commandResult( { commandName="clearEndpointKey", - keyId="vTNt0oPoHvIvwGMHa3AuXE3ZcY+Oocv5KZ+R0yveEag=", + keyId=key_id, statusCode="success", userIndex=1 }, @@ -523,6 +549,261 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Handle Set Endpoint Key command received from SmartThings and busy status", + function() + lock_utils.is_busy_state_set(mock_device) + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setEndpointKey", + args = { + 0, -- user index + key_id, + "nonEvictableEndpointKey", + endpoint_key, + request_id + } + }, + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setEndpointKey", + keyId=key_id, + requestId=request_id, + statusCode="busy" + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Handle Set Endpoint Key command received from SmartThings and user_index is occupied", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setEndpointKey", + args = { + 0, -- user index + key_id, + "nonEvictableEndpointKey", + endpoint_key, + request_id + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY, + credential_index = 1 + } + ), -- credential + lock_utils.hex_string_to_octet_string(endpoint_key), -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.OCCUPIED, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY, + credential_index = 2 + } + ), -- credential + lock_utils.hex_string_to_octet_string(endpoint_key), -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Handle Set Endpoint Key command received from SmartThings and user_index is occupied and next_credential_index is nil", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setEndpointKey", + args = { + 0, -- user index + key_id, + "nonEvictableEndpointKey", + endpoint_key, + request_id + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY, + credential_index = 1 + } + ), -- credential + lock_utils.hex_string_to_octet_string(endpoint_key), -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.OCCUPIED, -- status + 1, -- user_index + nil -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setEndpointKey", + keyId=key_id, + requestId=request_id, + statusCode="resourceExhausted" + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Handle Set Endpoint Key command received from SmartThings and user_index is failure", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setEndpointKey", + args = { + 0, -- user index + key_id, + "nonEvictableEndpointKey", + endpoint_key, + request_id + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_NON_EVICTABLE_ENDPOINT_KEY, + credential_index = 1 + } + ), -- credential + lock_utils.hex_string_to_octet_string(endpoint_key), -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.FAILURE, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setEndpointKey", + keyId=key_id, + requestId=request_id, + statusCode="failure" + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + test.register_coroutine_test( "Handle Set Issuer Key command and Clear Issuer Key command received from SmartThings.", function() @@ -534,8 +815,8 @@ test.register_coroutine_test( command = "setIssuerKey", args = { 0, - "041a748a78566aaee985d9141730fa72bd83bf34e7b93072a0ca7b56a79b6debac9493eded05a65701b5148517bd49a6c91c78ed6811543491eff1d257280ed809", - "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2" + endpoint_key, + request_id } }, } @@ -552,7 +833,7 @@ test.register_coroutine_test( credential_index = 1 } ), -- credential - "\x04\x1A\x74\x8A\x78\x56\x6A\xAE\xE9\x85\xD9\x14\x17\x30\xFA\x72\xBD\x83\xBF\x34\xE7\xB9\x30\x72\xA0\xCA\x7B\x56\xA7\x9B\x6D\xEB\xAC\x94\x93\xED\xED\x05\xA6\x57\x01\xB5\x14\x85\x17\xBD\x49\xA6\xC9\x1C\x78\xED\x68\x11\x54\x34\x91\xEF\xF1\xD2\x57\x28\x0E\xD8\x09", -- credential_data + lock_utils.hex_string_to_octet_string(endpoint_key), -- credential_data nil, -- user_index nil, -- user_status DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type @@ -599,7 +880,7 @@ test.register_coroutine_test( capabilities.lockAliro.commandResult( { commandName="setIssuerKey", - requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", + requestId=request_id, statusCode="success", userIndex=1 }, @@ -614,7 +895,7 @@ test.register_coroutine_test( { capability = capabilities.lockAliro.ID, command = "clearIssuerKey", - args = {1, "1f3acdf6-8930-45f7-ae3d-f0b47851c3e2"} + args = {1, request_id} }, } ) @@ -668,7 +949,7 @@ test.register_coroutine_test( capabilities.lockAliro.commandResult( { commandName="clearIssuerKey", - requestId="1f3acdf6-8930-45f7-ae3d-f0b47851c3e2", + requestId=request_id, statusCode="success", userIndex=1 }, @@ -679,4 +960,304 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Handle Set Issuer Key command received from SmartThings and busy status", + function() + lock_utils.is_busy_state_set(mock_device) + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setIssuerKey", + args = { + 0, + endpoint_key, + request_id + } + }, + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setIssuerKey", + requestId=request_id, + statusCode="busy" + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Handle Set Issuer Key command received from SmartThings and user_index is occupied", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setIssuerKey", + args = { + 0, + endpoint_key, + request_id + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, + credential_index = 1 + } + ), -- credential + lock_utils.hex_string_to_octet_string(endpoint_key), -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.OCCUPIED, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, + credential_index = 2 + } + ), -- credential + lock_utils.hex_string_to_octet_string(endpoint_key), -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Handle Set Issuer Key command received from SmartThings and user_index is occupied and next_credential_index is nil", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setIssuerKey", + args = { + 0, + endpoint_key, + request_id + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, + credential_index = 1 + } + ), -- credential + lock_utils.hex_string_to_octet_string(endpoint_key), -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.OCCUPIED, -- status + 1, -- user_index + nil -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setIssuerKey", + requestId=request_id, + statusCode="resourceExhausted" + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Handle Set Issuer Key command received from SmartThings and user_index is failure", + function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.lockAliro.ID, + command = "setIssuerKey", + args = { + 0, + endpoint_key, + request_id + } + }, + } + ) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetCredential( + mock_device, 1, -- endpoint + DoorLock.types.DataOperationTypeEnum.ADD, -- operation_type + DoorLock.types.CredentialStruct( + { + credential_type = DoorLock.types.CredentialTypeEnum.ALIRO_CREDENTIAL_ISSUER_KEY, + credential_index = 1 + } + ), -- credential + lock_utils.hex_string_to_octet_string(endpoint_key), -- credential_data + nil, -- user_index + nil, -- user_status + DoorLock.types.DlUserType.UNRESTRICTED_USER -- user_type + ), + } + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.SetCredentialResponse:build_test_command_response( + mock_device, 1, + DoorLock.types.DlStatus.FAILURE, -- status + 1, -- user_index + 2 -- next_credential_index + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setIssuerKey", + requestId=request_id, + statusCode="failure" + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "set_reader_config should send SetAliroReaderConfig command on device init", + function() + mock_device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, nil, {persist = true}) + mock_device:set_field(lock_utils.BUSY_STATE, false, {persist = true}) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ profile = t_utils.get_profile_definition("lock.yml")})) + test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) + test.socket.matter:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetAliroReaderConfig( + mock_device, 1, + lock_utils.hex_string_to_octet_string(EXPECTED_PRIV_HEX), + lock_utils.hex_string_to_octet_string(EXPECTED_PUB_HEX), + lock_utils.hex_string_to_octet_string(EXPECTED_GROUP_ID_HEX), + nil + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) + ) + end +) + +test.register_coroutine_test( + "Set Reader Config command sets busy state in command result when busy", + function() + mock_device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, nil, {persist = true}) + lock_utils.is_busy_state_set(mock_device) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ profile = t_utils.get_profile_definition("lock.yml")})) + test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockAliro.commandResult( + { + commandName="setReaderConfig", + statusCode="busy" + }, + {state_change=true, visibility={displayed=false}} + ) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) + ) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua index 64f8944a43..8ec2a2a781 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua @@ -162,6 +162,82 @@ local mock_device_user_pin_schedule_unlatch = test.mock_device.build_test_matter } }) +local mock_device_battery = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("lock-battery.yml"), + manufacturer_info = { + vendor_id = 0x147F, + product_id = 0x0001, + }, + 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 = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x0, + }, + { + cluster_id = clusters.PowerSource.ID, + cluster_type = "SERVER", + feature_map = 10 + }, + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + +local mock_device_battery_level = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("lock-batteryLevel.yml"), + manufacturer_info = { + vendor_id = 0x147F, + product_id = 0x0001, + }, + 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 = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x0, + }, + { + cluster_id = clusters.PowerSource.ID, + cluster_type = "SERVER", + feature_map = 10 + }, + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = DoorLock.ID} local function test_init() test.disable_startup_messages() @@ -273,6 +349,56 @@ local function test_init_user_pin_schedule_unlatch() mock_device_user_pin_schedule_unlatch:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end +local function test_init_battery() + test.disable_startup_messages() + -- subscribe request + local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_battery) + subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_battery)) + subscribe_request:merge(cluster_base.subscribe(mock_device_battery, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) + subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_battery)) + subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_battery)) + subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device_battery)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_battery)) + + -- add test device, handle initial subscribe + test.mock_device.add_test_device(mock_device_battery) + test.socket.matter:__expect_send({mock_device_battery.id, subscribe_request}) + -- actual onboarding flow + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "added" }) + test.socket.capability:__expect_send( + mock_device_battery:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "init" }) + test.socket.matter:__expect_send({mock_device_battery.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_battery.id, "doConfigure" }) + mock_device_battery:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + +local function test_init_battery_level() + test.disable_startup_messages() + -- subscribe request + local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_battery_level) + subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_battery_level)) + subscribe_request:merge(cluster_base.subscribe(mock_device_battery_level, nil, DoorLockFeatureMapAttr.cluster, DoorLockFeatureMapAttr.ID)) + subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_battery_level)) + subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_battery_level)) + subscribe_request:merge(clusters.PowerSource.attributes.BatChargeLevel:subscribe(mock_device_battery_level)) + subscribe_request:merge(clusters.PowerSource.attributes.AttributeList:subscribe(mock_device_battery_level)) + + -- add test device, handle initial subscribe + test.mock_device.add_test_device(mock_device_battery_level) + test.socket.matter:__expect_send({mock_device_battery_level.id, subscribe_request}) + -- actual onboarding flow + test.socket.device_lifecycle:__queue_receive({ mock_device_battery_level.id, "added" }) + test.socket.capability:__expect_send( + mock_device_battery_level:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + test.socket.device_lifecycle:__queue_receive({ mock_device_battery_level.id, "init" }) + test.socket.matter:__expect_send({mock_device_battery_level.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_battery_level.id, "doConfigure" }) + mock_device_battery_level:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + test.set_test_init_function(test_init) test.register_coroutine_test( @@ -656,4 +782,68 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Handle received BatPercentRemaining from device.", + function() + test.socket.matter:__queue_receive( + { + mock_device_battery.id, + clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data( + mock_device_battery, 1, 150 + ) + } + ) + test.socket.capability:__expect_send( + mock_device_battery:generate_test_message("main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5))) + ) + end, + { + test_init = test_init_battery, + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Handle received BatChargeLevel from device.", + function() + test.socket.matter:__queue_receive( + { + mock_device_battery_level.id, + clusters.PowerSource.attributes.BatChargeLevel:build_test_report_data( + mock_device_battery_level, 1, clusters.PowerSource.types.BatChargeLevelEnum.OK + ) + } + ) + test.socket.capability:__expect_send( + mock_device_battery_level:generate_test_message("main", capabilities.batteryLevel.battery.normal()) + ) + test.socket.matter:__queue_receive( + { + mock_device_battery_level.id, + clusters.PowerSource.attributes.BatChargeLevel:build_test_report_data( + mock_device_battery_level, 1, clusters.PowerSource.types.BatChargeLevelEnum.CRITICAL + ) + } + ) + test.socket.capability:__expect_send( + mock_device_battery_level:generate_test_message("main", capabilities.batteryLevel.battery.critical()) + ) + test.socket.matter:__queue_receive( + { + mock_device_battery_level.id, + clusters.PowerSource.attributes.BatChargeLevel:build_test_report_data( + mock_device_battery_level, 1, clusters.PowerSource.types.BatChargeLevelEnum.WARNING + ) + } + ) + test.socket.capability:__expect_send( + mock_device_battery_level:generate_test_message("main", capabilities.batteryLevel.battery.warning()) + ) + end, + { + test_init = test_init_battery_level, + min_api_version = 17 + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/unmigrated-matter-lock/can_handle.lua b/drivers/SmartThings/matter-lock/src/unmigrated-matter-lock/can_handle.lua new file mode 100644 index 0000000000..3da6b948bf --- /dev/null +++ b/drivers/SmartThings/matter-lock/src/unmigrated-matter-lock/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" + +local function is_unmigrated_matter_lock_product(opts, driver, device) + local is_migrated = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.migrated.NAME) or nil + if device:supports_capability(capabilities.lockAlarm) then + return false + elseif device:supports_capability(capabilities.lockCodes) and is_migrated == true then + return false + else + return true, require("unmigrated-matter-lock") + end +end + +return is_unmigrated_matter_lock_product diff --git a/drivers/SmartThings/matter-lock/src/unmigrated-matter-lock/init.lua b/drivers/SmartThings/matter-lock/src/unmigrated-matter-lock/init.lua new file mode 100644 index 0000000000..6e7d55d895 --- /dev/null +++ b/drivers/SmartThings/matter-lock/src/unmigrated-matter-lock/init.lua @@ -0,0 +1,758 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local clusters = require "st.matter.clusters" +local DoorLock = clusters.DoorLock +local PowerSource = clusters.PowerSource +local capabilities = require "st.capabilities" +local im = require "st.matter.interaction_model" +local lock_utils = require "lock_utils" + +local PROFILE_BASE_NAME = "__profile_base_name" +local INITIAL_COTA_INDEX = 1 +-- add this definition for locks to work on older lua libs +local UNLATCHED_STATE = 0x3 + +local subscribed_attributes = { + [capabilities.lock.ID] = { + DoorLock.attributes.LockState + }, + [capabilities.battery.ID] = { + PowerSource.attributes.BatPercentRemaining + }, + [capabilities.batteryLevel.ID] = { + PowerSource.attributes.BatChargeLevel + } +} + +--- If a device needs a cota credential this function attempts to set the credential +--- at the index provided. The set_credential_response_handler handles all failures +--- and retries with the appropriate index when necessary. +local function set_cota_credential(device, credential_index) + local eps = device:get_endpoints(DoorLock.ID) + local cota_cred = device:get_field(lock_utils.COTA_CRED) + if cota_cred == nil then + -- Shouldn't happen but defensive to try to figure out if we need the cota cred and set it. + device:send(DoorLock.attributes.RequirePINforRemoteOperation:read(device, #eps > 0 and eps[1] or 1)) + return + elseif cota_cred == false then + device.log.debug("Device does not require PIN for remote operation. Not setting COTA credential") + return + end + + if device:get_field(lock_utils.SET_CREDENTIAL) ~= nil then + device.log.debug("delaying setting COTA credential since a credential is currently being set") + device.thread:call_with_delay(2, function(t) + set_cota_credential(device, credential_index) + end) + return + end + + device:set_field(lock_utils.COTA_CRED_INDEX, credential_index, {persist = true}) + local credential = {credential_type = DoorLock.types.DlCredentialType.PIN, credential_index = credential_index} + -- Set the credential to a code + device:set_field(lock_utils.SET_CREDENTIAL, credential_index) + device.log.info(string.format("Attempting to set COTA credential at index %s", credential_index)) + device:send(DoorLock.server.commands.SetCredential( + device, + #eps > 0 and eps[1] or 1, + DoorLock.types.DlDataOperationType.ADD, + credential, + device:get_field(lock_utils.COTA_CRED), + nil, -- nil user_index creates a new user + DoorLock.types.DlUserStatus.OCCUPIED_ENABLED, + DoorLock.types.DlUserType.REMOTE_ONLY_USER + )) +end + +local function generate_cota_cred_for_device(device) + local len = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodeLength.NAME) or 4 + local cred_data = math.floor(math.random() * (10 ^ len)) + cred_data = string.format("%0" .. tostring(len) .. "d", cred_data) + device:set_field(lock_utils.COTA_CRED, cred_data, {persist = true}) +end + +local function lock_state_handler(driver, device, ib, response) + local LockState = DoorLock.attributes.LockState + local attr = capabilities.lock.lock + local LOCK_STATE = { + [LockState.NOT_FULLY_LOCKED] = attr.not_fully_locked(), + [LockState.LOCKED] = attr.locked(), + [LockState.UNLOCKED] = attr.unlocked(), + [UNLATCHED_STATE] = attr.unlocked(), -- Fully unlocked with latch pulled + } + + if ib.data.value ~= nil then + local event = LOCK_STATE[ib.data.value] + if event ~= nil then + device:emit_event(event) + else + device.log.warn(string.format("Received unknown lock state value (%s), emitting unknown", ib.data.value)) + device:emit_event(attr.unknown()) + end + else + device:emit_event(LOCK_STATE[LockState.NOT_FULLY_LOCKED]) + 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 handle_battery_charge_level(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 handle_power_source_attribute_list(driver, device, ib, response) + local support_battery_percentage = false + local support_battery_level = false + 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 + support_battery_percentage = true + elseif attr.value == 0x0E then + support_battery_level = true + end + end + local profile_name = device:get_field(PROFILE_BASE_NAME) + if profile_name ~= nil then + if not support_battery_percentage then + if support_battery_level then + profile_name = profile_name .. "-batteryLevel" + else + profile_name = profile_name .. "-nobattery" + end + end + device.log.info(string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({profile = profile_name}) + end +end + +local function max_pin_code_len_handler(driver, device, ib, response) + device:emit_event(capabilities.lockCodes.maxCodeLength(ib.data.value, {visibility = {displayed = false}})) +end + +local function min_pin_code_len_handler(driver, device, ib, response) + device:emit_event(capabilities.lockCodes.minCodeLength(ib.data.value, {visibility = {displayed = false}})) +end + +local function num_pin_users_handler(driver, device, ib, response) + device:set_field(lock_utils.TOTAL_PIN_USERS, ib.data.value) + device:emit_event(capabilities.lockCodes.maxCodes(ib.data.value, {visibility = {displayed = false}})) +end + +local function apply_cota_credentials_if_absent(device) + if not device:get_field(lock_utils.COTA_CRED) then + --Process after all other info blocks have been dispatched to ensure MaxPINCodeLength has been processed + device.thread:call_with_delay(0, function(t) + generate_cota_cred_for_device(device) + -- delay needed to allow test to override the random credential data + device.thread:call_with_delay(0, function(t) + -- Attempt to set cota credential at the lowest index + set_cota_credential(device, INITIAL_COTA_INDEX) + end) + end) + end +end + +local function require_remote_pin_handler(driver, device, ib, response) + if ib.data.value then + apply_cota_credentials_if_absent(device) + else + device:set_field(lock_utils.COTA_CRED, false, {persist = true}) + end +end + +local function clear_credential_response_handler(driver, device, ib, response) + local deleted_code_slot = device:get_field(lock_utils.DELETING_CODE) + if deleted_code_slot == nil and ib.status == im.InteractionResponse.Status.SUCCESS then + device.log.debug("Cleared space in lock credential db for COTA credential") + return + end + if ib.status == im.InteractionResponse.Status.SUCCESS then + lock_utils.lock_codes_event(device, lock_utils.code_deleted(device, tostring(deleted_code_slot))) + --make sure cota credential exists if the user deletes it or if space was created for the COTA cred + if deleted_code_slot == device:get_field(lock_utils.COTA_CRED_INDEX) or + device:get_field(lock_utils.NONFUNCTIONAL) then + set_cota_credential(device, device:get_field(lock_utils.COTA_CRED_INDEX) or INITIAL_COTA_INDEX) + end + else + device.log.error(string.format("Failed to delete code slot %s", deleted_code_slot)) + end + device:set_field(lock_utils.DELETING_CODE, nil) +end + +local function set_credential_response_handler(driver, device, ib, response) + if ib.status ~= im.InteractionResponse.Status.SUCCESS then + device.log.error("Failed to set code for device") + return + end + local elements = ib.info_block.data.elements + local credential_index = device:get_field(lock_utils.SET_CREDENTIAL) + device:set_field(lock_utils.SET_CREDENTIAL, nil) + if credential_index == nil then + device.log.error("Received unexpected SetCredentialResponse") + return + end + local code_slot = tostring(credential_index) + local status = elements.status.value + if status == DoorLock.types.DlStatus.SUCCESS then + local event = capabilities.lockCodes.codeChanged("", {state_change = true}) + local cota_cred_index = device:get_field(lock_utils.COTA_CRED_INDEX) + local code_name = (credential_index == cota_cred_index and lock_utils.COTA_CODE_NAME) or + lock_utils.get_code_name(device, code_slot) + event.data = {codeName = code_name} + event.value = lock_utils.get_change_type(device, tostring(code_slot)) + local lock_codes = lock_utils.get_lock_codes(device) + lock_codes[code_slot] = event.data.codeName + device:emit_event(event) + if credential_index == cota_cred_index then + device:emit_event( + capabilities.lockCodes.codeChanged( + code_slot .. " renamed", {state_change = true} + ) + ) + end + lock_utils.lock_codes_event(device, lock_codes) + lock_utils.reset_code_state(device, code_slot) + if device:get_field(lock_utils.NONFUNCTIONAL) and cota_cred_index == credential_index then + device.log.info("Successfully set COTA credential after being non-functional") + device:set_field(lock_utils.NONFUNCTIONAL, false, {persist = true}) + local profile_name = "base-lock" + device:set_field(PROFILE_BASE_NAME, profile_name, {persist = true}) + local battery_feature_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) + if #battery_feature_eps == 0 then + profile_name = profile_name .. "-nobattery" + device.log.info(string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({profile = profile_name, provisioning_state = "PROVISIONED"}) + else + local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) + req:merge(clusters.PowerSource.attributes.AttributeList:read()) + device:send(req) + end + end + elseif device:get_field(lock_utils.COTA_CRED) and credential_index == device:get_field(lock_utils.COTA_CRED_INDEX) then + -- Handle failure to set a COTA credential + if status == DoorLock.types.DlStatus.OCCUPIED and elements.next_credential_index.value ~= nil then + --This credential index is unavailable, but there is another available + set_cota_credential(device, elements.next_credential_index.value) + elseif status == DoorLock.types.DlStatus.OCCUPIED and + elements.next_credential_index.value == nil and + credential_index == INITIAL_COTA_INDEX then + --There are no credential indices available on the device + device.log.error("Device requires COTA credential, but has no credential indexes available!") + device.log.error("Lock and Unlock commands will no longer work!!") + device:set_field(PROFILE_BASE_NAME, "nonfunctional-lock", {persist = true}) + device:try_update_metadata({profile = "nonfunctional-lock", provisioning_state = "NONFUNCTIONAL"}) + device:set_field(lock_utils.NONFUNCTIONAL, true, {persist = true}) + elseif status == DoorLock.types.DlStatus.OCCUPIED and elements.next_credential_index.value == nil then + --There are no credential indices available, but we must ensure we search all indices. + set_cota_credential(device, INITIAL_COTA_INDEX) + elseif status == DoorLock.types.DlStatus.DUPLICATE then + --The credential we randomly generated already exists + generate_cota_cred_for_device(device) + --delay 0 needed for unit test verification of random value + device.thread:call_with_delay(0, function(t) set_cota_credential(device, credential_index) end) + elseif status == DoorLock.types.DlStatus.INVALID_FIELD then + device.log.error("Invalid SetCredential command sent to set a COTA credential. This is a bug.") + elseif elements.next_credential_index.value ~= nil then + device.log.warn(string.format( + "Received non-success SetCredentialResponse status (%s), but there is a next credential index available", elements.status + )) + set_cota_credential(device, elements.next_credential_index.value) + end + else + device.log.error( + string.format( + "Failed to set user code for device, SetCredential status received: %s", elements.status + ) + ) + end +end + +local function get_credential_status_response_handler(driver, device, ib, response) + if ib.status ~= im.InteractionResponse.Status.SUCCESS then + device.log.warn("Not taking action on GetCredentialStatusResponse because failed status") + end + local cred_index = device:get_field(lock_utils.CHECKING_CREDENTIAL) + if cred_index == nil then + device.log.warn("Received unexpected CredentialStatusResponse") + return + end + local elements = ib.info_block.data.elements + local credential_exists = elements.credential_exists.value + local next_credential_index = elements.next_credential_index and elements.next_credential_index.value or nil + + local event = capabilities.lockCodes.codeChanged("", {state_change = true}) + local code_slot = tostring(cred_index) + local cota_cred_index = device:get_field(lock_utils.COTA_CRED_INDEX) + local code_name = (cred_index == cota_cred_index and lock_utils.COTA_CODE_NAME) or lock_utils.get_code_name(device, code_slot) + event.data = {codeName = code_name} + if credential_exists then + -- Code slot is occupied + event.value = lock_utils.get_change_type(device, code_slot) + local lock_codes = lock_utils.get_lock_codes(device) + lock_codes[code_slot] = event.data.codeName + device:emit_event(event) + lock_utils.lock_codes_event(device, lock_codes) + lock_utils.reset_code_state(device, code_slot) + else + -- Code slot is unoccupied + if (lock_utils.get_lock_codes(device)[code_slot] ~= nil) then + -- Code has been deleted + lock_utils.lock_codes_event(device, lock_utils.code_deleted(device, code_slot)) + if cred_index == cota_cred_index then --make sure cota credential exists if it was deleted + set_cota_credential(device, INITIAL_COTA_INDEX) + end + else + -- Code is unset + event.value = code_slot .. " unset" + device:emit_event(event) + end + end + device:set_field(lock_utils.CHECKING_CREDENTIAL, nil) + + local is_scanning = device:get_latest_state( + "main", capabilities.lockCodes.ID, capabilities.lockCodes.scanCodes.NAME + ) == "Scanning" + if not is_scanning then + return + end + if (next_credential_index == nil) then + device:emit_event( + capabilities.lockCodes.scanCodes( + "Complete", {visibility = {displayed = false}} + ) + ) + local lock_codes = lock_utils.get_lock_codes(device) + lock_utils.lock_codes_event(device, lock_codes) + elseif next_credential_index ~= nil then + device:set_field(lock_utils.CHECKING_CREDENTIAL, next_credential_index) + device:send( + DoorLock.server.commands.GetCredentialStatus( + device, + ib.info_block.endpoint_id, + {credential_type = DoorLock.types.DlCredentialType.PIN, credential_index = device:get_field(lock_utils.CHECKING_CREDENTIAL)} + ) + ) + end +end + +local function alarm_event_handler(driver, device, ib, response) + local DlAlarmCode = DoorLock.types.DlAlarmCode + local alarm_code = ib.data.elements.alarm_code + if alarm_code.value == DlAlarmCode.FRONT_ESCEUTCHEON_REMOVED or alarm_code.value + == DlAlarmCode.WRONG_CODE_ENTRY_LIMIT or alarm_code.value == DlAlarmCode.FORCED_USER + or alarm_code.value == DlAlarmCode.DOOR_FORCED_OPEN then + device:emit_event(capabilities.tamperAlert.tamper.detected()) + end +end + +local function lock_op_event_handler(driver, device, ib, response) + local tamper_detected = device:get_latest_state( + device:endpoint_to_component(ib.endpoint_id), + capabilities.tamperAlert.ID, capabilities.tamperAlert.tamper.NAME + ) + if nil == tamper_detected or tamper_detected == capabilities.tamperAlert.tamper.detected.NAME then + device:emit_event(capabilities.tamperAlert.tamper.clear()) + end +end + +local function lock_user_change_event_handler(driver, device, ib, response) + local event = capabilities.lockCodes.codeChanged("", {state_change = true}) + local elements = ib.data.elements + local data_type_changed = elements.lock_data_type.value + local operation_type = elements.data_operation_type.value + local user_index = elements.user_index.value + local data_index = elements.data_index and elements.data_index.value + local cota_cred_index = device:get_field(lock_utils.COTA_CRED_INDEX) + + if data_type_changed == DoorLock.types.DlLockDataType.PIN then -- pin added or removed + local code_slot = data_index and tostring(data_index) or nil + if (operation_type == DoorLock.types.DlDataOperationType.ADD or operation_type + == DoorLock.types.DlDataOperationType.MODIFY) and code_slot ~= nil then + local change_type = lock_utils.get_change_type(device, code_slot) + event.value = change_type + local code_name = (data_index == cota_cred_index and lock_utils.COTA_CODE_NAME) or lock_utils.get_code_name(device, code_slot) + event.data = {codeName = code_name} + device:emit_event(event) + if string.match(change_type, "%d+ set") ~= nil then + local lock_codes = lock_utils.get_lock_codes(device) + lock_codes[code_slot] = code_name + lock_utils.lock_codes_event(device, lock_codes) + end + elseif operation_type == DoorLock.types.DlDataOperationType.CLEAR and code_slot ~= nil then + lock_utils.lock_codes_event(device, lock_utils.code_deleted(device, tostring(code_slot))) + --make sure cota credential is created if the user deletes it or a space is made for it + if data_index == cota_cred_index or device:get_field(lock_utils.NONFUNCTIONAL) then + set_cota_credential(device, cota_cred_index or INITIAL_COTA_INDEX) + end + else -- invalid event because no credential index + device.log.error( + "Received unhandled LockUserChangeEvent because it didn't affect a PIN credential" + ) + end + elseif data_type_changed == DoorLock.types.DlLockDataType.USER_INDEX and operation_type + == DoorLock.types.DlDataOperationType.CLEAR then + if user_index == 0xFFFE then + device.log.warn("All users were cleared by another fabric") -- we never do this + for cs, _ in pairs(lock_utils.get_lock_codes(device)) do + lock_utils.code_deleted(device, cs) + end + lock_utils.lock_codes_event(device, {}) + if device:get_field(lock_utils.COTA_CRED) ~= nil then set_cota_credential(device, INITIAL_COTA_INDEX) end + else + device.log.info("Not handling LockUserChange event") + end + -- Note when a Lock User is deleted, the credentials associated with that user are also deleted. + -- Change events are created for each credential as well as the user. + else + device.log.info( + string.format( + "Not handling LockUserChange event because the data type (%s) doesn't affect lock codes", + elements.lock_data_type + ) + ) + end +end + +local function handle_refresh(driver, device, command) + -- Note: no endpoint specified indicates a wildcard endpoint + local req = DoorLock.attributes.LockState:read(device) + req:merge(PowerSource.attributes.BatPercentRemaining:read(device)) + device:send(req) +end + +local function handle_lock(driver, device, command) + local ep = device:component_to_endpoint(command.component) + local cota_cred = device:get_field(lock_utils.COTA_CRED) + if cota_cred then + device:send( + DoorLock.server.commands.LockDoor(device, ep, cota_cred) + ) + else + device:send(DoorLock.server.commands.LockDoor(device, ep)) + end +end + +local function handle_unlock(driver, device, command) + local ep = device:component_to_endpoint(command.component) + local cota_cred = device:get_field(lock_utils.COTA_CRED) + if cota_cred then + device:send( + DoorLock.server.commands.UnlockDoor(device, ep, cota_cred) + ) + else + device:send(DoorLock.server.commands.UnlockDoor(device, ep)) + end +end + +local function handle_delete_code(driver, device, command) + local endpoint = device:component_to_endpoint(command.component) + device:set_field(lock_utils.DELETING_CODE, command.args.codeSlot) + device:send(DoorLock.server.commands.ClearCredential( + device, + endpoint, + {credential_type = DoorLock.types.DlCredentialType.PIN, credential_index = command.args.codeSlot} + )) +end + +local function handle_reload_all_codes(driver, device, command) + if (device:get_field(lock_utils.CHECKING_CREDENTIAL) == nil) then + lock_utils.lock_codes_event(device, {}) + device:set_field(lock_utils.CHECKING_CREDENTIAL, 1) + else + device.log.info(string.format("Delaying scanning since currently checking credential %d", device:get_field(lock_utils.CHECKING_CREDENTIAL))) + device.thread:call_with_delay(2, function(t) handle_reload_all_codes(driver, device, command) end) + return + end + device.thread:call_with_delay(5, function(t) + device:emit_event(capabilities.lockCodes.scanCodes("Scanning")) + device:send( + clusters.DoorLock.server.commands.GetCredentialStatus( + device, device:component_to_endpoint(command.component), + {credential_type = DoorLock.types.DlCredentialType.PIN, credential_index = device:get_field(lock_utils.CHECKING_CREDENTIAL)} + ) + ) + end) +end + +local function handle_request_code(driver, device, command) + local endpoint_id = device:component_to_endpoint(command.component) + device:set_field(lock_utils.CHECKING_CREDENTIAL, command.args.codeSlot) + local credential = { + credential_type = DoorLock.types.DlCredentialType.PIN, + credential_index = command.args.codeSlot, + } + device:send(clusters.DoorLock.server.commands.GetCredentialStatus(device, endpoint_id, credential)) +end + +local function handle_set_code(driver, device, command) + local endpoint = device:component_to_endpoint(command.component) + if (command.args.codePIN == "") then + driver:inject_capability_command( + device, { + capability = capabilities.lockCodes.ID, + command = capabilities.lockCodes.commands.nameSlot.NAME, + args = {command.args.codeSlot, command.args.codeName}, + } + ) + else + local credential = { + credential_type = DoorLock.types.DlCredentialType.PIN, + credential_index = command.args.codeSlot, + } + device:set_field(lock_utils.SET_CREDENTIAL, command.args.codeSlot) + device:send( + DoorLock.server.commands.SetCredential( + device, endpoint, DoorLock.types.DlDataOperationType.ADD, -- operation_type + credential, command.args.codePIN, -- credential_data + nil, -- nil user_index creates a new user + DoorLock.types.DlUserStatus.OCCUPIED_ENABLED, DoorLock.types.DlUserType.UNRESTRICTED_USER + ) + ) + if (command.args.codeName ~= nil) then + -- wait for confirmation from the lock to commit this to memory + -- Groovy driver has a lot more info passed here as a description string, may need to be investigated + local codeState = device:get_field(lock_utils.CODE_STATE) or {} + codeState["setName" .. command.args.codeSlot] = command.args.codeName + device:set_field(lock_utils.CODE_STATE, codeState, {persist = true}) + end + end +end + +local function handle_name_slot(driver, device, command) + local code_slot = tostring(command.args.codeSlot) + local lock_codes = lock_utils.get_lock_codes(device) + if (lock_codes[code_slot] ~= nil) then + lock_codes[code_slot] = command.args.codeName + device:emit_event( + capabilities.lockCodes.codeChanged( + code_slot .. " renamed", {state_change = true} + ) + ) + lock_utils.lock_codes_event(device, lock_codes) + end +end + +local function json_to_table(input) + if #input == 0 then return end + local result = {} + for key, value in string.gmatch(input, '"(.-)"%s*:%s*"(.-)"') do + table.insert(result, {key, value}) + end + return result +end + +local function store_lock_codes(device) + local lockCodes = device:get_latest_state( + "main", + capabilities.lockCodes.ID, + capabilities.lockCodes.lockCodes.NAME, + {} + ) + lockCodes = json_to_table(lockCodes) + device:set_field(lock_utils.LOCK_CODES_FOR_MIGRATION, lockCodes, {persist = true}) +end + +local function handle_migrate(driver, device, command) + store_lock_codes(device) + device:set_field(lock_utils.LOCK_CODES_COPY_REQUIRED, true, {persist = true}) + device:emit_event(capabilities.lockCodes.migrated(true)) + driver:inject_capability_command(device, { + capability = capabilities.refresh.ID, + command = capabilities.refresh.commands.refresh.NAME, + args = {} + }) +end + +local function find_default_endpoint(device, cluster) + local res = device.MATTER_DEFAULT_ENDPOINT + local eps = device:get_endpoints(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) + return find_default_endpoint(device, clusters.DoorLock.ID) +end + +local function info_changed(driver, device, event, args) + if device.profile.id ~= args.old_st_store.profile.id then + device:subscribe() + end +end + +local function do_configure(driver, device) + -- check if the device is NOT currently profiled as base-lock + -- by ANDing a query for every capability in the base-lock profiles. + -- If it does not use base-lock, it is WWST and does not need re-profiling. + if not (device:supports_capability(capabilities.lock) and + device:supports_capability(capabilities.lockCodes) and + device:supports_capability(capabilities.tamperAlert) and + device:supports_capability(capabilities.battery)) then + return + end + + -- if not fingerprinted, dynamically configure base-lock profile + local profile_name = "base-lock" + device:set_field(PROFILE_BASE_NAME, profile_name, {persist = true}) + local battery_feature_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) + if #battery_feature_eps == 0 then + profile_name = profile_name .. "-nobattery" + device.log.info(string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({profile = profile_name}) + else + local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) + req:merge(clusters.PowerSource.attributes.AttributeList:read()) + device:send(req) + end +end + +local function device_init(driver, device) + device:set_component_to_endpoint_fn(component_to_endpoint) + device:subscribe() + + -- check if we have a missing COTA credential. Only run this if it has not been run before (i.e. in device added), + -- because there is a delay built into the COTA process and we do not want to start two COTA generations at the same time + -- in the event this was triggered on add. + if not device:get_field(lock_utils.COTA_READ_INITIALIZED) or not device:get_field(lock_utils.COTA_CRED) then + local eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.DoorLockFeature.CREDENTIALSOTA | DoorLock.types.DoorLockFeature.PIN_CREDENTIALS}) + if #eps == 0 then + device.log.debug("Device will not require PIN for remote operation") + device:set_field(lock_utils.COTA_CRED, false, {persist = true}) + else + device:send(DoorLock.attributes.RequirePINforRemoteOperation:read(device, eps[1])) + device:set_field(lock_utils.COTA_READ_INITIALIZED, true, {persist = true}) + end + end +end + +local function device_added(driver, device) + --Note: May want to write OperatingMode to NORMAL, to attempt to ensure remote operation works + --Note: May want to write RequirePINForRemoteOperation, to avoid cota cases if possible. + device:emit_event(capabilities.tamperAlert.tamper.clear()) + local eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.DoorLockFeature.PIN_CREDENTIALS}) + if #eps == 0 then + if device:supports_capability_by_id(capabilities.tamperAlert.ID) then + device.log.debug("Device does not support lockCodes. Switching profile.") + local profile_name = "lock-without-codes" + device:set_field(PROFILE_BASE_NAME, profile_name, {persist = true}) + local battery_feature_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) + if #battery_feature_eps == 0 then + profile_name = profile_name .. "-nobattery" + end + device.log.info(string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({profile = profile_name}) + else + device.log.debug("Device supports neither lock codes nor tamper. Unable to switch profile.") + end + else + local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) + req:merge(DoorLock.attributes.MaxPINCodeLength:read(device, eps[1])) + req:merge(DoorLock.attributes.MinPINCodeLength:read(device, eps[1])) + req:merge(DoorLock.attributes.NumberOfPINUsersSupported:read(device, eps[1])) + driver:inject_capability_command(device, { + capability = capabilities.lockCodes.ID, + command = capabilities.lockCodes.commands.reloadAllCodes.NAME, + args = {} + }) + + --Device may require pin for remote operation if it supports COTA and PIN features. + eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.DoorLockFeature.CREDENTIALSOTA | DoorLock.types.DoorLockFeature.PIN_CREDENTIALS}) + if #eps == 0 then + device.log.debug("Device will not require PIN for remote operation") + device:set_field(lock_utils.COTA_CRED, false, {persist = true}) + else + req:merge(DoorLock.attributes.RequirePINforRemoteOperation:read(device, eps[1])) + device:set_field(lock_utils.COTA_READ_INITIALIZED, true, {persist = true}) + end + device:send(req) + end +end + +local unmigrated_matter_lock_driver = { + NAME = "Unmigrated Matter Lock Driver", + matter_handlers = { + attr = { + [DoorLock.ID] = { + [DoorLock.attributes.LockState.ID] = lock_state_handler, + [DoorLock.attributes.MaxPINCodeLength.ID] = max_pin_code_len_handler, + [DoorLock.attributes.MinPINCodeLength.ID] = min_pin_code_len_handler, + [DoorLock.attributes.NumberOfPINUsersSupported.ID] = num_pin_users_handler, + [DoorLock.attributes.RequirePINforRemoteOperation.ID] = require_remote_pin_handler, + }, + [PowerSource.ID] = { + [PowerSource.attributes.AttributeList.ID] = handle_power_source_attribute_list, + [PowerSource.attributes.BatPercentRemaining.ID] = handle_battery_percent_remaining, + [PowerSource.attributes.BatChargeLevel.ID] = handle_battery_charge_level, + }, + }, + event = { + [DoorLock.ID] = { + [DoorLock.events.DoorLockAlarm.ID] = alarm_event_handler, + [DoorLock.events.LockOperation.ID] = lock_op_event_handler, + [DoorLock.events.LockUserChange.ID] = lock_user_change_event_handler, + }, + }, + cmd_response = { + [DoorLock.ID] = { + [DoorLock.client.commands.SetCredentialResponse.ID] = set_credential_response_handler, + [DoorLock.client.commands.GetCredentialStatusResponse.ID] = get_credential_status_response_handler, + [DoorLock.server.commands.ClearCredential.ID] = clear_credential_response_handler, + }, + }, + }, + subscribed_attributes = subscribed_attributes, + subscribed_events = { + [capabilities.tamperAlert.ID] = {DoorLock.events.DoorLockAlarm, DoorLock.events.LockOperation}, + [capabilities.lockAlarm.ID] = {DoorLock.events.DoorLockAlarm}, + [capabilities.lockCodes.ID] = {DoorLock.events.LockUserChange}, + }, + capability_handlers = { + [capabilities.refresh.ID] = {[capabilities.refresh.commands.refresh.NAME] = handle_refresh}, + [capabilities.lock.ID] = { + [capabilities.lock.commands.lock.NAME] = handle_lock, + [capabilities.lock.commands.unlock.NAME] = handle_unlock, + }, + [capabilities.lockCodes.ID] = { + [capabilities.lockCodes.commands.deleteCode.NAME] = handle_delete_code, + [capabilities.lockCodes.commands.reloadAllCodes.NAME] = handle_reload_all_codes, + [capabilities.lockCodes.commands.requestCode.NAME] = handle_request_code, + [capabilities.lockCodes.commands.setCode.NAME] = handle_set_code, + [capabilities.lockCodes.commands.nameSlot.NAME] = handle_name_slot, + [capabilities.lockCodes.commands.migrate.NAME] = handle_migrate, + }, + }, + supported_capabilities = { + capabilities.lock, + capabilities.lockCodes, + capabilities.tamperAlert, + capabilities.battery, + capabilities.batteryLevel, + }, + lifecycle_handlers = { + init = device_init, + added = device_added, + doConfigure = do_configure, + infoChanged = info_changed, + }, + can_handle = require("unmigrated-matter-lock.can_handle"), +} + +return unmigrated_matter_lock_driver From 8ca1717ec47622ce3d7b09c10f6d27a70724adb4 Mon Sep 17 00:00:00 2001 From: Juho Date: Fri, 10 Jul 2026 10:33:07 +0900 Subject: [PATCH 435/538] zwave-lock: respond to Time DATE_GET with DATE_REPORT The driver only handled TIME_GET, so a lock requesting the date during a Time CC sync never got an answer (the hub ACKs at the MAC layer but no DATE_REPORT is sent). Add a DATE_GET handler that reports the current date, following the reworked lock_handlers structure. Fixes #3076 --- drivers/SmartThings/zwave-lock/src/init.lua | 3 ++- .../src/lock_handlers/zwave_responses.lua | 13 ++++++++++++ .../zwave-lock/src/test/test_lock_battery.lua | 21 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zwave-lock/src/init.lua b/drivers/SmartThings/zwave-lock/src/init.lua index 8b2cf76eb8..6c85639625 100644 --- a/drivers/SmartThings/zwave-lock/src/init.lua +++ b/drivers/SmartThings/zwave-lock/src/init.lua @@ -77,7 +77,8 @@ local driver_template = { }, zwave_handlers = { [cc.TIME] = { - [0x01] = zwave_handlers.time_get_handler -- used by DanaLock + [0x01] = zwave_handlers.time_get_handler, -- used by DanaLock + [0x03] = zwave_handlers.date_get_handler }, [cc.NOTIFICATION] = { [Notification.REPORT] = zwave_handlers.notification_report diff --git a/drivers/SmartThings/zwave-lock/src/lock_handlers/zwave_responses.lua b/drivers/SmartThings/zwave-lock/src/lock_handlers/zwave_responses.lua index cfabe41932..7822ab2bfe 100644 --- a/drivers/SmartThings/zwave-lock/src/lock_handlers/zwave_responses.lua +++ b/drivers/SmartThings/zwave-lock/src/lock_handlers/zwave_responses.lua @@ -240,4 +240,17 @@ function ZwaveHandlers.time_get_handler(driver, device, cmd) ) end +function ZwaveHandlers.date_get_handler(driver, device, cmd) + local Time = (require "st.zwave.CommandClass.Time")({ version = 1 }) + local time = os.date("*t") + device:send_to_component( + Time:DateReport({ + year = time.year, + month = time.month, + day = time.day + }), + device:endpoint_to_component(cmd.src_channel) + ) +end + return ZwaveHandlers diff --git a/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua b/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua index 11c03650c6..d9642851e1 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua @@ -140,4 +140,25 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "The driver should respond correctly to a date get", + function () + test.socket.zwave:__queue_receive({ mock_device.id, Time:DateGet({},{ + encap = zw.ENCAP.AUTO, + src_channel = 0, + dst_channels = {} + }) + }) + local time = os.date("*t") + test.socket.zwave:__expect_send(Time:DateReport({ + year = time.year, + month = time.month, + day = time.day + }):build_test_tx(mock_device.id)) + end, + { + min_api_version = 17 + } +) + test.run_registered_tests() From c4126ccdede6d67275efb309f86fce355af1862f Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:14:56 -0500 Subject: [PATCH 436/538] Matter Switch: Support 3R Garage Door Subdriver (#2702) --- .../matter-switch/fingerprints.yml | 5 + .../profiles/garage-door-battery.yml | 14 + .../SmartThings/matter-switch/src/init.lua | 1 + .../third_reality_garage_door/can_handle.lua | 11 + .../third_reality_garage_door/init.lua | 92 +++ .../third_reality_mk1/can_handle.lua | 7 +- .../sub_drivers/third_reality_mk1/init.lua | 1 + .../matter-switch/src/switch_utils/fields.lua | 10 +- .../src/test/test_third_reality_mk1.lua | 241 -------- .../test/test_third_reality_subdrivers.lua | 533 ++++++++++++++++++ 10 files changed, 666 insertions(+), 249 deletions(-) create mode 100644 drivers/SmartThings/matter-switch/profiles/garage-door-battery.yml create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_garage_door/can_handle.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_garage_door/init.lua delete mode 100644 drivers/SmartThings/matter-switch/src/test/test_third_reality_mk1.lua create mode 100644 drivers/SmartThings/matter-switch/src/test/test_third_reality_subdrivers.lua diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 8637b37713..930e38e559 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -269,6 +269,11 @@ matterManufacturer: vendorId: 0x1407 productId: 0x1388 deviceProfileName: 12-button-keyboard + - id: "5127/4248" + deviceLabel: Smart Garage Door Opener + vendorId: 0x1407 + productId: 0x1098 + deviceProfileName: garage-door-battery #Edishine - id: "5458/3328" diff --git a/drivers/SmartThings/matter-switch/profiles/garage-door-battery.yml b/drivers/SmartThings/matter-switch/profiles/garage-door-battery.yml new file mode 100644 index 0000000000..bbab3c16ad --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/garage-door-battery.yml @@ -0,0 +1,14 @@ +name: garage-door-battery +components: +- id: main + capabilities: + - id: doorControl + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: GarageDoor diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index e2a04e103a..577820c171 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -375,6 +375,7 @@ local matter_driver_template = { 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_garage_door"), switch_utils.lazy_load_if_possible("sub_drivers.third_reality_mk1") }, shared_device_thread_enabled = true, diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_garage_door/can_handle.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_garage_door/can_handle.lua new file mode 100644 index 0000000000..2e1b734406 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_garage_door/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local utils = require "switch_utils.utils" + +return function(opts, driver, device) + if utils.get_product_override_field(device, "is_third_reality_garage_door") then + return true, require("sub_drivers.third_reality_garage_door") + end + return false +end diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_garage_door/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_garage_door/init.lua new file mode 100644 index 0000000000..4666d091bc --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_garage_door/init.lua @@ -0,0 +1,92 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" + +------------------------------------------------------------------------------------- +-- Third Reality Garage Door Opener specifics +-- +-- This device uses the OnOff cluster to control the door: +-- OnOff = true -> door open +-- OnOff = false -> door closed +-- Commands are mapped from doorControl capability to OnOff cluster commands. +------------------------------------------------------------------------------------- + +local function device_init(driver, device) + -- Force a subscription to the OnOff cluster, since doorControl does not explicitly map to it in the default driver. + device:add_subscribed_attribute(clusters.OnOff.attributes.OnOff) + device:add_subscribed_attribute(clusters.PowerSource.attributes.BatPercentRemaining) + device:subscribe() +end + +local function match_profile(driver, device) + device:try_update_metadata({profile = "garage-door-battery"}) +end + +-- Prevent any of the main driver's logic from running +local function device_added(driver, device) end + +-- Prevent any of the main driver's logic from running +local function info_changed(driver, device, event, args) end + +local function do_configure(driver, device) + match_profile(driver, device) +end + +local function driver_switched(driver, device) + match_profile(driver, device) + device:try_update_metadata({provisioning_state = "PROVISIONED"}) +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.doorControl.door.open()) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.doorControl.door.closed()) + end +end + +local function handle_door_open(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:emit_event_for_endpoint(endpoint_id, capabilities.doorControl.door.opening()) + device:send(clusters.OnOff.server.commands.On(device, endpoint_id)) +end + +local function handle_door_close(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:emit_event_for_endpoint(endpoint_id, capabilities.doorControl.door.closing()) + device:send(clusters.OnOff.server.commands.Off(device, endpoint_id)) +end + +local third_reality_garage_door_handler = { + NAME = "ThirdReality Garage Door Handler", + lifecycle_handlers = { + init = device_init, + added = device_added, + doConfigure = do_configure, + driverSwitched = driver_switched, + infoChanged = info_changed, + }, + matter_handlers = { + attr = { + [clusters.OnOff.ID] = { + [clusters.OnOff.attributes.OnOff.ID] = on_off_attr_handler, + }, + }, + }, + capability_handlers = { + [capabilities.doorControl.ID] = { + [capabilities.doorControl.commands.open.NAME] = handle_door_open, + [capabilities.doorControl.commands.close.NAME] = handle_door_close, + }, + }, + supported_capabilities = { + capabilities.doorControl, + capabilities.battery, + }, + can_handle = require("sub_drivers.third_reality_garage_door.can_handle") +} + +return third_reality_garage_door_handler 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 index f3f5342989..f68e568639 100644 --- 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 @@ -1,13 +1,10 @@ -- Copyright 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 -local device_lib = require "st.device" +local utils = require "switch_utils.utils" 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 + if utils.get_product_override_field(device, "is_third_reality_mk1") then return true, require("sub_drivers.third_reality_mk1") end return false 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 9c80c247be..8ba2bd28a1 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 @@ -81,6 +81,7 @@ end local function driver_switched(driver, device) match_profile(driver, device) + device:try_update_metadata({provisioning_state = "PROVISIONED"}) end local function initial_press_event_handler(driver, device, ib, response) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 02c864fa4f..a8e9781172 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -120,9 +120,13 @@ SwitchFields.vendor_overrides = { [0x000C] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, [0x000D] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, }, - [0x1209] = { -- Bosch - [0x3013] = {target_profile = "light-level-battery-illuminance-motion-temperature"} - } + [0x1209] = { -- BOSCH_MANUFACTURER_ID + [0x3013] = {target_profile = "light-level-battery-illuminance-motion-temperature"}, + }, + [0x1407] = { -- THIRD_REALITY_MANUFACTURER_ID + [0x1098] = { is_third_reality_garage_door = true }, + [0x1388] = { is_third_reality_mk1 = true}, + }, } SwitchFields.switch_category_vendor_overrides = { 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 deleted file mode 100644 index b7e94797f6..0000000000 --- a/drivers/SmartThings/matter-switch/src/test/test_third_reality_mk1.lua +++ /dev/null @@ -1,241 +0,0 @@ --- Copyright © 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local capabilities = require "st.capabilities" -local clusters = require "st.matter.clusters" -local dkjson = require "dkjson" -local t_utils = require "integration_test.utils" -local test = require "integration_test" -local utils = require "st.utils" - -local mock_device = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("12-button-keyboard.yml"), - manufacturer_info = {vendor_id = 0x1407, product_id = 0x1388}, - 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, - 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, - 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.Feature.MOMENTARY_SWITCH, - 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, - 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, - 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.Feature.MOMENTARY_SWITCH, - 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, - 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, - 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.Feature.MOMENTARY_SWITCH, - cluster_type = "SERVER" - } - }, - device_types = { - {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch - } - }, - { - endpoint_id = 10, - clusters = { - { - cluster_id = clusters.Switch.ID, - feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH, - cluster_type = "SERVER" - } - }, - device_types = { - {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch - } - }, - { - endpoint_id = 11, - clusters = { - { - cluster_id = clusters.Switch.ID, - feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH, - cluster_type = "SERVER" - } - }, - device_types = { - {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch - } - }, - { - endpoint_id = 12, - clusters = { - { - cluster_id = clusters.Switch.ID, - feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH, - cluster_type = "SERVER" - } - }, - device_types = { - {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch - } - } - } -}) - -local function configure_buttons() - for key = 1, 12 do - local component = "F" .. key - if key == 1 then component = "main" end - test.socket.capability:__expect_send(mock_device:generate_test_message(component, capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) - end -end - -local function test_init() - test.disable_startup_messages() - test.mock_device.add_test_device(mock_device) - local cluster_subscribe_list = { - clusters.Switch.events.InitialPress - } - 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.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 = "12-button-keyboard" }) - mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - configure_buttons() - - local device_info_copy = utils.deep_copy(mock_device.raw_st_data) - device_info_copy.profile.id = "12-buttons-keyboard" - local device_info_json = dkjson.encode(device_info_copy) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json }) - configure_buttons() - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) -end - -test.set_test_init_function(test_init) - -test.register_coroutine_test( - "Handle single press sequence", - function() - for key = 1, 12 do - test.socket.matter:__queue_receive({ - mock_device.id, - clusters.Switch.events.InitialPress:build_test_event_report(mock_device, key, {new_position = 1}) - }) - test.socket.capability:__expect_send( - mock_device:generate_test_message(key == 1 and "main" or "F" .. key, capabilities.button.button.pushed({state_change = true})) - ) - end - end, - { - min_api_version = 17 - } -) - --- run the tests -test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/test/test_third_reality_subdrivers.lua b/drivers/SmartThings/matter-switch/src/test/test_third_reality_subdrivers.lua new file mode 100644 index 0000000000..a32ce8f8ff --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_third_reality_subdrivers.lua @@ -0,0 +1,533 @@ +-- Copyright © 2026 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 dkjson = require "dkjson" +local utils = require "st.utils" + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("garage-door-battery.yml"), + manufacturer_info = {vendor_id = 0x1407, product_id = 0x1098}, + 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.PowerSource.ID, + cluster_type = "SERVER", + feature_map = clusters.PowerSource.types.Feature.BATTERY, + }, + }, + device_types = { + {device_type_id = 0x010A, device_type_revision = 1} -- On/Off Plug-in Unit + } + } + } +}) + +-- The subscribe list matches the profile capabilities: +-- doorControl -> OnOff.attributes.OnOff +-- battery -> PowerSource.attributes.BatPercentRemaining +-- The subdriver overrides device_init, so the main driver's extend_device("subscribe", ...) +-- is not called; the default device:subscribe() is used, which does not add AttributeList. +local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.PowerSource.attributes.BatPercentRemaining, +} + +local function test_init() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) + + 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 + + -- added lifecycle: subdriver overrides device_added to a no-op so no subscribe here + test.socket.device_lifecycle:__queue_receive({mock_device.id, "added"}) + + -- init lifecycle: device_init subscribes + test.socket.device_lifecycle:__queue_receive({mock_device.id, "init"}) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + + -- doConfigure: sets battery support field and updates profile metadata + test.socket.device_lifecycle:__queue_receive({mock_device.id, "doConfigure"}) + mock_device:expect_metadata_update({profile = "garage-door-battery"}) + mock_device:expect_metadata_update({provisioning_state = "PROVISIONED"}) +end + +test.set_test_init_function(test_init) + +-- ── Attribute handler tests ────────────────────────────────────────────────── + +test.register_message_test( + "OnOff true should emit door.open", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, 1, true) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.doorControl.door.open()) + } + } +) + +test.register_message_test( + "OnOff false should emit door.closed", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, 1, false) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.doorControl.door.closed()) + } + } +) + +-- ── Capability command tests ───────────────────────────────────────────────── + +test.register_message_test( + "doorControl open command should emit opening then send OnOff.On", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + {capability = "doorControl", component = "main", command = "open", args = {}} + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.doorControl.door.opening()) + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.On(mock_device, 1) + } + } + } +) + +test.register_message_test( + "doorControl close command should emit closing then send OnOff.Off", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + {capability = "doorControl", component = "main", command = "close", args = {}} + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.doorControl.door.closing()) + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.Off(mock_device, 1) + } + } + } +) + +-- ── Battery attribute tests ─────────────────────────────────────────────────── + +test.register_message_test( + "BatPercentRemaining report should emit correct battery percentage", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data(mock_device, 1, 200) + } + }, + { + channel = "capability", + direction = "send", + -- BatPercentRemaining is in units of 0.5%, so 200 = 100% + message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) + } + } +) + +test.register_message_test( + "BatPercentRemaining report of 150 should emit 75% battery", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data(mock_device, 1, 150) + } + }, + { + channel = "capability", + direction = "send", + -- 150 * 0.5 = 75% + message = mock_device:generate_test_message("main", capabilities.battery.battery(75)) + } + } +) + +-- ── Profile / driverSwitched tests ──────────────────────────────────────────── + +test.register_coroutine_test( + "doConfigure should set garage-door-battery profile", + function() + test.socket.device_lifecycle:__queue_receive({mock_device.id, "doConfigure"}) + mock_device:expect_metadata_update({profile = "garage-door-battery"}) + mock_device:expect_metadata_update({provisioning_state = "PROVISIONED"}) + end, + {min_api_version = 17} +) + +test.register_coroutine_test( + "driverSwitched should restore garage-door-battery profile", + function() + test.socket.device_lifecycle:__queue_receive({mock_device.id, "driverSwitched"}) + mock_device:expect_metadata_update({profile = "garage-door-battery"}) + mock_device:expect_metadata_update({provisioning_state = "PROVISIONED"}) + end, + {min_api_version = 17} +) + + +local mock_device_misprofiled = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("light-binary.yml"), + manufacturer_info = {vendor_id = 0x1407, product_id = 0x1098}, + 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.PowerSource.ID, + cluster_type = "SERVER", + feature_map = clusters.PowerSource.types.Feature.BATTERY, + }, + }, + device_types = { + {device_type_id = 0x010A, device_type_revision = 1} -- On/Off Plug-in Unit + } + } + } +}) + +test.register_coroutine_test( + "doConfigure should correct profile if misprofiled", + function() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device_misprofiled) + + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_misprofiled) + for i, clus in ipairs(cluster_subscribe_list) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_misprofiled)) end + end + + -- added lifecycle: subdriver overrides device_added to a no-op so no subscribe here + test.socket.device_lifecycle:__queue_receive({mock_device_misprofiled.id, "added"}) + + -- init lifecycle: device_init subscribes + test.socket.device_lifecycle:__queue_receive({mock_device_misprofiled.id, "init"}) + test.socket.matter:__expect_send({mock_device_misprofiled.id, subscribe_request}) + + -- doConfigure: sets battery support field and updates profile metadata + test.socket.device_lifecycle:__queue_receive({mock_device_misprofiled.id, "doConfigure"}) + mock_device_misprofiled:expect_metadata_update({profile = "garage-door-battery"}) + mock_device_misprofiled:expect_metadata_update({provisioning_state = "PROVISIONED"}) + end, + { + test_init = function() test.mock_device.add_test_device(mock_device_misprofiled) end, + } +) + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("12-button-keyboard.yml"), + manufacturer_info = {vendor_id = 0x1407, product_id = 0x1388}, + 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, + 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, + 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.Feature.MOMENTARY_SWITCH, + 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, + 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, + 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.Feature.MOMENTARY_SWITCH, + 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, + 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, + 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.Feature.MOMENTARY_SWITCH, + cluster_type = "SERVER" + } + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 10, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH, + cluster_type = "SERVER" + } + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 11, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH, + cluster_type = "SERVER" + } + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 12, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH, + cluster_type = "SERVER" + } + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + } + } +}) + +local function configure_buttons() + for key = 1, 12 do + local component = "F" .. key + if key == 1 then component = "main" end + test.socket.capability:__expect_send(mock_device:generate_test_message(component, capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) + end +end + +local function test_init_mk1() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) + local cluster_subscribe_list = { + clusters.Switch.events.InitialPress + } + 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.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 = "12-button-keyboard" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + configure_buttons() + + local device_info_copy = utils.deep_copy(mock_device.raw_st_data) + device_info_copy.profile.id = "12-buttons-keyboard" + local device_info_json = dkjson.encode(device_info_copy) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json }) + configure_buttons() + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) +end + +test.register_coroutine_test( + "Handle single press sequence", + function() + for key = 1, 12 do + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report(mock_device, key, {new_position = 1}) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message(key == 1 and "main" or "F" .. key, capabilities.button.button.pushed({state_change = true})) + ) + end + end, + { + test_init = test_init_mk1, + min_api_version = 17 + } +) + +-- run the tests +test.run_registered_tests() From 95280338c049a9ef14b832c3af9da6e1549b517d Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Fri, 10 Jul 2026 15:41:02 -0500 Subject: [PATCH 437/538] Hue: fix issue with small color temp increments (#2771) Incrementing or decrementing the color temp by 1 can lead to the driver sending the Hue bridge the same value in mireds as the device is already set to, and the bridge not responding, which in turn leads to a spinning wheel in the app and eventually the value being reset to its previous value. This change emits the capability with the updated value without waiting for a response from the bridge. --------- Co-authored-by: Harrison Carter --- .../src/handlers/attribute_emitters.lua | 13 +++++++++ .../philips-hue/src/handlers/commands.lua | 2 ++ .../src/handlers/grouped_light_commands.lua | 27 +++++++++++-------- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/drivers/SmartThings/philips-hue/src/handlers/attribute_emitters.lua b/drivers/SmartThings/philips-hue/src/handlers/attribute_emitters.lua index f274ef37f0..05c9dbb7e9 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/attribute_emitters.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/attribute_emitters.lua @@ -158,6 +158,19 @@ local function _emit_light_events_inner(light_device, light_repr) end end +-- A sufficiently small color temp command can transform to the same value (in mireks) that a device already has, so the bridge +-- may not respond to said command. To avoid any odd behaviors in this case, we should emit a kelvin value change event immediately +function AttributeEmitters.emit_color_temp_when_mirek_unchanged(device, clamped_kelvin, mirek) + local current_color_temp = device:get_latest_state("main", capabilities.colorTemperature.ID, capabilities.colorTemperature.colorTemperature.NAME) + if current_color_temp then + local current_mirek = math.floor(utils.kelvin_to_mirek(current_color_temp)) + if current_mirek == mirek then + log.debug(string.format("Color temp change from %dK to %dK results in same mirek value (%d), emitting event directly", current_color_temp, clamped_kelvin, mirek)) + device:emit_event(capabilities.colorTemperature.colorTemperature(clamped_kelvin)) + end + end +end + function AttributeEmitters.connectivity_update(child_device, zigbee_status) if child_device == nil or (child_device and child_device.id == nil) then log.warn("Tried to emit attribute events for a device that has been deleted") diff --git a/drivers/SmartThings/philips-hue/src/handlers/commands.lua b/drivers/SmartThings/philips-hue/src/handlers/commands.lua index 2c55f39452..480f003769 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/commands.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/commands.lua @@ -7,6 +7,7 @@ st_utils.stringify_table = st_utils.stringify_table local Consts = require "consts" local Fields = require "fields" local HueColorUtils = require "utils.cie_utils" +local attribute_emitters = require "handlers.attribute_emitters" local utils = require "utils" @@ -154,6 +155,7 @@ local function do_color_temp_action(driver, device, args) local min = device:get_field(Fields.MIN_KELVIN) or Consts.MIN_TEMP_KELVIN_WHITE_AMBIANCE local clamped_kelvin = st_utils.clamp_value(kelvin, min, Consts.MAX_TEMP_KELVIN) local mirek = math.floor(utils.kelvin_to_mirek(clamped_kelvin)) + attribute_emitters.emit_color_temp_when_mirek_unchanged(device, clamped_kelvin, mirek) local resp, err = hue_api:set_light_color_temp(light_id, mirek) log_command_response_errors(resp, err, "color temp action") diff --git a/drivers/SmartThings/philips-hue/src/handlers/grouped_light_commands.lua b/drivers/SmartThings/philips-hue/src/handlers/grouped_light_commands.lua index 1a1099fc5e..266971b99c 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/grouped_light_commands.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/grouped_light_commands.lua @@ -4,6 +4,7 @@ local Consts = require "consts" local Fields = require "fields" local HueColorUtils = require "utils.cie_utils" local grouped_utils = require "utils.grouped_utils" +local attribute_emitters = require "handlers.attribute_emitters" local utils = require "utils" @@ -94,7 +95,7 @@ end ---@param bridge_device HueBridgeDevice ---@param group table ---@param args table ----@param aux table auxilary data needed for the command that the devices all had in common +---@param aux table auxiliary data needed for the command that the devices all had in common local function do_color_action(driver, bridge_device, group, args, aux) local hue, sat = (args.args.color.hue / 100), (args.args.color.saturation / 100) if hue == 1 then -- 0 and 360 degrees are equivalent in HSV, but not in our conversion function @@ -135,7 +136,7 @@ end ---@param bridge_device HueBridgeDevice ---@param group table ---@param args table ----@param aux table auxilary data needed for the command that the devices all had in common +---@param aux table auxiliary data needed for the command that the devices all had in common local function do_setHue_action(driver, bridge_device, group, args, aux) local currentSaturation = aux[Fields.COLOR_SATURATION] or 0 args.args.color = { @@ -149,7 +150,7 @@ end ---@param bridge_device HueBridgeDevice ---@param group table ---@param args table ----@param aux table auxilary data needed for the command that the devices all had in common +---@param aux table auxiliary data needed for the command that the devices all had in common local function do_setSaturation_action(driver, bridge_device, group, args, aux) local currentHue = aux[Fields.COLOR_HUE] or 0 args.args.color = { @@ -163,7 +164,7 @@ end ---@param bridge_device HueBridgeDevice ---@param group table ---@param args table ----@param aux table auxilary data needed for the command that the devices all had in common +---@param aux table auxiliary data needed for the command that the devices all had in common local function do_color_temp_action(driver, bridge_device, group, args, aux) local kelvin = args.args.temperature @@ -185,6 +186,10 @@ local function do_color_temp_action(driver, bridge_device, group, args, aux) local clamped_kelvin = st_utils.clamp_value(kelvin, min, Consts.MAX_TEMP_KELVIN) local mirek = math.floor(utils.kelvin_to_mirek(clamped_kelvin)) + for _, device in ipairs(group.devices) do + attribute_emitters.emit_color_temp_when_mirek_unchanged(device, clamped_kelvin, mirek) + end + local resp, err = hue_api:set_grouped_light_color_temp(grouped_light_id, mirek) if not resp or (resp.errors and #resp.errors == 0) then @@ -203,7 +208,7 @@ end ---@param bridge_device HueBridgeDevice ---@param group table ---@param args table ----@param aux table auxilary data needed for the command that the devices all had in common +---@param aux table auxiliary data needed for the command that the devices all had in common function GroupedLightCommandHandlers.switch_on_handler(driver, bridge_device, group, args, aux) do_switch_action(driver, bridge_device, group, args) end @@ -212,7 +217,7 @@ end ---@param bridge_device HueBridgeDevice ---@param group table ---@param args table ----@param aux table auxilary data needed for the command that the devices all had in common +---@param aux table auxiliary data needed for the command that the devices all had in common function GroupedLightCommandHandlers.switch_off_handler(driver, bridge_device, group, args, aux) do_switch_action(driver, bridge_device, group, args) end @@ -221,7 +226,7 @@ end ---@param bridge_device HueBridgeDevice ---@param group table ---@param args table ----@param aux table auxilary data needed for the command that the devices all had in common +---@param aux table auxiliary data needed for the command that the devices all had in common function GroupedLightCommandHandlers.switch_level_handler(driver, bridge_device, group, args, aux) do_switch_level_action(driver, bridge_device, group, args) end @@ -230,7 +235,7 @@ end ---@param bridge_device HueBridgeDevice ---@param group table ---@param args table ----@param aux table auxilary data needed for the command that the devices all had in common +---@param aux table auxiliary data needed for the command that the devices all had in common function GroupedLightCommandHandlers.set_color_handler(driver, bridge_device, group, args, aux) do_color_action(driver, bridge_device, group, args, aux) end @@ -239,7 +244,7 @@ end ---@param bridge_device HueBridgeDevice ---@param group table ---@param args table ----@param aux table auxilary data needed for the command that the devices all had in common +---@param aux table auxiliary data needed for the command that the devices all had in common function GroupedLightCommandHandlers.set_hue_handler(driver, bridge_device, group, args, aux) do_setHue_action(driver, bridge_device, group, args, aux) end @@ -248,7 +253,7 @@ end ---@param bridge_device HueBridgeDevice ---@param group table ---@param args table ----@param aux table auxilary data needed for the command that the devices all had in common +---@param aux table auxiliary data needed for the command that the devices all had in common function GroupedLightCommandHandlers.set_saturation_handler(driver, bridge_device, group, args, aux) do_setSaturation_action(driver, bridge_device, group, args, aux) end @@ -257,7 +262,7 @@ end ---@param bridge_device HueBridgeDevice ---@param group table ---@param args table ----@param aux table auxilary data needed for the command that the devices all had in common +---@param aux table auxiliary data needed for the command that the devices all had in common function GroupedLightCommandHandlers.set_color_temp_handler(driver, bridge_device, group, args, aux) do_color_temp_action(driver, bridge_device, group, args, aux) end From cd56d787c8876971c44a73e9dfbc77eae789a875 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:19:20 -0500 Subject: [PATCH 438/538] Matter Ikea Scroll: Add rotation sensitivity preferences (#3100) --- .../matter-switch/profiles/ikea-scroll.yml | 9 +- .../scroll_handlers/event_handlers.lua | 6 +- .../ikea_scroll/scroll_utils/event_utils.lua | 14 ++ .../ikea_scroll/scroll_utils/fields.lua | 7 + .../src/test/test_ikea_scroll.lua | 196 ++++++++++++++++++ 5 files changed, 230 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml index 973c789ac6..1793ae209e 100644 --- a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml +++ b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml @@ -33,6 +33,13 @@ components: version: 1 categories: - name: Button +preferences: + - preferenceId: knobSensitivityGroup1 + explicit: true + - preferenceId: knobSensitivityGroup2 + explicit: true + - preferenceId: knobSensitivityGroup3 + explicit: true metadata: mnmn: SmartThingsEdge - vid: ikea-scroll \ No newline at end of file + vid: ikea-scroll diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua index dbad323784..1fac107a75 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua @@ -10,12 +10,16 @@ local event_utils = require "sub_drivers.ikea_scroll.scroll_utils.event_utils" local IkeaScrollEventHandlers = {} + local function rotate_amount_event_helper(device, endpoint_id, num_presses_to_handle) if num_presses_to_handle <= 0 then return end -- to cut down on checks, we can assume that if the endpoint is not in ENDPOINTS_UP_SCROLL, it is in ENDPOINTS_DOWN_SCROLL local scroll_direction = switch_utils.tbl_contains(scroll_fields.ENDPOINTS_UP_SCROLL, endpoint_id) and 1 or -1 - local scroll_amount = st_utils.clamp_value(scroll_direction * scroll_fields.PER_SCROLL_EVENT_ROTATION * num_presses_to_handle, -100, 100) + local scroll_amount = st_utils.clamp_value( + event_utils.rotate_amount_scaling_helper(device, endpoint_id, scroll_direction * scroll_fields.PER_SCROLL_EVENT_ROTATION * num_presses_to_handle), + -100, 100 + ) if event_utils.is_valid_scroll_amount(device, scroll_amount) then device:emit_event_for_endpoint(endpoint_id, capabilities.knob.rotateAmount(scroll_amount, {state_change = true})) diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua index 8a7a684cf6..d5568d2c9f 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua @@ -73,4 +73,18 @@ function IkeaScrollEventUtils.aggregate_scroll_amount_for_info_blocks(device, in return { total_presses = total_presses, presses_in_current_chain = presses_in_current_chain } end +function IkeaScrollEventUtils.rotate_amount_scaling_helper(device, endpoint_id, scroll_amount) + local scale_factor = 1.0 -- the default scale factor is 1.0, i.e none + local component = device:endpoint_to_component(endpoint_id) + if component == "main" then + scale_factor = scroll_fields.KNOB_SENSITIVITY_PREFERENCE_FACTORS[device.preferences.knobSensitivityGroup1] or 1.0 + elseif component == "group2" then + scale_factor = scroll_fields.KNOB_SENSITIVITY_PREFERENCE_FACTORS[device.preferences.knobSensitivityGroup2] or 1.0 + elseif component == "group3" then + scale_factor = scroll_fields.KNOB_SENSITIVITY_PREFERENCE_FACTORS[device.preferences.knobSensitivityGroup3] or 1.0 + end + local scaled_scroll_amount = st_utils.clamp_value(math.floor(scroll_amount * scale_factor), -100, 100) + return scaled_scroll_amount +end + return IkeaScrollEventUtils 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 index 27e0dbcd6d..2445e2fd30 100644 --- 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 @@ -33,6 +33,13 @@ IkeaScrollFields.CLEAR_STATE_TIMER = "__clear_state_timer" -- Delay in seconds to wait before clearing the global rotate amount state after the last scroll event IkeaScrollFields.CLEAR_STATE_DELAY_S = 8 +-- Amounts by which to multiply the scroll amount based on user preference for sensitivity +IkeaScrollFields.KNOB_SENSITIVITY_PREFERENCE_FACTORS = { + ["0"] = 0.5, -- Low + ["1"] = 1.0, -- Medium + ["2"] = 2.0 -- High +} + -- Required Events for the ENDPOINTS_PUSH. IkeaScrollFields.switch_press_subscribed_events = { clusters.Switch.events.InitialPress.ID, diff --git a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua index f421b17f79..be28bd040a 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua @@ -749,4 +749,200 @@ test.register_coroutine_test( end ) +-- Coroutine tests for sensitivity preference logic. +-- KNOB_SENSITIVITY_FACTORS = {0.5, 1.0, 2.0}, indexed by the preference value. +-- nil preference falls back to 1.0 (tested by the message tests above). + +test.register_coroutine_test( + "Ikea Scroll low sensitivity (factor 0.5) scales rotateAmount correctly on main", + function() + test.socket.device_lifecycle():__queue_receive(mock_ikea_scroll:generate_info_changed({ preferences = { knobSensitivityGroup1 = "0" } })) + test.wait_for_events() + + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {new_position = 1} + ) + }) + + -- MultiPressOngoing count=2 (delta=2) → floor(12 * 0.5) = 6 + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {current_number_of_presses_counted = 2, new_position = 2} + ) + }) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(6, {state_change = true}))) + + -- MultiPressOngoing count=5 (delta=3) → floor(18 * 0.5) = 9 + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {current_number_of_presses_counted = 5, new_position = 5} + ) + }) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(9, {state_change = true}))) + + -- MultiPressComplete total=5 (delta=0, no event emitted) + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {new_position = 5, total_number_of_presses_counted = 5, previous_position = 0} + ) + }) + + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Ikea Scroll high sensitivity (factor 2.0) scales rotateAmount correctly on main", + function() + test.socket.device_lifecycle():__queue_receive(mock_ikea_scroll:generate_info_changed({ preferences = { knobSensitivityGroup1 = "2" } })) + test.wait_for_events() + + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {new_position = 1} + ) + }) + + -- MultiPressOngoing count=2 (delta=2) → floor(12 * 2.0) = 24 + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {current_number_of_presses_counted = 2, new_position = 2} + ) + }) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(24, {state_change = true}))) + + -- MultiPressOngoing count=5 (delta=3) → floor(18 * 2.0) = 36 + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {current_number_of_presses_counted = 5, new_position = 5} + ) + }) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(36, {state_change = true}))) + + -- MultiPressComplete total=5 (delta=0, no event emitted) + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {new_position = 5, total_number_of_presses_counted = 5, previous_position = 0} + ) + }) + + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Ikea Scroll high sensitivity (factor 2.0) scales rotateAmount correctly on group2", + function() + test.socket.device_lifecycle():__queue_receive(mock_ikea_scroll:generate_info_changed({ preferences = { knobSensitivityGroup2 = "2" } })) + test.wait_for_events() + + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[3], {new_position = 1} + ) + }) + + -- MultiPressOngoing count=2 (delta=2) → floor(12 * 2.0) = 24 + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[3], {current_number_of_presses_counted = 2, new_position = 2} + ) + }) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(24, {state_change = true}))) + + -- MultiPressOngoing count=5 (delta=3) → floor(18 * 2.0) = 36 + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[3], {current_number_of_presses_counted = 5, new_position = 5} + ) + }) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(36, {state_change = true}))) + + -- MultiPressComplete total=5 (delta=0, no event emitted) + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[3], {new_position = 5, total_number_of_presses_counted = 5, previous_position = 0} + ) + }) + + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Ikea Scroll low sensitivity (factor 0.5) scales rotateAmount correctly on group3", + function() + test.socket.device_lifecycle():__queue_receive(mock_ikea_scroll:generate_info_changed({ preferences = { knobSensitivityGroup3 = "0" } })) + test.wait_for_events() + + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[5], {new_position = 1} + ) + }) + + -- MultiPressOngoing count=2 (delta=2) → floor(12 * 0.5) = 6 + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[5], {current_number_of_presses_counted = 2, new_position = 2} + ) + }) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(6, {state_change = true}))) + + -- MultiPressOngoing count=5 (delta=3) → floor(18 * 0.5) = 9 + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[5], {current_number_of_presses_counted = 5, new_position = 5} + ) + }) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(9, {state_change = true}))) + + -- MultiPressComplete total=5 (delta=0, no event emitted) + test.socket.matter:__queue_receive({ + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[5], {new_position = 5, total_number_of_presses_counted = 5, previous_position = 0} + ) + }) + + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + test.run_registered_tests() From db11e084578211ee7321295be280947ae3ca6043 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 13 Jul 2026 18:15:46 -0500 Subject: [PATCH 439/538] Zigbee Humidity Sensor: Add category to profile (#3117) --- .../profiles/frient-airquality-humidity-temperature-battery.yml | 2 ++ 1 file changed, 2 insertions(+) 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 index 08774c0d31..1df453e4aa 100644 --- 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 @@ -27,6 +27,8 @@ components: version: 1 - id: refresh version: 1 + categories: + - name: AirQualityDetector preferences: - preferenceId: humidityOffset explicit: true From 9a0c5866129a766d234adeed537b7ecbb061bcad Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 14 Jul 2026 11:53:21 -0500 Subject: [PATCH 440/538] WWSTCERT-12300 Aqara Smart Lock U500 (#3036) * WWSTCERT-12300 Aqara Smart Lock U500 * Update Aqara lock fingerprint descriptions * Fix U500 fingerprint profile * Remove obsolete matter-lock fingerprints file * chore: retrigger PR checks --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 6d4d068b71..bfa19905a5 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -25,6 +25,11 @@ matterManufacturer: vendorId: 0x115F productId: 0x2804 deviceProfileName: lock + - id: "4447/10254" + deviceLabel: Aqara Smart Lock U500 + vendorId: 0x115F + productId: 0x280E + deviceProfileName: lock-modular - id: "4447/10255" deviceLabel: Aqara Smart Lock U500 Rim vendorId: 0x115F From 7853d5452036db05d4cde9aaa97de4d5d6b93dfd Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 04:53:12 +0800 Subject: [PATCH 441/538] add firstled-io_M2S2BAC (#3054) --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 3 ++- tools/localizations/cn.csv | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 1d67094618..b7a6852a7d 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,11 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/M2S2BAC" + deviceLabel: Mirror Series 2x2 1 + manufacturer: FIRSTLED + model: M2S2BAC + deviceProfileName: switch-light-restore-wireless - id: "ManDai Technology/DC2DC12MiV1" deviceLabel: Easy-Fit Driver manufacturer: ManDai Technology diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 2586782642..9ae9dfa66c 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -5,5 +5,6 @@ --The number of buttons determines how many buttons devices will be created. --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { - { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" } + { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "M2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" } } diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 007f8059b5..ab18642a6a 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,4 +141,5 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Mirror Series 2x2 1",镜系列2x2 1 "WALLHERO Thermostat ACL-403STC1",墙壁侠温控ACL-403STC1 From ba49f38b8900912836b7bddc4cbdb089b0882150 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 06:04:33 +0800 Subject: [PATCH 442/538] add firstled-io_M1S1BAC (#3055) * add firstled-io_M1S1BAC * Fix duplicate entry for FIRSTLED M1S1BAC model --------- Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 4 ++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index b7a6852a7d..80f3e79740 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,10 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/M1S1BAC" + deviceLabel: Mirror Series 1x1 1 + manufacturer: FIRSTLED + model: M1S1BAC - id: "FIRSTLED/M2S2BAC" deviceLabel: Mirror Series 2x2 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 9ae9dfa66c..0ad15cbd1c 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,5 +6,6 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "M1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "M2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" } } diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index ab18642a6a..be1ffa7247 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,5 +141,6 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Mirror Series 1x1 1",镜系列1x1 1 "Mirror Series 2x2 1",镜系列2x2 1 "WALLHERO Thermostat ACL-403STC1",墙壁侠温控ACL-403STC1 From 3ce648328656c844b4df88bbc7a505c1480087a7 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 06:17:24 +0800 Subject: [PATCH 443/538] add firstled-io_DL1S1BAC (#3056) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 4 ++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 80f3e79740..8fdd8850f4 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,10 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/DL1S1BAC" + deviceLabel: Dynamic Lite Series 1x1 1 + manufacturer: FIRSTLED + model: DL1S1BAC - id: "FIRSTLED/M1S1BAC" deviceLabel: Mirror Series 1x1 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 0ad15cbd1c..49ceca6976 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "DL1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "M1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "M2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" } } diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index be1ffa7247..d25b167bfd 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Dynamic Lite Series 1x1 1",灵动L系列1x1 1 "Mirror Series 1x1 1",镜系列1x1 1 "Mirror Series 2x2 1",镜系列2x2 1 "WALLHERO Thermostat ACL-403STC1",墙壁侠温控ACL-403STC1 From d550b8fda7ca225c41ce3057211c474e25661946 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 06:23:59 +0800 Subject: [PATCH 444/538] add firstled-io_DL2S2BAC (#3057) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 4 ++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 8fdd8850f4..39ae5dcf8a 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,10 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/DL2S2BAC" + deviceLabel: Dynamic Lite Series 2x2 1 + manufacturer: FIRSTLED + model: DL2S2BAC - id: "FIRSTLED/DL1S1BAC" deviceLabel: Dynamic Lite Series 1x1 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 49ceca6976..024d232795 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "DL2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "DL1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "M1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "M2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" } diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index d25b167bfd..5131fc25ff 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Dynamic Lite Series 2x2 1",灵动L系列2x2 1 "Dynamic Lite Series 1x1 1",灵动L系列1x1 1 "Mirror Series 1x1 1",镜系列1x1 1 "Mirror Series 2x2 1",镜系列2x2 1 From 7c292203c434ea1bff638aa7c26447dec628143e Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 06:38:41 +0800 Subject: [PATCH 445/538] add firstled-io_DL3S3BAC (#3058) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 4 ++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 39ae5dcf8a..9c949efd30 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,10 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/DL3S3BAC" + deviceLabel: Dynamic Lite Series 3x3 1 + manufacturer: FIRSTLED + model: DL3S3BAC - id: "FIRSTLED/DL2S2BAC" deviceLabel: Dynamic Lite Series 2x2 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 024d232795..2cbe62735c 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "DL3S3BAC", children = 3, buttons = 3, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "DL2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "DL1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "M1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 5131fc25ff..5e136800b6 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Dynamic Lite Series 3x3 1",灵动L系列3x3 1 "Dynamic Lite Series 2x2 1",灵动L系列2x2 1 "Dynamic Lite Series 1x1 1",灵动L系列1x1 1 "Mirror Series 1x1 1",镜系列1x1 1 From ac1f0b7425927d9092864ed44ef735620f3c2ebc Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 06:48:07 +0800 Subject: [PATCH 446/538] add firstled-io_DL4S4BAC (#3059) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 4 ++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 9c949efd30..4f90d9bac1 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,10 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/DL4S4BAC" + deviceLabel: Dynamic Lite Series 4x4 1 + manufacturer: FIRSTLED + model: DL4S4BAC - id: "FIRSTLED/DL3S3BAC" deviceLabel: Dynamic Lite Series 3x3 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 2cbe62735c..c29f174f99 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "DL4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "DL3S3BAC", children = 3, buttons = 3, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "DL2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "DL1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 5e136800b6..d5b3024c22 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Dynamic Lite Series 4x4 1",灵动L系列4x4 1 "Dynamic Lite Series 3x3 1",灵动L系列3x3 1 "Dynamic Lite Series 2x2 1",灵动L系列2x2 1 "Dynamic Lite Series 1x1 1",灵动L系列1x1 1 From 6e2441021cc28d32e2a6aa817ebd78be08cf5319 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 07:03:42 +0800 Subject: [PATCH 447/538] add firstled-io_G1S1BAC (#3060) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 4 ++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 4f90d9bac1..885b084409 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,10 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/G1S1BAC" + deviceLabel: Guanzhi Series 1x1 1 + manufacturer: FIRSTLED + model: G1S1BAC - id: "FIRSTLED/DL4S4BAC" deviceLabel: Dynamic Lite Series 4x4 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index c29f174f99..7ac6eaf468 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "G1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "DL4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "DL3S3BAC", children = 3, buttons = 3, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "DL2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index d5b3024c22..ef3ff55900 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Guanzhi Series 1x1 1",观致系列1x1 1 "Dynamic Lite Series 4x4 1",灵动L系列4x4 1 "Dynamic Lite Series 3x3 1",灵动L系列3x3 1 "Dynamic Lite Series 2x2 1",灵动L系列2x2 1 From fd65fe9e4145fcfd6e330702cf9f10722febf67b Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 09:11:30 +0800 Subject: [PATCH 448/538] add firstled-io_G2S2BAC (#3061) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 4 ++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 6 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 885b084409..fc69dcccd6 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,10 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/G2S2BAC" + deviceLabel: Guanzhi Series 2x2 1 + manufacturer: FIRSTLED + model: G2S2BAC - id: "FIRSTLED/G1S1BAC" deviceLabel: Guanzhi Series 1x1 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 7ac6eaf468..b19894cf1e 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "G2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "G1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "DL4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "DL3S3BAC", children = 3, buttons = 3, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index ef3ff55900..c17d0042f8 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Guanzhi Series 2x2 1",观致系列2x2 1 "Guanzhi Series 1x1 1",观致系列1x1 1 "Dynamic Lite Series 4x4 1",灵动L系列4x4 1 "Dynamic Lite Series 3x3 1",灵动L系列3x3 1 From 5da711f17cc8dccf3d591b906de89eba3cc236b5 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 19:27:08 +0800 Subject: [PATCH 449/538] add firstled-io_G3S3BAC (#3062) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 14 +++++++++++++- .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index fc69dcccd6..fe5963dd64 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,34 +2457,46 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/G3S3BAC" + deviceLabel: Guanzhi Series 3x3 1 + manufacturer: FIRSTLED + model: G3S3BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/G2S2BAC" deviceLabel: Guanzhi Series 2x2 1 manufacturer: FIRSTLED model: G2S2BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/G1S1BAC" deviceLabel: Guanzhi Series 1x1 1 manufacturer: FIRSTLED model: G1S1BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/DL4S4BAC" deviceLabel: Dynamic Lite Series 4x4 1 manufacturer: FIRSTLED model: DL4S4BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/DL3S3BAC" deviceLabel: Dynamic Lite Series 3x3 1 manufacturer: FIRSTLED model: DL3S3BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/DL2S2BAC" deviceLabel: Dynamic Lite Series 2x2 1 manufacturer: FIRSTLED model: DL2S2BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/DL1S1BAC" deviceLabel: Dynamic Lite Series 1x1 1 manufacturer: FIRSTLED model: DL1S1BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/M1S1BAC" deviceLabel: Mirror Series 1x1 1 manufacturer: FIRSTLED model: M1S1BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/M2S2BAC" deviceLabel: Mirror Series 2x2 1 manufacturer: FIRSTLED @@ -2574,4 +2586,4 @@ zigbeeGeneric: - 0x0006 #OnOff - 0x0702 #Simple Metering - 0x0B04 #Electrical Measurment - deviceProfileName: switch-power \ No newline at end of file + deviceProfileName: switch-power diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index b19894cf1e..672ffd749d 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "G3S3BAC", children = 3, buttons = 3, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "G2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "G1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "DL4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index c17d0042f8..7560f4506d 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Guanzhi Series 3x3 1",观致系列3x3 1 "Guanzhi Series 2x2 1",观致系列2x2 1 "Guanzhi Series 1x1 1",观致系列1x1 1 "Dynamic Lite Series 4x4 1",灵动L系列4x4 1 From e44f98d32e6e16fa8da356e6aced71f428d5c3bd Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 19:43:26 +0800 Subject: [PATCH 450/538] add firstled-io_MX1S2BAC (#3063) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 7 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index fe5963dd64..4fec9c6290 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,11 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/MX1S2BAC" + deviceLabel: Matrix X Series 1x2 1 + manufacturer: FIRSTLED + model: MX1S2BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/G3S3BAC" deviceLabel: Guanzhi Series 3x3 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 672ffd749d..95705deff9 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "MX1S2BAC", children = 1, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "G3S3BAC", children = 3, buttons = 3, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "G2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "G1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 7560f4506d..257b486449 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Matrix X Series 1x2 1",矩阵系列1x2 1 "Guanzhi Series 3x3 1",观致系列3x3 1 "Guanzhi Series 2x2 1",观致系列2x2 1 "Guanzhi Series 1x1 1",观致系列1x1 1 From 91803c1aeaeb56c97a2150414c786ff197ef1eb1 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 19:49:26 +0800 Subject: [PATCH 451/538] add firstled-io_MX2S2BAC (#3064) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 7 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 4fec9c6290..293c311524 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,11 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/MX2S2BAC" + deviceLabel: Matrix X Series 2x2 1 + manufacturer: FIRSTLED + model: MX2S2BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/MX1S2BAC" deviceLabel: Matrix X Series 1x2 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 95705deff9..68593900ac 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "MX2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "MX1S2BAC", children = 1, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "G3S3BAC", children = 3, buttons = 3, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "G2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 257b486449..91d05a1b4f 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Matrix X Series 2x2 1",矩阵系列2x2 1 "Matrix X Series 1x2 1",矩阵系列1x2 1 "Guanzhi Series 3x3 1",观致系列3x3 1 "Guanzhi Series 2x2 1",观致系列2x2 1 From cc30a5eb5fb113f8d2844ee23eb15d0e0f1060dc Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 19:54:45 +0800 Subject: [PATCH 452/538] add firstled-io_MX3S2BAC (#3065) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 7 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 293c311524..61b139e873 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,11 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/MX3S2BAC" + deviceLabel: Matrix X Series 3x2 1 + manufacturer: FIRSTLED + model: MX3S2BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/MX2S2BAC" deviceLabel: Matrix X Series 2x2 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 68593900ac..361637739b 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "MX3S2BAC", children = 3, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "MX2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "MX1S2BAC", children = 1, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "G3S3BAC", children = 3, buttons = 3, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 91d05a1b4f..4cd241d156 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Matrix X Series 3x2 1",矩阵系列3x2 1 "Matrix X Series 2x2 1",矩阵系列2x2 1 "Matrix X Series 1x2 1",矩阵系列1x2 1 "Guanzhi Series 3x3 1",观致系列3x3 1 From bc3100649699fa49fb22de48cbba563d293715cc Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 20:01:56 +0800 Subject: [PATCH 453/538] add firstled-io_MX4S2BAC (#3066) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 7 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 61b139e873..a6b565216f 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,11 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/MX4S2BAC" + deviceLabel: Matrix X Series 4x2 1 + manufacturer: FIRSTLED + model: MX4S2BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/MX3S2BAC" deviceLabel: Matrix X Series 3x2 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 361637739b..1db3711245 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "MX4S2BAC", children = 4, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "MX3S2BAC", children = 3, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "MX2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "MX1S2BAC", children = 1, buttons = 2, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 4cd241d156..bb50903777 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Matrix X Series 4x2 1",矩阵系列4x2 1 "Matrix X Series 3x2 1",矩阵系列3x2 1 "Matrix X Series 2x2 1",矩阵系列2x2 1 "Matrix X Series 1x2 1",矩阵系列1x2 1 From 3fdc1cbbd19b115d84601b773d349b33edc640a1 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 20:07:29 +0800 Subject: [PATCH 454/538] add firstled-io_P1S1BAC (#3067) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 7 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index a6b565216f..117b85b72b 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,11 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/P1S1BAC" + deviceLabel: Punk Series 1x1 1 + manufacturer: FIRSTLED + model: P1S1BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/MX4S2BAC" deviceLabel: Matrix X Series 4x2 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 1db3711245..4264aeb477 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "P1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "MX4S2BAC", children = 4, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "MX3S2BAC", children = 3, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "MX2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index bb50903777..452bd6119a 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Punk Series 1x1 1",朋克系列1x1 1 "Matrix X Series 4x2 1",矩阵系列4x2 1 "Matrix X Series 3x2 1",矩阵系列3x2 1 "Matrix X Series 2x2 1",矩阵系列2x2 1 From 65ea2a8e9bc06fe8751716780a41829c2a02c92d Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 20:13:00 +0800 Subject: [PATCH 455/538] add firstled-io_P2S2BAC (#3068) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 7 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 117b85b72b..f5c6b29006 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,11 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/P2S2BAC" + deviceLabel: Punk Series 2x2 1 + manufacturer: FIRSTLED + model: P2S2BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/P1S1BAC" deviceLabel: Punk Series 1x1 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 4264aeb477..3e7ecf47f2 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "P2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "P1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "MX4S2BAC", children = 4, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "MX3S2BAC", children = 3, buttons = 2, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 452bd6119a..5906810402 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Punk Series 2x2 1",朋克系列2x2 1 "Punk Series 1x1 1",朋克系列1x1 1 "Matrix X Series 4x2 1",矩阵系列4x2 1 "Matrix X Series 3x2 1",矩阵系列3x2 1 From 664cef00e26baaf6fc2d1b87c7f8d0164bed7159 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 20:18:33 +0800 Subject: [PATCH 456/538] add firstled-io_P3S3BAC (#3069) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 7 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index f5c6b29006..fbd5adaaf0 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,11 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/P3S3BAC" + deviceLabel: Punk Series 3x3 1 + manufacturer: FIRSTLED + model: P3S3BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/P2S2BAC" deviceLabel: Punk Series 2x2 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 3e7ecf47f2..5b50a09368 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "P3S3BAC", children = 3, buttons = 3, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "P2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "P1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "MX4S2BAC", children = 4, buttons = 2, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 5906810402..f23ee9fe68 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Punk Series 3x3 1",朋克系列3x3 1 "Punk Series 2x2 1",朋克系列2x2 1 "Punk Series 1x1 1",朋克系列1x1 1 "Matrix X Series 4x2 1",矩阵系列4x2 1 From 2f1dcd9fb644e1b2088fd81d9040666a4d06a0c2 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 15 Jul 2026 20:57:20 +0800 Subject: [PATCH 457/538] add firstled-io_M3S3BAC (#3053) Co-authored-by: Chris Baumler --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ .../zigbee-switch/src/firstled-io/fingerprints.lua | 1 + tools/localizations/cn.csv | 1 + 3 files changed, 7 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index fbd5adaaf0..439bb5da28 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2457,6 +2457,11 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M4S4BAC deviceProfileName: switch-light-restore-wireless + - id: "FIRSTLED/M3S3BAC" + deviceLabel: Mirror Series 3x3 1 + manufacturer: FIRSTLED + model: M3S3BAC + deviceProfileName: switch-light-restore-wireless - id: "FIRSTLED/P3S3BAC" deviceLabel: Punk Series 3x3 1 manufacturer: FIRSTLED diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua index 5b50a09368..1b5722fa7c 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -6,6 +6,7 @@ --The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products. return { { mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }, + { mfr = "FIRSTLED", model = "M3S3BAC", children = 3, buttons = 3, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "P3S3BAC", children = 3, buttons = 3, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "P2S2BAC", children = 2, buttons = 2, child_profile = "switch-wireless" }, { mfr = "FIRSTLED", model = "P1S1BAC", children = 1, buttons = 1, child_profile = "switch-wireless" }, diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index f23ee9fe68..6c6aac0a81 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -141,6 +141,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 "Mirror Series 4x4 1",镜系列4x4 1 +"Mirror Series 3x3 1",镜系列3x3 1 "Punk Series 3x3 1",朋克系列3x3 1 "Punk Series 2x2 1",朋克系列2x2 1 "Punk Series 1x1 1",朋克系列1x1 1 From 7acd4d7ea9f7bcb36ce42efb91b762451f7363b5 Mon Sep 17 00:00:00 2001 From: JanJakubiszyn <101173721+JanJakubiszyn@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:15:28 +0200 Subject: [PATCH 458/538] fix: reapply Hager WAASYS device support (#3104) This reverts commit a85de74f33374ab9a5d436ff2ed2493fe6577ca3, reversing changes made to 52ae9953ccb435da4c27535ab58c7313f18fc12b. --- .../matter-switch/fingerprints.yml | 20 + .../profiles/motion-illuminance.yml | 14 + .../profiles/window-covering.yml | 21 + .../src/embedded_clusters/Descriptor/init.lua | 4 + .../server/attributes/DeviceTypeList.lua | 78 + .../Descriptor/types/DeviceTypeStruct.lua | 82 + .../Descriptor/types/init.lua | 17 + .../SmartThings/matter-switch/src/init.lua | 1 + .../src/sub_drivers/hager/can_handle.lua | 13 + .../src/sub_drivers/hager/init.lua | 483 +++++ .../switch_handlers/attribute_handlers.lua | 2 +- .../src/switch_utils/device_configuration.lua | 1 - .../matter-switch/src/switch_utils/fields.lua | 10 + .../src/test/test_hager_waasys.lua | 1780 +++++++++++++++++ 14 files changed, 2524 insertions(+), 2 deletions(-) create mode 100644 drivers/SmartThings/matter-switch/profiles/motion-illuminance.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/window-covering.yml create mode 100644 drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/server/attributes/DeviceTypeList.lua create mode 100644 drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/types/DeviceTypeStruct.lua create mode 100644 drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/types/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/hager/can_handle.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 930e38e559..d6a367c685 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -909,6 +909,26 @@ matterManufacturer: vendorId: 0x1285 productId: 0x0009 deviceProfileName: 4-button-batteryLevel + - id: "4741/5" + deviceLabel: Hager Switch 1G + vendorId: 0x1285 + productId: 0x0005 + deviceProfileName: matter-bridge + - id: "4741/6" + deviceLabel: Hager Switch 2G + vendorId: 0x1285 + productId: 0x0006 + deviceProfileName: matter-bridge + - id: "4741/7" + deviceLabel: Hager PIR 1.1M + vendorId: 0x1285 + productId: 0x0007 + deviceProfileName: matter-bridge + - id: "4741/10" + deviceLabel: Hager PIR 2.2M + vendorId: 0x1285 + productId: 0x000A + deviceProfileName: matter-bridge # HAOJAI - id: "5530/4113" deviceLabel: HAOJAI Smart Switch 3-key diff --git a/drivers/SmartThings/matter-switch/profiles/motion-illuminance.yml b/drivers/SmartThings/matter-switch/profiles/motion-illuminance.yml new file mode 100644 index 0000000000..dfe052043c --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/motion-illuminance.yml @@ -0,0 +1,14 @@ +name: motion-illuminance +components: +- id: main + capabilities: + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: MotionSensor diff --git a/drivers/SmartThings/matter-switch/profiles/window-covering.yml b/drivers/SmartThings/matter-switch/profiles/window-covering.yml new file mode 100644 index 0000000000..b588b41ec9 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/window-covering.yml @@ -0,0 +1,21 @@ +name: window-covering +components: +- id: main + capabilities: + - id: windowShade + version: 1 + - id: windowShadePreset + version: 1 + - id: windowShadeLevel + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Blind +preferences: + - preferenceId: presetPosition + explicit: true + - preferenceId: reverse + explicit: true diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/init.lua index fb7a38e63b..12419a9a26 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/init.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/init.lua @@ -3,6 +3,7 @@ local cluster_base = require "st.matter.cluster_base" local DescriptorServerAttributes = require "embedded_clusters.Descriptor.server.attributes" +local DescriptorTypes = require "embedded_clusters.Descriptor.types" local Descriptor = {} @@ -11,9 +12,11 @@ Descriptor.NAME = "Descriptor" Descriptor.server = {} Descriptor.client = {} Descriptor.server.attributes = DescriptorServerAttributes:set_parent_cluster(Descriptor) +Descriptor.types = DescriptorTypes function Descriptor:get_attribute_by_id(attr_id) local attr_id_map = { + [0x0000] = "DeviceTypeList", [0x0003] = "PartsList", } local attr_name = attr_id_map[attr_id] @@ -33,6 +36,7 @@ function Descriptor:get_server_command_by_id(command_id) end Descriptor.attribute_direction_map = { + ["DeviceTypeList"] = "server", ["PartsList"] = "server", } diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/server/attributes/DeviceTypeList.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/server/attributes/DeviceTypeList.lua new file mode 100644 index 0000000000..7bd42e15eb --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/server/attributes/DeviceTypeList.lua @@ -0,0 +1,78 @@ +-- Copyright © 2026 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 DeviceTypeList = { + ID = 0x0000, + NAME = "DeviceTypeList", + base_type = require "st.matter.data_types.Array", + element_type = require "embedded_clusters.Descriptor.types.DeviceTypeStruct", +} + +function DeviceTypeList: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, DeviceTypeList.element_type) + end +end + +function DeviceTypeList:new_value(...) + local o = self.base_type(table.unpack({ ... })) + return o +end + +function DeviceTypeList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function DeviceTypeList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function DeviceTypeList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function DeviceTypeList: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 DeviceTypeList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(DeviceTypeList, { __call = DeviceTypeList.new_value, __index = DeviceTypeList.base_type }) +return DeviceTypeList + diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/types/DeviceTypeStruct.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/types/DeviceTypeStruct.lua new file mode 100644 index 0000000000..0cb771558d --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/types/DeviceTypeStruct.lua @@ -0,0 +1,82 @@ +-- Copyright 2026 SmartThings +-- 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 DeviceTypeStruct = {} +local new_mt = StructureABC.new_mt({ NAME = "DeviceTypeStruct", ID = data_types.name_to_id_map["Structure"] }) + +DeviceTypeStruct.field_defs = { + { + name = "device_type", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint32", + }, + { + name = "revision", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint16", + }, +} + +DeviceTypeStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for _idx, field_def in ipairs(cls.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 tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif not (field_def.is_optional and tbl[field_def.name] == nil) then + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +DeviceTypeStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = DeviceTypeStruct.init +new_mt.__index.serialize = DeviceTypeStruct.serialize + +DeviceTypeStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + 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 + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(DeviceTypeStruct, new_mt) + +return DeviceTypeStruct + diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/types/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/types/init.lua new file mode 100644 index 0000000000..8ecaa987fd --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/types/init.lua @@ -0,0 +1,17 @@ +-- Copyright 2026 SmartThings +-- 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("embedded_clusters.Descriptor.types." .. key) + end + return types_mt.__types_cache[key] +end + +local DescriptorTypes = {} + +setmetatable(DescriptorTypes, types_mt) + +return DescriptorTypes diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 577820c171..9d4e22fb15 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -374,6 +374,7 @@ local matter_driver_template = { 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.hager"), switch_utils.lazy_load_if_possible("sub_drivers.ikea_scroll"), switch_utils.lazy_load_if_possible("sub_drivers.third_reality_garage_door"), switch_utils.lazy_load_if_possible("sub_drivers.third_reality_mk1") diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/hager/can_handle.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/can_handle.lua new file mode 100644 index 0000000000..3ac75a5805 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local device_lib = require "st.device" + local get_product_override_field = require "switch_utils.utils".get_product_override_field + + local checked_device = device.network_type == device_lib.NETWORK_TYPE_MATTER and device or device:get_parent_device() + if get_product_override_field(checked_device, "needs_hager_subdriver") then + return true, require("sub_drivers.hager") + end + return false +end diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua new file mode 100644 index 0000000000..db8cfe67d4 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua @@ -0,0 +1,483 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" +local device_lib = require "st.device" +local buttonCfg = require "switch_utils.device_configuration".ButtonCfg +local create_child = require "switch_utils.device_configuration".ChildCfg.create_or_update_child_devices +local switch_utils = require "switch_utils.utils" +local fields = require "switch_utils.fields" +local version = require "version" + +local ACTIVE_EPS = "__active_EPS" +local MATTER_DEVICE_ID = "MATTER_DEVICE_ID" +local PARENT_ID = "PARENT_ID" +local CURRENT_LIFT = "__current_lift" +local BUTTON_EPS = "__button_eps" +local MAIN_WC_EP = "__main_wc_ep" + +local PRODUCT_ID = { + SWITCH_1G = 0x0005, + SWITCH_2G = 0x0006, + PIR_1_1M = 0x0007, + PIR_2_2M = 0x000A, +} + +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" +end + +local function subscribe (device, endpoint_id, cluster_id, attr_id, event_id) + device:send(cluster_base.subscribe(device, endpoint_id, cluster_id, attr_id, event_id)) +end + +local function get_parent (driver, device) + return driver:get_device_info(device:get_field(PARENT_ID) or nil) +end + +local function get_matter_device(device) + local matter_device_id = device:get_field(MATTER_DEVICE_ID) + if matter_device_id then + local driver = device.driver + if driver then + local matter_device = driver:get_device_info(matter_device_id) + if matter_device then + return matter_device + end + end + end + return nil +end + +local function extract_reported_endpoints(ib) + local eps = {} + if ib.data and ib.data.elements then + for _, el in ipairs(ib.data.elements) do + local ep_id = el.value + if type(ep_id) == "number" and ep_id ~= 1 and ep_id ~= 2 then + table.insert(eps, ep_id) + end + end + end + return eps +end + +local function detect_endpoint_changes (device, ib_elements) + local stored_eps = device:get_field(ACTIVE_EPS) or {} + ib_elements = ib_elements or {} + + local old_set, new_set = {}, {} + for _, ep_id in ipairs(stored_eps) do + old_set[ep_id] = true + end + for _, ep_id in ipairs(ib_elements) do + new_set[ep_id] = true + end + + local removed_eps, added_eps = {}, {} + + for ep_id in pairs(old_set) do + if not new_set[ep_id] then + table.insert(removed_eps, ep_id) + end + end + for ep_id in pairs(new_set) do + if not old_set[ep_id] then + table.insert(added_eps, ep_id) + end + end + return removed_eps, added_eps +end + +local function assign_profile_for_endpoint(device_type_id) + local profile = fields.device_type_profile_map[device_type_id] or "switch-binary" + return function(device, ep_id, is_child_device) + return profile, nil + end +end + +local function link_matter_device_and_parent(matter_device) + local parent = matter_device:get_parent_device() + matter_device:set_field(PARENT_ID, parent.id, { persist = true }) + matter_device:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) + parent:set_field(PARENT_ID, parent.id, { persist = true }) + parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) +end + +local function handle_set_preset(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local lift_value = device.preferences.presetPosition + local hundredths_lift_percent = (100 - tonumber(lift_value)) * 100 + device:send(clusters.WindowCovering.server.commands.GoToLiftPercentage(device, endpoint_id, hundredths_lift_percent)) +end + +local function handle_close(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local req = clusters.WindowCovering.server.commands.DownOrClose(device, endpoint_id) + if device.preferences.reverse then + req = clusters.WindowCovering.server.commands.UpOrOpen(device, endpoint_id) + end + device:send(req) +end + +local function handle_open(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local req = clusters.WindowCovering.server.commands.UpOrOpen(device, endpoint_id) + if device.preferences.reverse then + req = clusters.WindowCovering.server.commands.DownOrClose(device, endpoint_id) + end + device:send(req) +end + +local function handle_pause(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local req = clusters.WindowCovering.server.commands.StopMotion(device, endpoint_id) + device:send(req) +end + +local function handle_shade_level(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local lift_percentage_value = 100 - cmd.args.shadeLevel + local hundredths_lift_percentage = lift_percentage_value * 100 + local req = clusters.WindowCovering.server.commands.GoToLiftPercentage( + device, endpoint_id, hundredths_lift_percentage + ) + device:send(req) +end + +local current_pos_handler = function(attribute) + return function(driver, device, ib, response) + if ib.data.value == nil then + return + end + local windowShade = capabilities.windowShade.windowShade + local position = 100 - math.floor(ib.data.value / 100) + local reverse = device.preferences.reverse + device:emit_event_for_endpoint(ib.endpoint_id, attribute(position)) + if attribute == capabilities.windowShadeLevel.shadeLevel then + device:set_field(CURRENT_LIFT, position) + end + local lift_position = device:get_field(CURRENT_LIFT) + if lift_position == 100 then + device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closed() or windowShade.open()) + elseif lift_position > 0 then + device:emit_event_for_endpoint(ib.endpoint_id, windowShade.partially_open()) + elseif lift_position == 0 then + device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.open() or windowShade.closed()) + end + end +end + +local function current_status_handler(driver, device, ib, response) + local windowShade = capabilities.windowShade.windowShade + local reverse = device.preferences.reverse + local state = ib.data.value & clusters.WindowCovering.types.OperationalStatus.GLOBAL + if state == 1 then + device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closing() or windowShade.opening()) + elseif state == 2 then + device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.opening() or windowShade.closing()) + elseif state ~= 0 then + device:emit_event_for_endpoint(ib.endpoint_id, windowShade.unknown()) + end +end + +local function info_changed(driver, device, event, args) + if not device or not device.id or not device.profile then + return + end + + if device.profile.id ~= args.old_st_store.profile.id or device.network_type == device_lib.NETWORK_TYPE_CHILD then + local parent = device:get_parent_device() + local matter_device = get_matter_device(parent) + local map = {} + device.thread:call_with_delay(2, function() + if device:supports_capability(capabilities.button) then + local button_eps = parent:get_field(BUTTON_EPS) + local clean_eps = {} + table.sort(button_eps) + for _, v in ipairs(button_eps or {}) do + table.insert(clean_eps, v) + end + + buttonCfg.update_button_component_map(matter_device, clean_eps[1], clean_eps) + for _, ep_id in ipairs(clean_eps) do + subscribe(device, ep_id, clusters.Switch.ID, nil, clusters.Switch.events.MultiPressComplete.ID) + subscribe(device, ep_id, clusters.Switch.ID, nil, clusters.Switch.events.LongPress.ID) + device:emit_event_for_endpoint(ep_id, capabilities.button.supportedButtonValues({ "pushed", "double", "held" })) + end + return + elseif device:supports_capability(capabilities.switch) then + map = { main = 3 } + subscribe(device, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) + if device:supports_capability(capabilities.switchLevel) then + map = { main = 4 } + subscribe(device, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.CurrentLevel.ID, nil) + subscribe(device, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MaxLevel.ID) + subscribe(device, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID) + end + elseif device:supports_capability(capabilities.motionSensor) then + subscribe(device, nil, clusters.OccupancySensing.ID, clusters.OccupancySensing.attributes.Occupancy.ID, nil) + subscribe(device, nil, clusters.IlluminanceMeasurement.ID, clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID, nil) + elseif device:supports_capability(capabilities.windowShadeLevel) then + map = { main = 5 } + subscribe(device, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.OperationalStatus.ID) + subscribe(device, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) + end + if device.id == matter_device.id then + parent:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, map, { persist = true }) + matter_device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, map, { persist = true }) + end + end) + end +end + +local function device_init (driver, device) + if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then + device.thread:call_with_delay(4, function() + info_changed(driver, device, nil, { + old_st_store = { profile = { } } + }) + end) + return + end + + if device:get_parent_device() ~= nil then + link_matter_device_and_parent(device) + local parent = device:get_parent_device() + device:extend_device("send", function(self, message) + return parent:send(message) + end) + else + subscribe(device, 2, clusters.Descriptor.ID, clusters.Descriptor.attributes.PartsList.ID) + device.thread:call_with_delay(3, function() + local matter_device = get_matter_device(device) + device:extend_device("emit_event_for_endpoint", function(self, ep_info, event) + local endpoint_id = type(ep_info) == "number" and ep_info or ep_info.endpoint_id + local child = self:get_child_by_parent_assigned_key(string.format("%d", endpoint_id)) + if child then + return child:emit_event(event) + end + if matter_device then + return matter_device:emit_event_for_endpoint(ep_info, event) + end + end) + end) + end + + device:set_component_to_endpoint_fn(switch_utils.component_to_endpoint) + device:set_endpoint_to_component_fn(switch_utils.endpoint_to_component) +end + +local function handle_descriptor_report(driver, device, ib, response) + local product_id = device.manufacturer_info and device.manufacturer_info.product_id + local parent = get_parent(driver, device) + + if not parent or ib.endpoint_id ~= 2 then + return + end + + local matter_device = get_matter_device(device) + + local new_eps = extract_reported_endpoints(ib) or {} + table.sort(new_eps) + local removed_eps, added_eps = detect_endpoint_changes(device, new_eps) + + device:set_field(ACTIVE_EPS, new_eps, { persist = true }) + + for _, ep_id in ipairs(added_eps or {}) do + subscribe(parent, ep_id, clusters.Descriptor.ID, clusters.Descriptor.attributes.DeviceTypeList.ID, nil) + + if product_id == PRODUCT_ID.SWITCH_1G and ep_id == 3 then + matter_device:try_update_metadata({ profile = "light-binary" }) + elseif product_id == PRODUCT_ID.SWITCH_2G then + if ep_id == 3 then + local profile = switch_utils.tbl_contains(new_eps, 4) and "light-binary" or "2-button" + matter_device:try_update_metadata({ profile = profile }) + elseif ep_id == 4 then + local profile = switch_utils.tbl_contains(new_eps, 3) and "light-binary" or "2-button" + matter_device:try_update_metadata({ profile = profile }) + end + end + end + + for _, ep_id in ipairs(removed_eps) do + local button_eps = parent:get_field(BUTTON_EPS) or {} + local clean_eps = {} + + for _, value in ipairs(button_eps) do + if value ~= ep_id then + table.insert(clean_eps, value) + end + end + table.sort(clean_eps) + parent:set_field(BUTTON_EPS, clean_eps, { persist = true }) + matter_device:set_field(BUTTON_EPS, clean_eps, { persist = true }) + + local child = parent:get_child_by_parent_assigned_key(tostring(ep_id)) + if child then + driver:try_delete_device(child.id) + end + + if ep_id == 3 then + if device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_1G then + matter_device:try_update_metadata({ profile = "2-button" }) + elseif device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_2G then + local has_ep4 = switch_utils.tbl_contains(new_eps, 4) + matter_device:try_update_metadata({ profile = has_ep4 and "2-button" or "4-button" }) + end + elseif ep_id == 4 then + if device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_2G then + local button_comb = switch_utils.tbl_contains(new_eps, 3) + if button_comb then + matter_device:try_update_metadata({ profile = "2-button" }) + create_child(driver, parent, { 3 }, 1, assign_profile_for_endpoint(fields.DEVICE_TYPE_ID.LIGHT.ON_OFF)) + else + matter_device:try_update_metadata({ profile = "4-button" }) + end + end + end + end +end + +local function device_type_handler (driver, device, ib) + if version.api < 16 then + clusters.Descriptor.server.attributes.DeviceTypeList:augment_type(ib.data) + end + + local parent = get_parent(driver, device) + if not parent then + return + end + + local matter_device = get_matter_device(device) + local stored_btn_eps = parent:get_field(BUTTON_EPS) or {} + local new_btn_eps = {} + local ep_id = ib.endpoint_id + local value = ib.data.elements + + for _, v in ipairs(stored_btn_eps) do + table.insert(new_btn_eps, v) + end + for _, element in ipairs(value) do + local device_type_field = element.elements.device_type + local device_type_id = device_type_field and device_type_field.value + + if device_type_id == fields.DEVICE_TYPE_ID.GENERIC_SWITCH then + if not switch_utils.tbl_contains(new_btn_eps, ep_id) then + switch_utils.set_field_for_endpoint(parent, fields.SUPPORTS_MULTI_PRESS, ep_id) + switch_utils.set_field_for_endpoint(parent, fields.IGNORE_NEXT_MPC, ep_id) + table.insert(new_btn_eps, ep_id) + table.sort(new_btn_eps) + parent:set_field(BUTTON_EPS, new_btn_eps, { persist = true }) + end + end + + if device_type_id == fields.DEVICE_TYPE_ID.LIGHT.ON_OFF then + local active_eps = device:get_field(ACTIVE_EPS) + + if ep_id == 3 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_1G then + return + elseif ep_id == 4 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_2G then + if switch_utils.tbl_contains(active_eps, 3) then + local ep3 = parent:get_child_by_parent_assigned_key("3") + if ep3 then + driver:try_delete_device(ep3.id) + end + else + create_child(driver, parent, { 4 }, 1, assign_profile_for_endpoint(device_type_id)) + return + end + elseif ep_id == 3 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_2G then + if not switch_utils.tbl_contains(active_eps, 4) then + create_child(driver, parent, { 3 }, 1, assign_profile_for_endpoint(device_type_id)) + else + driver:try_delete_device(ep_id) + end + return + end + create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id)) + elseif device_type_id == fields.DEVICE_TYPE_ID.LIGHT.DIMMABLE then + if ep_id == 4 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_1G then + return + end + create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id)) + elseif device_type_id == fields.DEVICE_TYPE_ID.WINDOW_COVERING then + if matter_device:get_field(MAIN_WC_EP) == nil then + create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id)) + end + end + end +end + +local function do_configure (driver, device) + if device.network_type == device_lib.NETWORK_TYPE_MATTER then + + local wc_eps = device:get_endpoints(clusters.WindowCovering.ID) + local oc_eps = device:get_endpoints(clusters.OccupancySensing.ID) + local bt_eps = device:get_endpoints(clusters.Switch.ID) + local lvl_eps = device:get_endpoints(clusters.LevelControl.ID) + local product_id = device.manufacturer_info.product_id + + table.sort(wc_eps) + table.sort(oc_eps) + table.sort(lvl_eps) + + if #oc_eps > 0 then + device:try_update_metadata({ profile = "motion-illuminance" }) + elseif #wc_eps > 0 and product_id == PRODUCT_ID.SWITCH_1G then + device:try_update_metadata({ profile = "window-covering" }) + device:set_field(MAIN_WC_EP, wc_eps[1], { persist = true }) + elseif #bt_eps == 4 then + device:try_update_metadata({ profile = "4-button" }) + elseif #bt_eps == 2 then + device:try_update_metadata({ profile = "2-button" }) + elseif #lvl_eps > 0 and product_id == PRODUCT_ID.SWITCH_1G then + device:try_update_metadata({ profile = "light-level" }) + end + device:set_field(BUTTON_EPS, bt_eps, { persist = true }) + end +end + +local function added (driver, device) end +local function driver_switched(driver, device) end + +local hager_switch = { + NAME = "Hager Subdriver", + lifecycle_handlers = { + added = added, + init = device_init, + infoChanged = info_changed, + doConfigure = do_configure, + driverSwitched = driver_switched + }, + matter_handlers = { + attr = { + [clusters.Descriptor.ID] = { + [clusters.Descriptor.attributes.PartsList.ID] = handle_descriptor_report, + [clusters.Descriptor.attributes.DeviceTypeList.ID] = device_type_handler, + }, + [clusters.WindowCovering.ID] = { + [clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID] = current_pos_handler(capabilities.windowShadeLevel.shadeLevel), + [clusters.WindowCovering.attributes.OperationalStatus.ID] = current_status_handler, + }, + }, + }, + capability_handlers = { + [capabilities.windowShadePreset.ID] = { + [capabilities.windowShadePreset.commands.presetPosition.NAME] = handle_set_preset, + }, + [capabilities.windowShade.ID] = { + [capabilities.windowShade.commands.close.NAME] = handle_close, + [capabilities.windowShade.commands.open.NAME] = handle_open, + [capabilities.windowShade.commands.pause.NAME] = handle_pause, + }, + [capabilities.windowShadeLevel.ID] = { + [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = handle_shade_level, + }, + }, + can_handle = require("sub_drivers.hager.can_handle") +} + +return hager_switch diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index 45694bc613..b7feed6344 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -246,7 +246,7 @@ end -- [[ OCCUPANCY CLUSTER ATTRIBUTES ]] -- function AttributeHandlers.occupancy_handler(driver, device, ib, response) - device:emit_event(ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) + device:emit_event_for_endpoint(ib.endpoint_id, ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 772669ebd7..9c5235d1e2 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -156,7 +156,6 @@ function ButtonDeviceConfiguration.update_button_component_map(device, default_e 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}) diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index a8e9781172..5873cb40b2 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -34,7 +34,9 @@ SwitchFields.DEVICE_TYPE_ID = { ELECTRICAL_SENSOR = 0x0510, FAN = 0x002B, GENERIC_SWITCH = 0x000F, + LIGHT_SENSOR = 0x0106, IRRIGATION_SYSTEM = 0x0040, + OCCUPANCY_SENSOR = 0x0107, MOUNTED_ON_OFF_CONTROL = 0x010F, MOUNTED_DIMMABLE_LOAD_CONTROL = 0x0110, ON_OFF_PLUG_IN_UNIT = 0x010A, @@ -50,6 +52,7 @@ SwitchFields.DEVICE_TYPE_ID = { COLOR_DIMMER = 0x0105, }, WATER_VALVE = 0x0042, + WINDOW_COVERING = 0x0202 } SwitchFields.device_type_profile_map = { @@ -64,6 +67,7 @@ SwitchFields.device_type_profile_map = { [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", + [SwitchFields.DEVICE_TYPE_ID.WINDOW_COVERING] = "window-covering" } -- COMPONENT_TO_ENDPOINT_MAP is here to preserve the endpoint mapping for @@ -116,6 +120,12 @@ SwitchFields.vendor_overrides = { [0x1189] = { -- LEDVANCE_MANUFACTURER_ID [0x0891] = { target_profile = "switch-binary", initial_profile = "light-binary" }, }, + [0x1285] = { -- HAGER_MANUFACTURER_ID + [0x0005] = { needs_hager_subdriver = true }, -- Hager HBnet 1g switch + [0x0006] = { needs_hager_subdriver = true }, -- Hager HBnet 2g switch + [0x0007] = { needs_hager_subdriver = true }, -- Hager HBnet PIR 1.1M + [0x000A] = { needs_hager_subdriver = true }, -- Hager HBnet PIR 2.2M + }, [0x1321] = { -- SONOFF_MANUFACTURER_ID [0x000C] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, [0x000D] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, diff --git a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua new file mode 100644 index 0000000000..96a684529d --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua @@ -0,0 +1,1780 @@ +-- Copyright © 2026 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 clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" +local descriptor = require "embedded_clusters.Descriptor" +local version = require "version" + +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" +end + +local MATTER_DEVICE_ID = "MATTER_DEVICE_ID" +local PARENT_ID = "PARENT_ID" +local BUTTON_EPS = "__button_eps" + +local function create_parent_device(product_id) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 4x Button", + profile = t_utils.get_profile_definition("matter-bridge.yml"), + manufacturer_info = { + vendor_id = 0x1285, + product_id = product_id, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 1, + clusters = { + { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } + }, + device_types = { { device_type_id = 0x000E, device_type_revision = 1 } } -- AggregateNode + }, + { + endpoint_id = 2, + clusters = { + { + cluster_id = clusters.Descriptor.ID, + cluster_type = "SERVER", + cluster_revision = 1, + } + }, + device_types = { { device_type_id = 0x0039, device_type_revision = 1 } } -- BridgedNode + } + }, + }) +end + +local function create_matter_device(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 4x Button", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + type = "MATTER", + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0006, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 8, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + { + endpoint_id = 9, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + { + endpoint_id = 10, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + { + endpoint_id = 11, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + }, + }) +end + +-- Create Hager 2G Relay device with endpoints 3 & 4 (OnOff clusters) +local function create_hager_2g_relay(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 2G Relay", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + type = "MATTER", + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0006, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 3, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OnOff.attributes.OnOff.ID] = false + } + } + }, + device_types = { { device_type_id = 0x0100, device_type_revision = 1 } } + }, + { + endpoint_id = 4, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OnOff.attributes.OnOff.ID] = false + } + } + }, + device_types = { { device_type_id = 0x0100, device_type_revision = 1 } } + }, + } + }) +end + +-- Create Hager Dimmer device with ONLY dimmable endpoint (3) - no button endpoints +local function create_hager_dimmer_device_1g(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 Dimmer", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + type = "MATTER", + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0005, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 4, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OnOff.attributes.OnOff.ID] = false + } + }, + { + cluster_id = clusters.LevelControl.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.LevelControl.attributes.CurrentLevel.ID] = 254 + } + } + }, + device_types = { { device_type_id = 0x0101, device_type_revision = 1 } } + }, + } + }) +end + +local function create_hager_dimmer_device_2g(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 Dimmer", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0006, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 3, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OnOff.attributes.OnOff.ID] = false + } + }, + { + cluster_id = clusters.LevelControl.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.LevelControl.attributes.CurrentLevel.ID] = 254 + } + } + }, + device_types = { { device_type_id = 0x0101, device_type_revision = 1 } } + }, + { + endpoint_id = 8, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + { + endpoint_id = 9, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + } + }) +end + +local function create_matter_device_with_window(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 matter device with Window Covering", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0006, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 8, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + { + endpoint_id = 9, + clusters = { + { + cluster_id = clusters.Switch.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + attributes = { + [clusters.Switch.attributes.MultiPressMax.ID] = 2 + } + } + }, + device_types = { { device_type_id = 0x003B, device_type_revision = 1 } } + }, + { + endpoint_id = 12, + clusters = { + { + cluster_id = clusters.WindowCovering.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.WindowCovering.types.Feature.LIFT | + clusters.WindowCovering.types.Feature.POSITION_AWARE_LIFT | + clusters.WindowCovering.types.Feature.ABSOLUTE_POSITION, + attributes = { + [clusters.WindowCovering.attributes.OperationalStatus.ID] = 0x00, + [clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID] = 0x0000, + } + } + }, + device_types = { { device_type_id = 0x0202, device_type_revision = 1 } } + }, + } + }) +end + +local function create_hager_pir_device(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 PIR with Buttons and Motion/Illuminance/Dimmer", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + type = "MATTER", + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0007, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 3, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OnOff.attributes.OnOff.ID] = false + } + }, + { + cluster_id = clusters.LevelControl.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.LevelControl.attributes.CurrentLevel.ID] = 254 + } + } + }, + device_types = { { device_type_id = 0x0101, device_type_revision = 1 } } + }, + { + endpoint_id = 6, + clusters = { + { + cluster_id = clusters.OccupancySensing.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OccupancySensing.attributes.Occupancy.ID] = 0 + } + } + }, + device_types = { { device_type_id = 0x0107, device_type_revision = 1 } } + }, + { + endpoint_id = 5, + clusters = { + { + cluster_id = clusters.IlluminanceMeasurement.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID] = 0 + } + } + }, + device_types = { { device_type_id = 0x0106, device_type_revision = 1 } } + }, + } + }) +end + +local function add_parent_device(parent) + test.mock_device.add_test_device(parent) + test.socket.device_lifecycle:__queue_receive({ parent.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ parent.id, "init" }) + test.mock_time.advance_time(5) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, 2, descriptor.ID, descriptor.attributes.PartsList.ID, nil) + }) +end + +local function subscribe_switch_events(parent, button_eps) + for _, ep in ipairs(button_eps) do + test.socket.matter:__expect_send({ + parent.id, + clusters.Switch.events.MultiPressComplete:subscribe(parent, ep) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Switch.events.LongPress:subscribe(parent, ep) + }) + end +end + +local function add_matter_device(matter_device, parent, expected_profile_change) + test.mock_device.add_test_device(matter_device) + test.socket.device_lifecycle:__queue_receive({ matter_device.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ matter_device.id, "doConfigure" }) + matter_device:expect_metadata_update({ profile = expected_profile_change }) + matter_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.device_lifecycle:__queue_receive({ matter_device.id, "init" }) + + matter_device:set_field(PARENT_ID, parent.id, { persist = true }) + matter_device:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) + parent:set_field(PARENT_ID, parent.id, { persist = true }) + parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) + parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) +end + +local function add_switch_matter_device(matter_device, parent) + test.mock_device.add_test_device(matter_device) + test.socket.device_lifecycle:__queue_receive({ matter_device.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ matter_device.id, "doConfigure" }) + matter_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.device_lifecycle:__queue_receive({ matter_device.id, "init" }) + + matter_device:set_field(PARENT_ID, parent.id, { persist = true }) + matter_device:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) + parent:set_field(PARENT_ID, parent.id, { persist = true }) + parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) + parent:set_field(MATTER_DEVICE_ID, matter_device.id, { persist = true }) +end + +local function button_supported_values (matter_device) + test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) + test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) + test.socket.capability:__expect_send(matter_device:generate_test_message("button3", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) + test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) +end + +local function initiate_info_changed(device, profile) + test.socket.device_lifecycle:__queue_receive(device:generate_info_changed({ profile = { id = profile } })) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.mock_time.advance_time(2) +end + +local function configure_parent(device) + test.socket.device_lifecycle:__queue_receive({ device.id, "doConfigure" }) + device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.device_lifecycle:__queue_receive({ device.id, "init" }) + test.socket.matter:__expect_send({ + device.id, + cluster_base.subscribe(device, 2, descriptor.ID, descriptor.attributes.PartsList.ID, nil) + }) + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + test.mock_time.advance_time(3) +end + +local parent = create_parent_device(0x0006) -- 2G button product +local parent_1g = create_parent_device(0x0005) -- 1G button product +local parent_pir = create_parent_device(0x0007) -- PIR product + +local function test_init() + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test("Test: 4-Button Device Detection - Profile Changes from matter-bridge to 4-button When Four Button Endpoints Present", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local matter_device = create_matter_device("matter-bridge", parent) + add_matter_device(matter_device, parent, "4-button") + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(10), + data_types.Uint16(11), + })) + }) + + local matter_device_parent_id = matter_device:get_field(PARENT_ID) + local matter_device_id = matter_device:get_field(MATTER_DEVICE_ID) + local parent_id = parent:get_field(PARENT_ID) + local parent_matter_device_id = parent:get_field(MATTER_DEVICE_ID) + + assert(matter_device_parent_id == parent.id, "link_host_and_subhub 1/4") + assert(matter_device_id == matter_device.id, "link_host_and_subhub 2/4") + assert(parent_id == parent.id, "link_host_and_subhub 3/4") + assert(parent_matter_device_id == matter_device.id, "link_host_and_subhub 4/4") + + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x08) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x09) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x0A) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x0B) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 10, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 11, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + +end) + +test.register_coroutine_test("Test: Button Event Handling - Pushed, Double Press, and Held Events on 4-Button Device", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local matter_device = create_matter_device("4-button", parent) + add_matter_device(matter_device, parent, "4-button") + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(10), + data_types.Uint16(11), + })) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x08) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x09) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x0A) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 0x0B) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 10, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 11, data_types.Array({ + { device_type = 0x000F, revision = 0x0003 } + })) + }) + test.wait_for_events() + + initiate_info_changed(matter_device, "4-button") + subscribe_switch_events(parent, { 8, 9, 10, 11 }) + button_supported_values(matter_device) + test.wait_for_events() + + --Test single press (pushed) on endpoint 8 (button1) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 8, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 1 -- Single press + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) + + --Test double press on endpoint 8 (button1) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 8, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 2 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.button.double({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + matter_device, 8, { + new_position = 1, + previous_position = 0, + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.button.held({ state_change = true }))) + + -- Test press on endpoint 9 (button2) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 9, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 1 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.button.pushed({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 9, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 2 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.button.double({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + matter_device, 9, { + new_position = 1, + previous_position = 0, + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.button.held({ state_change = true }))) + + -- Test press on endpoint 10 (button3) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 10, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 1 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button3", capabilities.button.button.pushed({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 10, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 2 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button3", capabilities.button.button.double({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + matter_device, 10, { + new_position = 1, + previous_position = 0, + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button3", capabilities.button.button.held({ state_change = true }))) + + -- Test press on endpoint 11 (button4) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 11, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 1 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.pushed({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + matter_device, 11, { + new_position = 1, + previous_position = 1, + total_number_of_presses_counted = 2 + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.double({ state_change = true }))) + test.socket.matter:__queue_receive({ + matter_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + matter_device, 11, { + new_position = 1, + previous_position = 0, + } + ) + }) + test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.held({ state_change = true }))) +end) + +test.register_coroutine_test("Test: Device Type Handler - Handles Button (Type 15) and OnOff (Type 256) Device Types with Child Creation", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local matter_device = create_matter_device("4-button", parent) + add_matter_device(matter_device, parent, "4-button") + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(9), + data_types.Uint16(8), + data_types.Uint16(6), + })) + }) + + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 6) + }) + + test.wait_for_events() + + -- Receive DeviceTypeList report with device type 15 (button) for endpoint 8 + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array { + { + device_type = data_types.Uint32(15), + revision = data_types.Uint16(1) + } + }) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array { + { + device_type = data_types.Uint32(15), + revision = data_types.Uint16(1) + } + }) + }) + + -- Receive DeviceTypeList report with device type 256 (OnOff) for endpoint 6 + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 6, data_types.Array { + { + device_type = data_types.Uint32(256), + revision = data_types.Uint16(1) + } + }) + }) + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-binary", + parent_device_id = parent.id, + parent_assigned_child_key = "6" + }) + test.wait_for_events() + initiate_info_changed(matter_device, "2-button") + subscribe_switch_events(parent, { 8, 9 }) + test.socket.capability:__expect_send(matter_device:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) + test.socket.capability:__expect_send(matter_device:generate_test_message("button2", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) + + test.wait_for_events() + + local child_switch = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("light-binary.yml"), + device_network_id = string.format("%s:6", parent.id), + parent_device_id = parent.id, + parent_assigned_child_key = "6" + }) + test.wait_for_events() + initiate_info_changed(child_switch, "light-binary") + + test.socket.matter:__expect_send({ + parent.id, + clusters.OnOff.attributes.OnOff:subscribe(parent, nil) + }) +end) + +test.register_coroutine_test("Test: 2G Relay - Profile Changes Between light-binary and 2-button Based On Endpoint Availability", function() + test.socket.matter:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + add_parent_device(parent) + local relay = create_hager_2g_relay("light-binary", parent) + add_switch_matter_device(relay, parent) + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + -- Scenario 1: EP3 + EP4 present → light-binary profile + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(3), + data_types.Uint16(4), + })) + }) + -- + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 3) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 4) + }) + + relay:expect_metadata_update({ profile = "light-binary" }) + relay:expect_metadata_update({ profile = "light-binary" }) + test.wait_for_events() + -- Both endpoints are device type 256 (OnOff) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 3, data_types.Array { + { + device_type = data_types.Uint32(256), + revision = data_types.Uint16(1) + } + }) + }) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 4, data_types.Array { + { + device_type = data_types.Uint32(256), + revision = data_types.Uint16(1) + } + }) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-binary", + parent_device_id = parent.id, + parent_assigned_child_key = "4" + }) + local child_switch = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("light-binary.yml"), + device_network_id = string.format("%s:4", parent.id), + parent_device_id = parent.id, + parent_assigned_child_key = "4" + }) + + initiate_info_changed(relay, "light-binary") + test.socket.matter:__expect_send({ + parent.id, + clusters.OnOff.attributes.OnOff:subscribe(parent, nil) + }) + + initiate_info_changed(child_switch, "light-binary") + test.socket.matter:__expect_send({ + parent.id, + clusters.OnOff.attributes.OnOff:subscribe(parent, nil) + }) + + --Scenario 2: EP4 removed → profile changes to 2-button, child created for EP3 + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(3), + data_types.Uint16(8), + data_types.Uint16(9), + })) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-binary", + parent_device_id = parent.id, + parent_assigned_child_key = "3" + }) + + relay:expect_metadata_update({ profile = "2-button" }) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array { + { + device_type = data_types.Uint32(15), + revision = data_types.Uint16(1) + } + }) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array { + { + device_type = data_types.Uint32(15), + revision = data_types.Uint16(1) + } + }) + }) + + -- Scenario 3: EP4 reappears → profile changes back to light-binary + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(3), + data_types.Uint16(4), + })) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 4) + }) + relay:expect_metadata_update({ profile = "light-binary" }) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 4, data_types.Array { + { + device_type = data_types.Uint32(256), + revision = data_types.Uint16(1) + } + }) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-binary", + parent_device_id = parent.id, + parent_assigned_child_key = "4" + }) + + -- Scenario 4: EP3 removed → profile changes to 2-button, child created for EP4 + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(4), + data_types.Uint16(12), + data_types.Uint16(13), + })) + }) + + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 12) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 13) + }) + + relay:expect_metadata_update({ profile = "2-button" }) + + -- Scenario 5: EP3 and EP4 removed → 4-button profile + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(10), + data_types.Uint16(11), + })) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 10) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 11) + }) + relay:expect_metadata_update({ profile = "4-button" }) + + -- Scenario 6: Only EP4 present → 2-button profile, child created + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(4), + data_types.Uint16(8), + data_types.Uint16(9), + })) + }) + + relay:expect_metadata_update({ profile = "2-button" }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 4) + }) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 4, data_types.Array { + { + device_type = data_types.Uint32(256), + revision = data_types.Uint16(1) + } + }) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-binary", + parent_device_id = parent.id, + parent_assigned_child_key = "4" + }) +end) + +test.register_coroutine_test("Test: Dimmer Device - Child Creation for Dimmable Endpoint with Button Support", function() + test.socket.matter:__set_channel_ordering("relaxed") + + add_parent_device(parent) + local dimmer = create_hager_dimmer_device_2g("2-button", parent) + add_matter_device(dimmer, parent, "2-button") + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + --Scenario 1: 2 button endpoints (8, 9) detected → 2-button profile + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(3) + })) + }) + + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 3) + }) + dimmer:expect_metadata_update({ profile = "2-button" }) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 3, data_types.Array { + { + device_type = data_types.Uint32(257), + revision = data_types.Uint16(1) + } + }) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-level", + parent_device_id = parent.id, + parent_assigned_child_key = "3" + }) + + -- Create mock child device for EP3 + local child_dimmer = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("light-level.yml"), + device_network_id = string.format("%s:3", parent.id), + parent_device_id = parent.id, + parent_assigned_child_key = "3" + }) + initiate_info_changed(child_dimmer, "light-level") + + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) + }) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.CurrentLevel.ID, nil) + }) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MaxLevel.ID, nil) + }) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID, nil) + }) + + test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switch", component = "main", command = "on", args = {} } }) + child_dimmer.expect_native_cmd_handler_registration(child_dimmer, "switch", "on") + + test.socket.matter:__expect_send({ + parent.id, + clusters.OnOff.server.commands.On(child_dimmer, 3) + }) + test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 20 } } }) + + child_dimmer.expect_native_cmd_handler_registration(child_dimmer, "switchLevel", "setLevel") + + test.socket.matter:__expect_send({ + parent.id, + clusters.LevelControl.server.commands.MoveToLevelWithOnOff(child_dimmer, 3, 51, nil, 0, 0) + }) + +end) + +test.register_coroutine_test("Test: 1G Dimmer - Initialization and Profile Update to light-level", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local dimmer = create_hager_dimmer_device_2g("2-button", parent) + add_matter_device(dimmer, parent, "2-button") + test.wait_for_events() + configure_parent(parent) +end) + +test.register_coroutine_test("Test: 1G Dimmer - Host Commands and Level Control Capabilities", function() + test.socket.matter:__set_channel_ordering("relaxed") + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + + add_parent_device(parent_1g) + local dimmer = create_hager_dimmer_device_1g("light-level", parent_1g) + add_matter_device(dimmer, parent_1g, "light-level") + test.wait_for_events() + configure_parent(parent_1g) + + -- Send dimmable endpoint 4 detection (device type 257) to trigger profile change and subscriptions + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_1g, 2, data_types.Array({ + data_types.Uint16(4), + })) + }) + + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 4) + }) + + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_1g, 4, data_types.Array { + { + device_type = data_types.Uint32(257), + revision = data_types.Uint16(1) + } + }) + }) + + ----subscriptions from device_type_handler + + initiate_info_changed(dimmer, "light-level") + test.socket.matter:__expect_send({ + parent_1g.id, + cluster_base.subscribe(parent_1g, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + cluster_base.subscribe(parent_1g, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.CurrentLevel.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + cluster_base.subscribe(parent_1g, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MaxLevel.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + cluster_base.subscribe(parent_1g, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID, nil) + }) + + test.socket.capability:__queue_receive({ dimmer.id, { capability = "switch", component = "main", command = "on", args = {} } }) + dimmer.expect_native_cmd_handler_registration(dimmer, "switch", "on") + + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.OnOff.server.commands.On(dimmer, 4) + }) + test.socket.capability:__queue_receive({ dimmer.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 20 } } }) + + dimmer.expect_native_cmd_handler_registration(dimmer, "switchLevel", "setLevel") + + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.LevelControl.server.commands.MoveToLevelWithOnOff(dimmer, 4, 51, nil, 0, 0) + }) +end) + +test.register_coroutine_test("Test: PIR Device - Initialization with Motion and Illuminance Capabilities", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent_pir) + local pir_device = create_hager_pir_device("motion-illuminance", parent_pir) + add_matter_device(pir_device, parent_pir, "motion-illuminance") + test.wait_for_events() + configure_parent(parent_pir) + +end) + +test.register_coroutine_test("Test: PIR Device - Complete Functionality with Motion, Illuminance, and Dimmer Support", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent_pir) + local pir_device = create_hager_pir_device("motion-illuminance", parent_pir) + add_matter_device(pir_device, parent_pir, "motion-illuminance") + test.wait_for_events() + + configure_parent(parent_pir) + + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_pir, 2, data_types.Array({ + data_types.Uint16(3), + data_types.Uint16(4), + data_types.Uint16(5) + + })) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_pir, 3) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_pir, 4) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_pir, 5) + }) + + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_pir, 4, data_types.Array { + { + device_type = data_types.Uint32(263), + revision = data_types.Uint16(1) + } + }) + }) + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_pir, 5, data_types.Array { + { + device_type = data_types.Uint32(262), + revision = data_types.Uint16(1) + } + }) + }) + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_pir, 3, data_types.Array { + { + device_type = data_types.Uint32(257), + revision = data_types.Uint16(1) + } + }) + }) + + parent_pir:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "light-level", + parent_device_id = parent_pir.id, + parent_assigned_child_key = "3" + }) + test.wait_for_events() + initiate_info_changed(pir_device, "motion-illuminance") + test.socket.matter:__expect_send({ + parent_pir.id, + cluster_base.subscribe(parent_pir, nil, clusters.IlluminanceMeasurement.ID, clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + cluster_base.subscribe(parent_pir, nil, clusters.OccupancySensing.ID, clusters.OccupancySensing.attributes.Occupancy.ID, nil) + }) + + local child_dimmer = test.mock_device.build_test_child_device({ + label = "Hager G2 4x Button 1", + profile = t_utils.get_profile_definition("light-level.yml"), + device_network_id = string.format("%s:3", parent_pir.id), + parent_device_id = parent_pir.id, + parent_assigned_child_key = "3" + }) + + test.mock_device.add_test_device(child_dimmer) + initiate_info_changed(child_dimmer, "light-level") + test.socket.matter:__expect_send({ + parent_pir.id, + cluster_base.subscribe(parent_pir, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + cluster_base.subscribe(parent_pir, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.CurrentLevel.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + cluster_base.subscribe(parent_pir, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MaxLevel.ID, nil) + }) + test.socket.matter:__expect_send({ + parent_pir.id, + cluster_base.subscribe(parent_pir, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID, nil) + }) + test.wait_for_events() + --Verify motion detected event + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(parent_pir, 6, 1) + }) + + test.socket.capability:__expect_send(pir_device:generate_test_message("main", capabilities.motionSensor.motion.active())) + + -- Verify illuminance measurement event + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.IlluminanceMeasurement.attributes.MeasuredValue:build_test_report_data(parent_pir, 5, 21370) + }) + test.socket.capability:__expect_send(pir_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance(137))) + + -- Send on command to OnOff endpoint (dimmer) + child_dimmer.expect_native_cmd_handler_registration(child_dimmer, "switch", "on") + + test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switch", component = "main", command = "on", args = {} } }) + test.socket.matter:__expect_send({ + parent_pir.id, + clusters.OnOff.commands.On(parent_pir, 3) + }) + -- Verify on state via attribute report + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.OnOff.attributes.OnOff:build_test_report_data(parent_pir, 3, true) + }) + test.socket.capability:__expect_send(child_dimmer:generate_test_message("main", capabilities.switch.switch.on())) + parent_pir.expect_native_attr_handler_registration(parent_pir, "switch", "switch") + + -- Set dimmer level to 50% + test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 50 } } }) + test.socket.matter:__expect_send({ + parent_pir.id, + clusters.LevelControl.commands.MoveToLevelWithOnOff(parent_pir, 3, 127, nil, 0, 0) + }) + child_dimmer.expect_native_cmd_handler_registration(child_dimmer, "switchLevel", "setLevel") + + -- Verify level via attribute report + test.socket.matter:__queue_receive({ + parent_pir.id, + clusters.LevelControl.attributes.CurrentLevel:build_test_report_data(parent_pir, 3, 127) + }) + test.socket.capability:__expect_send(child_dimmer:generate_test_message("main", capabilities.switchLevel.level(50))) + parent_pir.expect_native_attr_handler_registration(parent_pir, "switchLevel", "level") +end) + +test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile with Window Covering Child Device", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local window_covering_device = create_matter_device_with_window("2-button", parent) + add_matter_device(window_covering_device, parent, "2-button") + test.wait_for_events() + configure_parent(parent) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(12), + })) + }) + + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 12) + }) + + -- DeviceTypeList report for window covering endpoint (type 514 = Window Covering Device) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 12, data_types.Array { + { + device_type = data_types.Uint32(514), + revision = data_types.Uint16(1) + } + }) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "window-covering", + parent_device_id = parent.id, + parent_assigned_child_key = "12" + }) + + local child_wc = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("window-covering.yml"), + device_network_id = string.format("%s:12", parent.id), + parent_device_id = parent.id, + parent_assigned_child_key = "12" + }) + + test.mock_device.add_test_device(child_wc) + initiate_info_changed(child_wc, "window-covering") + + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.OperationalStatus.ID, nil) + }) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) + }) + + -- Open command + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "open", args = {} } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.UpOrOpen(parent, 12) }) + test.wait_for_events() + + -- Verify open state via attribute report + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.OperationalStatus:build_test_report_data(parent, 12, 0x01) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.opening())) + + + -- Close command + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "close", args = {} } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.DownOrClose(parent, 12) }) + + -- Verify close state via attribute report + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.OperationalStatus:build_test_report_data(parent, 12, 0x02) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closing())) + + -- Pause command + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "pause", args = {} } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.StopMotion(parent, 12) }) + test.wait_for_events() + + -- Set shade level to 50% + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 50 } } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.GoToLiftPercentage(parent, 12, 5000, nil, 0, 0) }) + test.wait_for_events() + + -- Verify shade level via attribute report + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 5000) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50))) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.partially_open())) + + + -- Set shade level to 100% + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 100 } } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.GoToLiftPercentage(parent, 12, 0, nil, 0, 0) }) + test.wait_for_events() + + -- Verify shade level via attribute report + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 0) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100))) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.open())) + + -- Set shade level to 0% + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 0 } } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.GoToLiftPercentage(parent, 12, 10000, nil, 0, 0) }) + + -- Verify shade level via attribute report + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 10000) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0))) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closed())) + +end) + +test.register_coroutine_test("Test: Window Covering - Preference Changes for Reverse Polarity and Preset Position", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local window_covering_device = create_matter_device_with_window("2-button", parent) + add_matter_device(window_covering_device, parent, "2-button") + test.wait_for_events() + configure_parent(parent) + + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(12), + })) + }) + -- + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 8) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 9) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 12) + }) + test.wait_for_events() + + -- DeviceTypeList reports for button endpoints (type 15 = Generic Switch) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 8, data_types.Array { + { + device_type = data_types.Uint32(15), + revision = data_types.Uint16(1) + } + }) + }) + + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 9, data_types.Array { + { + device_type = data_types.Uint32(15), + revision = data_types.Uint16(1) + } + }) + }) + + -- DeviceTypeList report for window covering endpoint (type 514 = Window Covering Device) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 12, data_types.Array { + { + device_type = data_types.Uint32(514), + revision = data_types.Uint16(1) + } + }) + }) + + parent:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "window-covering", + parent_device_id = parent.id, + parent_assigned_child_key = "12" + }) + + local child_wc = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("window-covering.yml"), + device_network_id = string.format("%s:12", parent.id), + parent_device_id = parent.id, + parent_assigned_child_key = "12" + }) + test.mock_device.add_test_device(child_wc) + + test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { reverse = "false" } })) + test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { reverse = "true" } })) + test.wait_for_events() + local reverse_preference_set = child_wc.preferences.reverse + assert(reverse_preference_set == "true", "reverse_preference_set is True") + -- + --Send open command - with reverse_polarity true, this should send DownOrClose + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "open", args = {} } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.DownOrClose(parent, 12) }) + + -- Send close command - with reverse_polarity true, this should send UpOrOpen + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "close", args = {} } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.UpOrOpen(parent, 12) }) + + -- Send Pause command + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShade", component = "main", command = "pause", args = {} } }) + test.socket.matter:__expect_send({ parent.id, clusters.WindowCovering.commands.StopMotion(parent, 12) }) + + -- Position preset testing + test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { presetPosition = "50" } })) + test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { presetPosition = "20" } })) + + test.wait_for_events() + + local PRESET_LEVEL_KEY = child_wc.preferences.presetPosition + assert(PRESET_LEVEL_KEY == "20", " presetPosition is set to 20") + + test.socket.capability:__queue_receive({ child_wc.id, { capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} } }) + test.socket.matter:__expect_send( + { parent.id, clusters.WindowCovering.server.commands.GoToLiftPercentage(parent, 12, 8000) } + ) + + test.wait_for_events() + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + })) + }) + + test.wait_for_events() + + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") + test.mock_time.advance_time(5) + local BUTTON_EPS_FIELD = parent:get_field(BUTTON_EPS) + assert(BUTTON_EPS_FIELD[1] == 8) + assert(BUTTON_EPS_FIELD[2] == 9) + + test.wait_for_events() + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(12), + })) + }) + test.socket.matter:__expect_send({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent, 12) + }) + test.socket.matter:__queue_receive({ + parent.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent, 12, data_types.Array { + { + device_type = data_types.Uint32(514), + revision = data_types.Uint16(1) + } + }) + }) + child_wc:expect_metadata_update({ profile = "window-covering" }) + initiate_info_changed(child_wc, "window-covering") + + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.OperationalStatus.ID, nil) + }) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) + }) +end) + +test.run_registered_tests() From d912324f90c10d85530d415f9878eff5d19a8c4f Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Wed, 15 Jul 2026 10:55:14 -0500 Subject: [PATCH 459/538] remove knob preferences for ikea scroll --- drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml index 1793ae209e..2f4f53542f 100644 --- a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml +++ b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml @@ -33,13 +33,6 @@ components: version: 1 categories: - name: Button -preferences: - - preferenceId: knobSensitivityGroup1 - explicit: true - - preferenceId: knobSensitivityGroup2 - explicit: true - - preferenceId: knobSensitivityGroup3 - explicit: true metadata: mnmn: SmartThingsEdge vid: ikea-scroll From 3e0a850adc03d2513bc5a3c2ee3612975a83617e Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Wed, 15 Jul 2026 10:57:00 -0500 Subject: [PATCH 460/538] re-add knob preferences for ikea scroll --- drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml index 2f4f53542f..1793ae209e 100644 --- a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml +++ b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml @@ -33,6 +33,13 @@ components: version: 1 categories: - name: Button +preferences: + - preferenceId: knobSensitivityGroup1 + explicit: true + - preferenceId: knobSensitivityGroup2 + explicit: true + - preferenceId: knobSensitivityGroup3 + explicit: true metadata: mnmn: SmartThingsEdge vid: ikea-scroll From 0255dd3614f8b9282a053b4cd9a7cf3b4e557d4e Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Thu, 16 Jul 2026 10:00:14 -0500 Subject: [PATCH 461/538] Matter Sensor: Fix Global defintion and adjust unit tests --- .../Global/types/MeasurementTypeEnum.lua | 2 +- .../src/sensor_handlers/attribute_handlers.lua | 14 +++++++++++--- .../src/test/test_matter_soil_sensor.lua | 10 +++++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementTypeEnum.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementTypeEnum.lua index 2e749ebacd..334ad78331 100644 --- a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementTypeEnum.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Global/types/MeasurementTypeEnum.lua @@ -2,7 +2,7 @@ local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" local MeasurementTypeEnum = {} -local new_mt = UintABC.new_mt({NAME = "MeasurementTypeEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +local new_mt = UintABC.new_mt({NAME = "MeasurementTypeEnum", ID = data_types.name_to_id_map["Uint16"]}, 2) new_mt.__index.pretty_print = function(self) local name_lookup = { [self.UNSPECIFIED] = "UNSPECIFIED", diff --git a/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua index eaa8730fc4..1867d65e0b 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua @@ -13,6 +13,12 @@ if version.api < 13 then clusters.Global = require "embedded_clusters.Global" end +-- The SOIL_MOISTURE MeasurementTypeEnum variant was added to the Global MeasurementTypeEnum +-- def in lua libs in api version 21 as a part of the fix for Shared and Global types. +if version.api < 21 then + clusters.Global.types.MeasurementTypeEnum = require "embedded_clusters.Global.types.MeasurementTypeEnum" +end + local AttributeHandlers = {} @@ -84,8 +90,10 @@ function AttributeHandlers.soil_moisture_measured_value_handler(driver, device, end function AttributeHandlers.soil_moisture_measurement_limits_handler(driver, device, ib, response) - local MeasurementAccuracyStruct = require "embedded_clusters.Global.types.MeasurementAccuracyStruct" - MeasurementAccuracyStruct:augment_type(ib.data) + if version.api < 13 then + local MeasurementAccuracyStruct = require "embedded_clusters.Global.types.MeasurementAccuracyStruct" + MeasurementAccuracyStruct:augment_type(ib.data) + end local min_val = ib.data.elements and ib.data.elements.min_measured_value and ib.data.elements.min_measured_value.value local max_val = ib.data.elements and ib.data.elements.max_measured_value and ib.data.elements.max_measured_value.value if not (min_val and max_val) or (min_val >= max_val) or (min_val < sensor_utils.SOIL_MOISTURE_MIN) or (max_val > sensor_utils.SOIL_MOISTURE_MAX) then @@ -229,4 +237,4 @@ function AttributeHandlers.flow_measured_value_bounds_factory(minOrMax) end end -return AttributeHandlers \ No newline at end of file +return AttributeHandlers diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_soil_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_soil_sensor.lua index 0c845fa47e..a5a8042fc0 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_soil_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_soil_sensor.lua @@ -7,10 +7,15 @@ local t_utils = require "integration_test.utils" local test = require "integration_test" local version = require "version" -clusters.Global = require "embedded_clusters.Global" +if version.api < 13 then + clusters.Global = require "embedded_clusters.Global" +end if version.api < 21 then clusters.SoilMeasurement = require "embedded_clusters.SoilMeasurement" + -- The SOIL_MOISTURE MeasurementTypeEnum variant was added to the Global MeasurementTypeEnum + -- def in lua libs in api version 21. + clusters.Global.types.MeasurementTypeEnum = require "embedded_clusters.Global.types.MeasurementTypeEnum" end local mock_device = test.mock_device.build_test_matter_device({ @@ -157,6 +162,9 @@ test.register_coroutine_test( ) local function build_soil_moisture_limits(min_value, max_value) + if version.api < 21 then + clusters.Global.types.MeasurementTypeEnum = require "embedded_clusters.Global.types.MeasurementTypeEnum" + end return clusters.Global.types.MeasurementAccuracyStruct({ measurement_type = clusters.Global.types.MeasurementTypeEnum.SOIL_MOISTURE, measured = true, From 1be5fd4968509e4738e9fb22428437295872cb25 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 8 Jul 2026 17:11:17 -0500 Subject: [PATCH 462/538] Aligned handlers and fields in value magnitude and added tests --- .../src/test/test_zenwithin_thermostat.lua | 361 ++++++++++++++++++ .../zigbee-thermostat/src/zenwithin/init.lua | 6 +- 2 files changed, 364 insertions(+), 3 deletions(-) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua index 947923e33f..721853579a 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua @@ -523,4 +523,365 @@ test.register_coroutine_test( } ) +test.register_message_test( + "MinHeatSetpointLimit report stores device field", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.MinHeatSetpointLimit:build_test_attr_report(mock_device, 500) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "MaxHeatSetpointLimit report stores device field", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.MaxHeatSetpointLimit:build_test_attr_report(mock_device, 3500) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "MinCoolSetpointLimit report stores device field", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.MinCoolSetpointLimit:build_test_attr_report(mock_device, 1600) } + } + }, + { + min_api_version = 17 + } +) + +test.register_message_test( + "MaxCoolSetpointLimit report stores device field", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.MaxCoolSetpointLimit:build_test_attr_report(mock_device, 3200) } + } + }, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Heating setpoint is clamped to MinHeatSetpointLimit", + function() + -- Receive MinHeatSetpointLimit = 18.0 C (1800 centidegrees) + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.MinHeatSetpointLimit:build_test_attr_report(mock_device, 1800) + }) + test.wait_for_events() + + -- Put device in heat mode so the setpoint is applied immediately + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, 0x04) -- HEAT + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.heat()) + ) + test.wait_for_events() + + -- Try to set heating setpoint below the stored min (16 C < 18 C); expect it clamped to 18 C + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatHeatingSetpoint", component = "main", command = "setHeatingSetpoint", args = { 16 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 1800) -- clamped to 18.0 C + }) + test.mock_time.advance_time(2) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Heating setpoint is clamped to MaxHeatSetpointLimit", + function() + -- Receive MaxHeatSetpointLimit = 30.0 C (3000 centidegrees) + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.MaxHeatSetpointLimit:build_test_attr_report(mock_device, 3000) + }) + test.wait_for_events() + + -- Put device in heat mode + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, 0x04) -- HEAT + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.heat()) + ) + test.wait_for_events() + + -- Try to set heating setpoint above the stored max (35 C > 30 C); expect it clamped to 30 C + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatHeatingSetpoint", component = "main", command = "setHeatingSetpoint", args = { 35 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 3000) -- clamped to 30.0 C + }) + test.mock_time.advance_time(2) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Cooling setpoint is clamped to MinCoolSetpointLimit", + function() + -- Receive MinCoolSetpointLimit = 18.0 C (1800 centidegrees) + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.MinCoolSetpointLimit:build_test_attr_report(mock_device, 1800) + }) + test.wait_for_events() + + -- Put device in cool mode so the setpoint is applied immediately + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, 0x03) -- COOL + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.cool()) + ) + test.wait_for_events() + + -- Try to set cooling setpoint below the stored min (16 C < 18 C); expect it clamped to 18 C + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatCoolingSetpoint", component = "main", command = "setCoolingSetpoint", args = { 16 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, 1800) -- clamped to 18.0 C + }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Cooling setpoint is clamped to MaxCoolSetpointLimit", + function() + -- Receive MaxCoolSetpointLimit = 30.0 C (3000 centidegrees) + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.MaxCoolSetpointLimit:build_test_attr_report(mock_device, 3000) + }) + test.wait_for_events() + + -- Put device in cool mode + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, 0x03) -- COOL + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.cool()) + ) + test.wait_for_events() + + -- Try to set cooling setpoint above the stored max (35 C > 30 C); expect it clamped to 30 C + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatCoolingSetpoint", component = "main", command = "setCoolingSetpoint", args = { 35 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, 3000) -- clamped to 30.0 C + }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +-- Integration test: full set_cooling_setpoint clamping path +-- +-- Exercises every branch in set_cooling_setpoint that involves get_field / default: +-- +-- Phase 1 – no limit fields present yet → clamp uses DEFAULT_MIN (4.0) / DEFAULT_MAX (37.5) +-- A value already inside the defaults passes through unchanged. +-- A value above DEFAULT_MAX is clamped down to 37.5 C. +-- +-- Phase 2 – device reports MinCoolSetpointLimit (20.0 C) and MaxCoolSetpointLimit (28.0 C) +-- stored via setpoint_limit_handler → get_field now returns real device limits. +-- A value inside those limits passes through unchanged. +-- A value below MinCoolSetpointLimit is clamped up to 20.0 C. +-- A value above MaxCoolSetpointLimit is clamped down to 28.0 C. +-- +-- Phase 3 – device switches to heat mode. +-- setCoolingSetpoint is rejected: driver defers with 10s backstop, +-- then re-emits the last known cooling setpoint without writing to the device. +test.register_coroutine_test( + "set_cooling_setpoint: default clamp, then device-reported limit clamp, then heat-mode rejection", + function() + -- ── Phase 1: no limit fields – defaults apply ───────────────────────────── + + -- Put device in cool mode so setpoints are applied immediately throughout phase 1 & 2 + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, 0x03) -- COOL + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.cool()) + ) + test.wait_for_events() + + -- 1a. Value within defaults (25 C): written as-is + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatCoolingSetpoint", component = "main", command = "setCoolingSetpoint", args = { 25 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, 2500) + }) + test.wait_for_events() + + -- Establish the device's reported current cooling setpoint state (needed later for re-emit) + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.OccupiedCoolingSetpoint:build_test_attr_report(mock_device, 2500) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 25.0, unit = "C" })) + ) + test.wait_for_events() + + -- 1b. Value above DEFAULT_MAX (38 C > 37.5 C): clamped to 37.5 C → written as 3750 + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatCoolingSetpoint", component = "main", command = "setCoolingSetpoint", args = { 38 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, 3750) + }) + test.wait_for_events() + + -- ── Phase 2: device reports real limits; get_field overrides defaults ────── + + -- Device sends MinCoolSetpointLimit = 20.0 C and MaxCoolSetpointLimit = 28.0 C + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.MinCoolSetpointLimit:build_test_attr_report(mock_device, 2000) + }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.MaxCoolSetpointLimit:build_test_attr_report(mock_device, 2800) + }) + test.wait_for_events() + + -- 2a. Value inside device limits (24 C): written as-is + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatCoolingSetpoint", component = "main", command = "setCoolingSetpoint", args = { 24 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, 2400) + }) + test.wait_for_events() + + -- 2b. Value below device MinCoolSetpointLimit (18 C < 20 C): clamped to 20.0 C + -- (18 is above DEFAULT_MIN of 4.0, proving the device field is used, not the default) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatCoolingSetpoint", component = "main", command = "setCoolingSetpoint", args = { 18 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, 2000) + }) + test.wait_for_events() + + -- 2c. Value above device MaxCoolSetpointLimit (30 C > 28 C): clamped to 28.0 C + -- (30 is below DEFAULT_MAX of 37.5, proving the device field is used, not the default) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatCoolingSetpoint", component = "main", command = "setCoolingSetpoint", args = { 30 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, 2800) + }) + test.wait_for_events() + + -- ── Phase 3: device switches to heat mode; setCoolingSetpoint is deferred ── + + -- Re-establish a known current cooling setpoint for the re-emit assertion + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.OccupiedCoolingSetpoint:build_test_attr_report(mock_device, 2400) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 24.0, unit = "C" })) + ) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, 0x04) -- HEAT + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.heat()) + ) + test.wait_for_events() + + -- 3. setCoolingSetpoint while in heat mode: driver stores the field (clamped to 24 C by + -- device limits), then takes the else-branch and arms the 10s backstop timer. + -- After the timer fires, update_device_setpoint detects heat mode, discards the pending + -- cooling value, and re-emits the last reported cooling setpoint (24.0 C) unchanged. + test.timer.__create_and_queue_test_time_advance_timer(10, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatCoolingSetpoint", component = "main", command = "setCoolingSetpoint", args = { 24 } } + }) + test.wait_for_events() + + test.mock_time.advance_time(10) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 24.0, unit = "C" })) + ) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/zenwithin/init.lua b/drivers/SmartThings/zigbee-thermostat/src/zenwithin/init.lua index 1b71db2609..f614eb3210 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/zenwithin/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/zenwithin/init.lua @@ -70,7 +70,7 @@ end local setpoint_limit_handler = function(limit_type) return function(driver, device, limit) - device:set_field(limit_type, limit.value, {persist = true}) + device:set_field(limit_type, limit.value / 100.0, {persist = true}) end end @@ -122,8 +122,8 @@ local set_cooling_setpoint = function(driver, device, command) value = utils.f_to_c(value) end value = utils.clamp_value(value, - device:get_field(MIN_HEAT_LIMIT) or DEFAULT_MIN_SETPOINT, - device:get_field(MAX_HEAT_LIMIT) or DEFAULT_MAX_SETPOINT) + device:get_field(MIN_COOL_LIMIT) or DEFAULT_MIN_SETPOINT, + device:get_field(MAX_COOL_LIMIT) or DEFAULT_MAX_SETPOINT) device:set_field(COOLING_SETPOINT, value) local current_mode = device:get_latest_state("main", ThermostatMode.ID, ModeAttribute.NAME) if current_mode == ModeAttribute.cool.NAME or current_mode == ModeAttribute.auto.NAME then From d8fc96762014e968235f6eab03e8e59308b64418 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Thu, 9 Jul 2026 15:20:29 -0500 Subject: [PATCH 463/538] Scaled zenwithin subdriver to centidegrees --- .../zigbee-thermostat/src/zenwithin/init.lua | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/SmartThings/zigbee-thermostat/src/zenwithin/init.lua b/drivers/SmartThings/zigbee-thermostat/src/zenwithin/init.lua index f614eb3210..f39da8afe5 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/zenwithin/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/zenwithin/init.lua @@ -28,9 +28,9 @@ local HEATING_SETPOINT = "heatingSetpoint" local BAT_MIN = 3.4 -- voltage when device UI starts to die, ie, when battery fails local BAT_MAX = 6.0 -- 4 batteries at 1.5V (6.0V) -local DEFAULT_MIN_SETPOINT = 4.0 -local DEFAULT_MAX_SETPOINT = 37.5 - +local DEFAULT_MIN_SETPOINT = 400 -- 4.0C +local DEFAULT_MAX_SETPOINT = 3750 -- 37.5C +local FAHRENHEIT_THRESHOLD = 40 -- 40C -- In zenwithin sub driver, supported thermostat mode follow preference option because sensor always returns 'all mode possible' local SUPPORTED_THERMOSTAT_MODES = { [0x01] = { ModeAttribute.off.NAME, ModeAttribute.heat.NAME }, @@ -70,7 +70,7 @@ end local setpoint_limit_handler = function(limit_type) return function(driver, device, limit) - device:set_field(limit_type, limit.value / 100.0, {persist = true}) + device:set_field(limit_type, limit.value, {persist = true}) end end @@ -93,11 +93,12 @@ local update_device_setpoint = function(device) local current_mode = device:get_latest_state("main", ThermostatMode.ID, ModeAttribute.NAME) if (current_mode == ModeAttribute.heat.NAME or current_mode == ModeAttribute.emergency_heat.NAME) and cooling_setpoint ~= nil then - cooling_setpoint = device:get_latest_state("main", ThermostatCoolingSetpoint.ID, ThermostatCoolingSetpoint.coolingSetpoint.NAME) -- tried to set cooling setpoint while device was in heat mode + cooling_setpoint = device:get_latest_state("main", ThermostatCoolingSetpoint.ID, ThermostatCoolingSetpoint.coolingSetpoint.NAME) device:emit_event(ThermostatCoolingSetpoint.coolingSetpoint({value = cooling_setpoint, unit = "C"})) cooling_setpoint = nil elseif (current_mode == ModeAttribute.cool.NAME) and heating_setpoint ~= nil then + -- tried to set heating setpoint while device was in cool mode heating_setpoint = device:get_latest_state("main", ThermostatHeatingSetpoint.ID, ThermostatHeatingSetpoint.heatingSetpoint.NAME) device:emit_event(ThermostatHeatingSetpoint.heatingSetpoint({value = heating_setpoint, unit = "C"})) heating_setpoint = nil @@ -108,20 +109,21 @@ local update_device_setpoint = function(device) end if (heating_setpoint ~= nil) then - device:send(Thermostat.attributes.OccupiedHeatingSetpoint:write(device, utils.round(heating_setpoint*100))) + device:send(Thermostat.attributes.OccupiedHeatingSetpoint:write(device, utils.round(heating_setpoint))) end if (cooling_setpoint ~= nil) then - device:send(Thermostat.attributes.OccupiedCoolingSetpoint:write(device, utils.round(cooling_setpoint*100))) + device:send(Thermostat.attributes.OccupiedCoolingSetpoint:write(device, utils.round(cooling_setpoint))) end end local set_cooling_setpoint = function(driver, device, command) local value = command.args.setpoint - if value >= 40 then -- we got a command in fahrenheit + if value >= FAHRENHEIT_THRESHOLD then -- we got a command in fahrenheit value = utils.f_to_c(value) end - value = utils.clamp_value(value, + -- Scale up to centidegrees to align with the rest of the values + value = utils.clamp_value(value * 100, device:get_field(MIN_COOL_LIMIT) or DEFAULT_MIN_SETPOINT, device:get_field(MAX_COOL_LIMIT) or DEFAULT_MAX_SETPOINT) device:set_field(COOLING_SETPOINT, value) @@ -138,10 +140,11 @@ end local set_heating_setpoint = function(driver, device, command) local value = command.args.setpoint - if value >= 40 then -- we got a command in fahrenheit + if value >= FAHRENHEIT_THRESHOLD then -- we got a command in fahrenheit value = utils.f_to_c(value) end - value = utils.clamp_value(value, + -- Scale up to centidegrees to align with the rest of the values + value = utils.clamp_value(value * 100, device:get_field(MIN_HEAT_LIMIT) or DEFAULT_MIN_SETPOINT, device:get_field(MAX_HEAT_LIMIT) or DEFAULT_MAX_SETPOINT) device:set_field(HEATING_SETPOINT, value) From b6e09b301c8b6cd2279d0ab3dafa84408dd17433 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri, 17 Jul 2026 09:48:40 -0500 Subject: [PATCH 464/538] Zwave Lock: Remove Migration on Added (#3127) * Zwave Lock: Remove Migration on Added * update and remove tests as needed --- drivers/SmartThings/zwave-lock/src/init.lua | 10 - .../src/test/test_init_lifecycle_handlers.lua | 63 ----- .../zwave-lock/src/test/test_keywe_lock.lua | 3 - .../zwave-lock/src/test/test_samsung_lock.lua | 217 --------------- .../zwave-lock/src/test/test_schlage_lock.lua | 263 ------------------ 5 files changed, 556 deletions(-) delete mode 100644 drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua delete mode 100644 drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua diff --git a/drivers/SmartThings/zwave-lock/src/init.lua b/drivers/SmartThings/zwave-lock/src/init.lua index 6c85639625..b49489a008 100644 --- a/drivers/SmartThings/zwave-lock/src/init.lua +++ b/drivers/SmartThings/zwave-lock/src/init.lua @@ -15,7 +15,6 @@ local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) local capabilities = require "st.capabilities" local consts = require "lock_utils.constants" -local lock_utils = require "lock_utils.utils" local table_utils = require "lock_utils.tables" local zwave_handlers = require "lock_handlers.zwave_responses" local capability_handlers = require "lock_handlers.capabilities" @@ -24,13 +23,6 @@ local capability_handlers = require "lock_handlers.capabilities" local LockLifecycle = {} function LockLifecycle.device_added(driver, device) - if device:supports_capability(capabilities.lockCodes) and device._provisioning_state == "TYPED" then - -- set the migrated field to true so new devices use lockCredentials/lockUsers from the start. - -- auto-migration is only run for typed devices, as provisioned devices have already been onboarded, - -- and should be migrated manually by the user. - device:emit_event(capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) - device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) - end if device:supports_capability(capabilities.tamperAlert) then device:emit_event(capabilities.tamperAlert.tamper.clear()) end @@ -40,8 +32,6 @@ function LockLifecycle.device_added(driver, device) command = capabilities.refresh.commands.refresh.NAME, args = {} }) - -- ensure our user/credential state is accurate to the current device state - device.thread:call_with_delay(5, function() lock_utils.sync_device_state(device) end) end function LockLifecycle.init(driver, device) diff --git a/drivers/SmartThings/zwave-lock/src/test/test_init_lifecycle_handlers.lua b/drivers/SmartThings/zwave-lock/src/test/test_init_lifecycle_handlers.lua index ac127f234a..d521e2d3a0 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_init_lifecycle_handlers.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_init_lifecycle_handlers.lua @@ -38,13 +38,6 @@ local mock_device_base = test.mock_device.build_test_zwave_device({ zwave_endpoints = zwave_lock_endpoints, }) --- Same profile but provisioning_state = "TYPED" (freshly fingerprinted) -local mock_device_typed = test.mock_device.build_test_zwave_device({ - profile = t_utils.get_profile_definition("base-lock.yml"), - zwave_endpoints = zwave_lock_endpoints, - _provisioning_state = "TYPED", -}) - -- lock-battery profile: lock + battery only (no lockCodes / lockCredentials) local mock_device_battery = test.mock_device.build_test_zwave_device({ profile = t_utils.get_profile_definition("lock-battery.yml"), @@ -62,31 +55,6 @@ end -- added (device_added) -- ============================================================================ -test.register_coroutine_test( - "added: TYPED device with lockCodes emits migrated event, persists SLGA_MIGRATED, and injects refresh", - function() - test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "added" }) - - -- Migrated event emitted for TYPED+lockCodes device - test.socket.capability:__expect_send( - mock_device_typed:generate_test_message("main", - capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) - ) - -- inject_capability_command calls the refresh handler: - -- DoorLock:OperationGet, Battery:Get, UserCode:UsersNumberGet (no cached values) - test.socket.zwave:__expect_send(DoorLock:OperationGet({}):build_test_tx(mock_device_typed.id)) - test.socket.zwave:__expect_send(Battery:Get({}):build_test_tx(mock_device_typed.id)) - test.socket.zwave:__expect_send(UserCode:UsersNumberGet({}):build_test_tx(mock_device_typed.id)) - test.wait_for_events() - - assert( - mock_device_typed:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true, - "SLGA_MIGRATED must be true after added fires for a TYPED device" - ) - end, - { test_init = make_test_init(mock_device_typed) } -) - test.register_coroutine_test( "added: non-TYPED (PROVISIONED) device with lockCodes does NOT emit migrated but still injects refresh", function() @@ -163,36 +131,5 @@ test.register_coroutine_test( { test_init = make_test_init(mock_device_battery) } ) --- ============================================================================ --- driverSwitched (LockLifecycle.driver_switched) --- ============================================================================ - -test.register_coroutine_test( - "driver_switched: device with lockCodes and migrated=true persists SLGA_MIGRATED and updates metadata", - function() - test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "added" }) - - -- Migrated event emitted for TYPED+lockCodes device - test.socket.capability:__expect_send( - mock_device_typed:generate_test_message("main", - capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })) - ) - -- inject_capability_command calls the refresh handler: - -- DoorLock:OperationGet, Battery:Get, UserCode:UsersNumberGet (no cached values) - test.socket.zwave:__expect_send(DoorLock:OperationGet({}):build_test_tx(mock_device_typed.id)) - test.socket.zwave:__expect_send(Battery:Get({}):build_test_tx(mock_device_typed.id)) - test.socket.zwave:__expect_send(UserCode:UsersNumberGet({}):build_test_tx(mock_device_typed.id)) - test.wait_for_events() - - -- driverSwitched occurs after added, so migrated=true is already set in the capability state cache - test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "driver_switched" }) - mock_device_typed:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - test.wait_for_events() - - assert(mock_device_typed:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true) - end, - { test_init = make_test_init(mock_device_typed) } -) - -- ============================================================================ test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua index f6554141b0..6724094462 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua @@ -9,7 +9,6 @@ local DoorLock = (require "st.zwave.CommandClass.DoorLock")({ version = 1 }) local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) -local lock_utils = require "lock_utils.constants".DRIVER_STATE test.disable_startup_messages() @@ -45,7 +44,6 @@ test.set_test_init_function(test_init) local function added() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))) test.socket.zwave:__expect_send( DoorLock:OperationGet({}):build_test_tx(mock_device.id) @@ -58,7 +56,6 @@ local function added() ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) test.wait_for_events() - assert(mock_device:get_field(lock_utils.SLGA_MIGRATED) == true, "Device should be marked as migrated") end test.register_coroutine_test( diff --git a/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua deleted file mode 100644 index 532e1da1a9..0000000000 --- a/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock.lua +++ /dev/null @@ -1,217 +0,0 @@ --- Copyright 2026 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local test = require "integration_test" -local capabilities = require "st.capabilities" -local zw_test_utils = require "integration_test.zwave_test_utils" -local t_utils = require "integration_test.utils" -local UserCode = (require "st.zwave.CommandClass.UserCode")({version=1}) -local DoorLock = (require "st.zwave.CommandClass.DoorLock")({version=1}) -local Battery = (require "st.zwave.CommandClass.Battery")({version=1}) -local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) -local lock_utils = require "lock_utils.constants".DRIVER_STATE - -test.disable_startup_messages() - -local SAMSUNG_MANUFACTURER_ID = 0x022E -local SAMSUNG_PRODUCT_TYPE = 0x0001 -local SAMSUNG_PRODUCT_ID = 0x0001 - -local mock_device = test.mock_device.build_test_zwave_device( - { - profile = t_utils.get_profile_definition("base-lock.yml"), - _provisioning_state = "TYPED", - zwave_manufacturer_id = SAMSUNG_MANUFACTURER_ID, - zwave_product_type = SAMSUNG_PRODUCT_TYPE, - zwave_product_id = SAMSUNG_PRODUCT_ID, - } -) - -local function test_init() - test.mock_device.add_test_device(mock_device) -end - -test.set_test_init_function(test_init) - -local function added() - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))) - - test.socket.zwave:__expect_send( - DoorLock:OperationGet({}):build_test_tx(mock_device.id) - ) - test.socket.zwave:__expect_send( - Battery:Get({}):build_test_tx(mock_device.id) - ) - test.socket.zwave:__expect_send( - UserCode:UsersNumberGet({}):build_test_tx(mock_device.id) - ) - test.wait_for_events() - assert(mock_device:get_field(lock_utils.SLGA_MIGRATED) == true, "Device should be marked as migrated") -end - -test.register_coroutine_test( - "When the device is added an unlocked event should be sent", - function() - added() - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lock.lock.unlocked()) - ) - mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end -) - -test.register_coroutine_test( - "Setting a user code name should be handled", - function() - added() - test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCredentials.ID, command = "addCredential", args = { 0, "guest", "pin", "1234"} } }) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - UserCode:Set({user_identifier = 1, user_code = "1234", user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS}) - ) - ) - test.wait_for_events() - test.socket.zwave:__queue_receive({ - mock_device.id, - Notification:Report({ - notification_type = Notification.notification_type.ACCESS_CONTROL, - event = Notification.event.access_control.NEW_USER_CODE_ADDED, - event_parameter = "" } - ) - }) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - UserCode:Get({user_identifier = 1}) - ) - ) - test.socket.zwave:__queue_receive({ - mock_device.id, - UserCode:Report({ - user_identifier = 1, - user_code = "1234", - user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS - }) - }) - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockCredentials.credentials({{ userIndex = 1, credentialIndex = 1, credentialType = "pin" }}, - { visibility = { displayed = false } }) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockCredentials.commandResult( - { commandName = "addCredential", statusCode = "success", credentialIndex = 1, userIndex = 1}, - { state_change = true, visibility = { displayed = false } } - ) - ) - ) - end -) - -test.register_coroutine_test( - "Notification about correctly added code should be handled", - function() - added() - test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCredentials.ID, command = "addCredential", args = { 0, "guest", "pin", "1234"} } }) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - UserCode:Set({user_identifier = 1, user_code = "1234", user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS}) - ) - ) - test.wait_for_events() - test.socket.zwave:__queue_receive({ mock_device.id, - Notification:Report({ - notification_type = Notification.notification_type.ACCESS_CONTROL, - event = Notification.event.access_control.NEW_USER_CODE_ADDED, - event_parameter = string.char(1) - }) - }) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - UserCode:Get({user_identifier = 1}) - ) - ) - end -) - -test.register_coroutine_test( - "All user codes should be reported as deleted upon changing Master Code", - function() - added() - mock_device:set_field("persistedUsers", { - { userIndex = 1, userName = "Code 1", userType = "guest" }, - { userIndex = 2, userName = "Code 2", userType = "guest" }, - { userIndex = 3, userName = "Code 3", userType = "guest" }, - }, { persist = true }) - mock_device:set_field("persistedCredentials", { - { userIndex = 1, credentialIndex = 1, credentialType = "pin" }, - { userIndex = 2, credentialIndex = 2, credentialType = "pin" }, - { userIndex = 3, credentialIndex = 3, credentialType = "pin" }, - }, { persist = true }) - test.socket.capability:__queue_receive({ - mock_device.id, - { - capability = capabilities.lockUsers.ID, - command = "updateUser", - args = {1, "new name", "guest" } - }, - }) - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockUsers.commandResult( - { commandName = "updateUser", statusCode = "success", userIndex = 1 }, - { state_change = true, visibility = { displayed = false } } - ) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockUsers.users({ - { userIndex = 1, userName = "new name", userType = "guest" }, - { userIndex = 2, userName = "Code 2", userType = "guest" }, - { userIndex = 3, userName = "Code 3", userType = "guest" } - }, - { visibility = { displayed = false } }) - ) - ) - test.wait_for_events() - test.socket.zwave:__queue_receive({ - mock_device.id, - Notification:Report({ - notification_type = Notification.notification_type.ACCESS_CONTROL, - event = Notification.event.access_control.NEW_PROGRAM_CODE_ENTERED_UNIQUE_CODE_FOR_LOCK_CONFIGURATION, - event_parameter = "" } - ) - }) - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockUsers.users({}, - { visibility = { displayed = false } }) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockCredentials.credentials({}, - { visibility = { displayed = false } }) - ) - ) - end -) - -test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua deleted file mode 100644 index 90d919c27e..0000000000 --- a/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock.lua +++ /dev/null @@ -1,263 +0,0 @@ --- Copyright 2026 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 t_utils = require "integration_test.utils" -local lock_utils = require "lock_utils.constants".DRIVER_STATE - -local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 }) -local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 2 }) -local Association = (require "st.zwave.CommandClass.Association")({ version = 1 }) -local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) -local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) -local DoorLock = (require "st.zwave.CommandClass.DoorLock")({ version = 1 }) - -test.disable_startup_messages() - -local SCHLAGE_MANUFACTURER_ID = 0x003B -local SCHLAGE_PRODUCT_TYPE = 0x0002 -local SCHLAGE_PRODUCT_ID = 0x0469 - --- supported comand classes -local zwave_lock_endpoints = { - { - command_classes = { - {value = zw.BATTERY}, - {value = zw.DOOR_LOCK}, - {value = zw.USER_CODE}, - {value = zw.NOTIFICATION} - } - } -} - -local mock_device = test.mock_device.build_test_zwave_device( - { - profile = t_utils.get_profile_definition("base-lock.yml"), - _provisioning_state = "TYPED", - zwave_endpoints = zwave_lock_endpoints, - zwave_manufacturer_id = SCHLAGE_MANUFACTURER_ID, - zwave_product_type = SCHLAGE_PRODUCT_TYPE, - zwave_product_id = SCHLAGE_PRODUCT_ID, - } -) - -local SCHLAGE_LOCK_CODE_LENGTH_PARAM = {number = 16, size = 1} - -local function test_init() - test.mock_device.add_test_device(mock_device) -end - -test.set_test_init_function(test_init) - -local function added() - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))) - - test.socket.zwave:__expect_send( - DoorLock:OperationGet({}):build_test_tx(mock_device.id) - ) - test.socket.zwave:__expect_send( - Battery:Get({}):build_test_tx(mock_device.id) - ) - test.socket.zwave:__expect_send( - UserCode:UsersNumberGet({}):build_test_tx(mock_device.id) - ) - test.wait_for_events() - assert(mock_device:get_field(lock_utils.SLGA_MIGRATED) == true, "Device should be marked as migrated") -end - -test.register_coroutine_test( - "Setting a user code should result in the named code changed event firing", - function() - added() - test.timer.__create_and_queue_test_time_advance_timer(4.2, "oneshot") - test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCredentials.ID, command = "addCredential", args = { 0, "guest", "pin", "1234"} } }) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - Configuration:Get({parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number}) - ) - ) - test.wait_for_events() - test.mock_time.advance_time(4.2) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - UserCode:Set({user_identifier = 1, user_code = "1234", user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS}) - ) - ) - test.wait_for_events() - test.socket.zwave:__queue_receive({mock_device.id, UserCode:Report({user_identifier = 1, user_id_status = UserCode.user_id_status.STATUS_NOT_AVAILABLE, user_code="0000\n\r"}) }) - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockCredentials.credentials({{ userIndex = 1, credentialIndex = 1, credentialType = "pin" }}, - { visibility = { displayed = false } }) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockCredentials.commandResult( - { commandName = "addCredential", statusCode = "success", credentialIndex = 1, userIndex = 1}, - { state_change = true, visibility = { displayed = false } } - ) - ) - ) - end -) - -test.register_coroutine_test( - "Configuration report should be handled", - function() - added() - test.socket.zwave:__queue_receive({ - mock_device.id, - Configuration:Report({ - parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number, - configuration_value = 6 - }) - }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCredentials.minPinCodeLen(6)) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCredentials.maxPinCodeLen(6)) - ) - end -) - -test.register_coroutine_test( - "Configuration report indicating code deletion should be handled", - function() - added() - test.socket.zwave:__queue_receive({ - mock_device.id, - Configuration:Report({ - parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number, - configuration_value = 6 - }) - }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCredentials.minPinCodeLen(6)) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCredentials.maxPinCodeLen(6)) - ) - test.wait_for_events() - test.socket.zwave:__queue_receive({ - mock_device.id, - Configuration:Report({ - parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number, - configuration_value = 4 - }) - }) - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockUsers.users({}, - { visibility = { displayed = false } }) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockCredentials.credentials({}, - { visibility = { displayed = false } }) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCredentials.minPinCodeLen(4)) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCredentials.maxPinCodeLen(4)) - ) - end -) - -test.register_coroutine_test( - "User code report indicating master code is available should indicate code deletion", - function() - added() - test.socket.zwave:__queue_receive({ - mock_device.id, - UserCode:Report({ - user_identifier = 0, - user_id_status = UserCode.user_id_status.AVAILABLE - }) - }) - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockUsers.users({}, - { visibility = { displayed = false } }) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockCredentials.credentials({}, - { visibility = { displayed = false } }) - ) - ) - end -) - -test.register_coroutine_test( - "Device should send appropriate configuration messages", - function() - added() - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - Configuration:Get({ - parameter_number = SCHLAGE_LOCK_CODE_LENGTH_PARAM.number - }) - ) - ) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - Association:Set({ - grouping_identifier = 2, - node_ids = {} - }) - ) - ) - mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end -) - -test.register_coroutine_test( - "Basic Sets should result in an Association remove", - function() - added() - test.socket.zwave:__queue_receive({ - mock_device.id, - Basic:Set({ - value = 0x00 - }) - }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lock.lock.unlocked({})) - ) - test.socket.zwave:__expect_send( - zw_test_utils.zwave_test_build_send_command( - mock_device, - Association:Remove({ - grouping_identifier = 1, - node_ids = {} - }) - ) - ) - end -) - -test.run_registered_tests() From 834ee18ae6001d2437b8a3f9cf60ba7894f85cc1 Mon Sep 17 00:00:00 2001 From: cjswedes Date: Fri, 17 Jul 2026 09:52:46 -0500 Subject: [PATCH 465/538] zigbee-range-extender: guard against nil device in health check Driver:get_device_info can return (nil, err) when the devices API request fails or the device is no longer known to the driver (e.g. transient device_api error, or a device removed between building the device list and looking it up). device_health_check ignored the second return value and unconditionally called device:send(...), which internally calls device:get_short_address() in cluster_base.lua, causing 'attempt to call a nil value (method get_short_address)' when device was nil. Impact: the periodic health-check coroutine crashes with an unhandled error, which can disrupt the scheduled health-check loop and spam hub logs, potentially leaving range extender devices marked unhealthy/unresponsive even though the actual issue was just a lookup failure. Fix: check the return value of get_device_info and skip sending the ZCLVersion read (logging a warning) when the device could not be resolved, instead of dereferencing nil. Co-Authored-By: OpenCode --- drivers/SmartThings/zigbee-range-extender/src/init.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/zigbee-range-extender/src/init.lua b/drivers/SmartThings/zigbee-range-extender/src/init.lua index f6ba3c1ffc..f06988f5cf 100644 --- a/drivers/SmartThings/zigbee-range-extender/src/init.lua +++ b/drivers/SmartThings/zigbee-range-extender/src/init.lua @@ -3,6 +3,7 @@ local capabilities = require "st.capabilities" +local log = require "log" local defaults = require "st.zigbee.defaults" local Basic = (require "st.zigbee.zcl.clusters").Basic local ZigbeeDriver = require "st.zigbee" @@ -33,8 +34,13 @@ local zigbee_range_extender_driver = ZigbeeDriver("zigbee-range-extender", zigbe function zigbee_range_extender_driver:device_health_check() local device_list = self.device_api.get_device_list() for _, device_id in ipairs(device_list) do - local device = self:get_device_info(device_id, false) - device:send(Basic.attributes.ZCLVersion:read(device)) + local device, err = self:get_device_info(device_id, false) + if device == nil then + log.warn_with({ hub_logs = true }, + string.format("device_health_check failed to get device info for device_id %s: %s", device_id, err)) + else + device:send(Basic.attributes.ZCLVersion:read(device)) + end end end From e6811a1f2fac309567d851d5e93dd769a0bced34 Mon Sep 17 00:00:00 2001 From: cjswedes Date: Fri, 17 Jul 2026 09:52:47 -0500 Subject: [PATCH 466/538] jbl: fix coroutine crash from concatenating nil credential in add_header update_connection() fetched the CREDENTIAL device field and passed it directly to jbl_api:add_header without checking for nil, unlike the sibling call site in device_added(). When a device reconnects via find_new_connection() before a credential has been stored on the device, add_header's string concatenation ('.. .. value') throws 'attempt to concatenate a nil value (local value)', which crashes the coroutine handling the device and can leave the device connection in a bad state (no SSE/health-check updates) until the driver restarts. Fix: - Guard update_connection so it logs the missing credential, marks the device offline, and returns early instead of reaching add_header with a nil value. - Harden jbl_api:add_header to tostring() its key/value before concatenation as defense-in-depth, so any other future caller can't trigger the same crash. Co-Authored-By: OpenCode --- drivers/SmartThings/jbl/src/init.lua | 5 +++++ drivers/SmartThings/jbl/src/jbl/api.lua | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/jbl/src/init.lua b/drivers/SmartThings/jbl/src/init.lua index f422ecf84c..830a98a181 100644 --- a/drivers/SmartThings/jbl/src/init.lua +++ b/drivers/SmartThings/jbl/src/init.lua @@ -82,6 +82,11 @@ local function update_connection(driver, device, device_ip, device_info) local conn_info = driver.discovery_helper.get_connection_info(driver, device_dni, device_ip, device_info) local credential = device:get_field(fields.CREDENTIAL) + if not credential then + log.error("update_connection : failed to find credential, dni = " .. tostring(device_dni)) + device:offline() + return + end conn_info:add_header(CREDENTIAL_KEY_HEADER, credential) diff --git a/drivers/SmartThings/jbl/src/jbl/api.lua b/drivers/SmartThings/jbl/src/jbl/api.lua index 95e25d14c3..7479289549 100644 --- a/drivers/SmartThings/jbl/src/jbl/api.lua +++ b/drivers/SmartThings/jbl/src/jbl/api.lua @@ -77,7 +77,7 @@ function jbl_api.new_device_manager(bridge_ip, bridge_info, socket_builder) end function jbl_api:add_header(key, value) - log.info("add_header : " .. key .. ", " .. value) + log.info("add_header : " .. tostring(key) .. ", " .. tostring(value)) self.headers[key] = value end From 57ae75d26dbd59d9bab3f695887a2551bc28ae2e Mon Sep 17 00:00:00 2001 From: cjswedes Date: Fri, 17 Jul 2026 09:53:40 -0500 Subject: [PATCH 467/538] aqara-presence-sensor: guard against receive(0) crashing SSE reader The Aqara Presence Sensor FP2 driver uses an SSE/HTTP chunked-transfer client (lunchbox/sse/eventsource.lua) to stream events from the device. When a chunk-encoded response ends, the server sends a zero-length chunk header ("0\r\n"). The reader parsed this as recv_as_num == 0 and called sock:receive(0), which the shared cosock TCP socket (cosock/socket/tcp.lua) does not handle gracefully: its output transform asserts that the received data is non-nil, so a 0-byte receive raises "socket receive returned nil data" instead of returning a normal result. That assertion failure crashes the coroutine driving the device's LAN connection, which drops presence updates until the hub restarts the driver. Fix: when the parsed chunk size is 0, skip the socket receive call entirely and treat it as an empty read, then continue on to consume the trailing CRLF as before. This avoids ever calling receive(0) on the socket, preventing the crash while preserving existing timeout/ closed/error handling paths. Co-Authored-By: OpenCode --- .../src/lunchbox/sse/eventsource.lua | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/drivers/Aqara/aqara-presence-sensor/src/lunchbox/sse/eventsource.lua b/drivers/Aqara/aqara-presence-sensor/src/lunchbox/sse/eventsource.lua index 7c72f2d81a..9b64844821 100644 --- a/drivers/Aqara/aqara-presence-sensor/src/lunchbox/sse/eventsource.lua +++ b/drivers/Aqara/aqara-presence-sensor/src/lunchbox/sse/eventsource.lua @@ -365,18 +365,26 @@ local function open_action(source) local recv_as_num = tonumber(recv, 16) if recv_as_num ~= nil then - recv, err, partial = source._sock:receive(recv_as_num) - if err then - if err == "timeout" or err == "wantread" then - return - else - --- real error, close the connection. - if source._sock ~= nil then - source._sock:close() - source._sock = nil + -- A chunk size of 0 signals the end of the chunked transfer. Some + -- versions of the underlying socket library will raise a hard error + -- (rather than returning a graceful nil/"closed" pair) if we ask to + -- receive 0 bytes, so avoid calling receive() in that case entirely. + if recv_as_num == 0 then + recv = "" + else + recv, err, partial = source._sock:receive(recv_as_num) + if err then + if err == "timeout" or err == "wantread" then + return + else + --- real error, close the connection. + if source._sock ~= nil then + source._sock:close() + source._sock = nil + end + source.ready_state = EventSource.ReadyStates.CLOSED + return nil, err, partial end - source.ready_state = EventSource.ReadyStates.CLOSED - return nil, err, partial end end local _, err, partial = source._sock:receive('*l') -- clear the final line From 9d7daafdce5b26a9944690b6a31fdcc29c9c8024 Mon Sep 17 00:00:00 2001 From: cjswedes Date: Fri, 17 Jul 2026 09:54:08 -0500 Subject: [PATCH 468/538] zwave-sensor: guard tamper-clear timer against device removal The timed-tamper-clear sub-driver schedules a delayed callback (via device.thread:call_with_delay) to emit a tamperAlert clear event a fixed time after a tamper notification is received. If the device is removed from the hub before that timer fires, st.device's Device:deleted() swaps the device's metatable so that every field and method lookup returns nil. When the pending timer callback later runs and calls device:emit_event_for_endpoint(...), that method resolves to nil, producing: attempt to call a nil value (method 'emit_event_for_endpoint') This crashed the device's Lua thread coroutine, so any other pending work for that device (or shared device thread, since this driver uses shared_device_thread_enabled) could be disrupted, degrading the user experience for other devices using the same driver/thread. Fix: check device.id (which becomes nil once the device is deleted) before touching the device inside the delayed callback, and skip the event emission/timer cleanup if the device is no longer valid. Co-Authored-By: OpenCode --- .../zwave-sensor/src/timed-tamper-clear/init.lua | 7 +++++++ 1 file changed, 7 insertions(+) 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 8554dab28f..dd6cc672f4 100644 --- a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua @@ -22,6 +22,13 @@ local function handle_tamper_event(driver, device, cmd) device.thread:cancel_timer(tamper_timer) end device:set_field(TAMPER_TIMER, device.thread:call_with_delay(TAMPER_CLEAR, function() + -- The device can be removed while this timer is still pending. Once a device is + -- deleted its metatable is swapped so that all field/method lookups return nil + -- (see Device:deleted() in st.device), so guard against that here to avoid + -- "attempt to call a nil value (method 'emit_event_for_endpoint')" errors. + if device.id == nil then + return + end device:emit_event_for_endpoint(cmd.src_channel, capabilities.tamperAlert.tamper.clear()) device:set_field(TAMPER_TIMER, nil) end)) From e53691e9f6c10253a525263b54ace6e06177f561 Mon Sep 17 00:00:00 2001 From: cjswedes Date: Fri, 17 Jul 2026 09:56:57 -0500 Subject: [PATCH 469/538] zigbee-switch(inovelli): guard nil parent device in child notification handlers Child (notification) devices call device:get_parent_device() and then schedule a 1s-delayed closure that sends a manufacturer-specific command via the captured parent reference. If the parent device is removed (or otherwise not resolvable, e.g. during a join/removal race) at command time, get_parent_device() returns nil. The delayed closure then invokes cluster_base.build_manufacturer_specific_command(nil, ...), which calls device:get_short_address() on nil, crashing the coroutine with: attempt to call a nil value (method 'get_short_address') This meant on/off/level/color/color-temperature commands on an Inovelli notification child device could silently crash the driver (breaking further command handling) instead of just failing to notify the parent. Fix: check the parent device reference for nil immediately after get_parent_device() and bail out before scheduling the send, in on_handler, off_handler, switch_level_handler, set_color_temperature, and set_color. Also updated getNotificationValue to accept the already resolved parent device instead of re-fetching it, avoiding a second nil dereference of the same kind. Co-Authored-By: OpenCode --- .../zigbee-switch/src/inovelli/init.lua | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli/init.lua index a83a343663..97cd33e821 100644 --- a/drivers/SmartThings/zigbee-switch/src/inovelli/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/inovelli/init.lua @@ -258,11 +258,11 @@ local function huePercentToValue(value) else return utils.round(value / 100 * 255) end end -local function getNotificationValue(device, value) +local function getNotificationValue(device, parent, 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 + local effect = (parent and parent.preferences.notificationType) or 1 notificationValue = notificationValue + (effect*16777216) notificationValue = notificationValue + (huePercentToValue(value or color)*65536) notificationValue = notificationValue + (level*256) @@ -276,13 +276,16 @@ local function on_handler(driver, device, command) else device:emit_event(capabilities.switch.switch("on")) local dev = device:get_parent_device() + if dev == nil then + return + end 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))) + utils.serialize_int(getNotificationValue(device, dev),4,false,false))) end device.thread:call_with_delay(1,send_configuration) end @@ -294,6 +297,9 @@ local function on_handler(driver, device, command) else device:emit_event(capabilities.switch.switch("off")) local dev = device:get_parent_device() + if dev == nil then + return + end local send_configuration = function() dev:send(cluster_base.build_manufacturer_specific_command( dev, @@ -313,13 +319,16 @@ local function switch_level_handler(driver, device, command) 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() + if dev == nil then + return + end 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))) + utils.serialize_int(getNotificationValue(device, dev),4,false,false))) end device.thread:call_with_delay(1,send_configuration) end @@ -330,13 +339,16 @@ local function set_color_temperature(driver, device, command) device:emit_event(capabilities.colorTemperature.colorTemperature(command.args.temperature)) device:emit_event(capabilities.switch.switch("on")) local dev = device:get_parent_device() + if dev == nil then + return + end 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))) + utils.serialize_int(getNotificationValue(device, dev, 100),4,false,false))) end device.thread:call_with_delay(1,send_configuration) end @@ -346,13 +358,16 @@ local function set_color_temperature(driver, device, command) device:emit_event(capabilities.colorControl.saturation(command.args.color.saturation)) device:emit_event(capabilities.switch.switch("on")) local dev = device:get_parent_device() + if dev == nil then + return + end 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))) + utils.serialize_int(getNotificationValue(device, dev),4,false,false))) end device.thread:call_with_delay(1,send_configuration) end From 93d2b3527b070fe23bd95afb064ae30a10de797e Mon Sep 17 00:00:00 2001 From: cjswedes Date: Fri, 17 Jul 2026 09:57:26 -0500 Subject: [PATCH 470/538] matter-lock: guard nil capability event for unmapped LockState value Co-Authored-By: OpenCode --- drivers/SmartThings/matter-lock/src/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-lock/src/init.lua b/drivers/SmartThings/matter-lock/src/init.lua index 5d82801b67..f94702c496 100755 --- a/drivers/SmartThings/matter-lock/src/init.lua +++ b/drivers/SmartThings/matter-lock/src/init.lua @@ -436,10 +436,10 @@ local function lock_state_handler(driver, device, ib, response) -- In this case, two events occur. To prevent this, when both functions are called, -- it send the event after 1 second so that no event occurs in the lock_state_handler. device.thread:call_with_delay(1, function () - if ib.data.value ~= nil then + if ib.data.value ~= nil and LOCK_STATE[ib.data.value] ~= nil then device:emit_event(LOCK_STATE[ib.data.value]) else - device.log.warn("Lock State is nil") + device.log.warn(string.format("Received unknown Lock State: %s", ib.data.value)) end end) end From 4e3d3b0ae018b86fdc62dcebd9c6b8dc6ded6acd Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Mon, 20 Jul 2026 10:58:38 -0500 Subject: [PATCH 471/538] CHAD-18838: Exclude `zoneManagement` when `USERDEFINED` feature is absent (#3126) --- .../camera_utils/device_configuration.lua | 5 +- .../src/test/test_matter_camera.lua | 180 +++++++++++++++--- 2 files changed, 161 insertions(+), 24 deletions(-) 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 index 398150d063..0619194926 100644 --- 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 @@ -259,8 +259,9 @@ function CameraDeviceConfiguration.match_profile(device) clus_has_feature(clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_ZOOM) then table.insert(main_component_capabilities, capabilities.mechanicalPanTiltZoom.ID) end - 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.ZoneManagement.ID and has_server_cluster_type(ep_cluster) and + clusters.ZoneManagement.are_features_supported(clusters.ZoneManagement.types.Feature.USER_DEFINED, ep_cluster.feature_map) 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) then diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index d443bdc573..c238bfd2ca 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -62,7 +62,8 @@ local mock_device = test.mock_device.build_test_matter_device({ { cluster_id = clusters.ZoneManagement.ID, feature_map = clusters.ZoneManagement.types.Feature.TWO_DIMENSIONAL_CARTESIAN_ZONE | - clusters.ZoneManagement.types.Feature.PER_ZONE_SENSITIVITY, + clusters.ZoneManagement.types.Feature.PER_ZONE_SENSITIVITY | + clusters.ZoneManagement.types.Feature.USER_DEFINED, cluster_type = "SERVER" }, { @@ -120,6 +121,46 @@ local mock_device = test.mock_device.build_test_matter_device({ }) local mock_device_no_per_zone_sensitivity = 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, + cluster_type = "SERVER" + }, + { + cluster_id = clusters.ZoneManagement.ID, + feature_map = clusters.ZoneManagement.types.Feature.TWO_DIMENSIONAL_CARTESIAN_ZONE | + clusters.ZoneManagement.types.Feature.USER_DEFINED, + cluster_type = "SERVER" + }, + { + cluster_id = clusters.PushAvStreamTransport.ID, + cluster_type = "SERVER" + } + }, + device_types = { + {device_type_id = 0x0142, device_type_revision = 1} -- Camera + } + } + } +}) + +local mock_device_no_user_defined_zone = 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}, @@ -229,6 +270,26 @@ local function test_init_no_per_zone_sensitivity() mock_device_no_per_zone_sensitivity:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end +local subscribe_request_no_user_defined_zone +local subscribed_attributes_no_user_defined_zone = { + clusters.CameraAvStreamManagement.attributes.AttributeList, +} + +local function test_init_no_user_defined_zone() + test.mock_device.add_test_device(mock_device_no_user_defined_zone) + test.socket.device_lifecycle:__queue_receive({ mock_device_no_user_defined_zone.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_no_user_defined_zone.id, "init" }) + subscribe_request_no_user_defined_zone = subscribed_attributes_no_user_defined_zone[1]:subscribe(mock_device_no_user_defined_zone) + subscribe_request_no_user_defined_zone:merge(cluster_base.subscribe(mock_device_no_user_defined_zone, nil, camera_fields.CameraAVSMFeatureMapAttr.cluster, camera_fields.CameraAVSMFeatureMapAttr.ID)) + subscribe_request_no_user_defined_zone:merge(cluster_base.subscribe(mock_device_no_user_defined_zone, nil, camera_fields.ZoneManagementFeatureMapAttr.cluster, camera_fields.ZoneManagementFeatureMapAttr.ID)) + for i, attr in ipairs(subscribed_attributes_no_user_defined_zone) do + if i > 1 then subscribe_request_no_user_defined_zone:merge(attr:subscribe(mock_device_no_user_defined_zone)) end + end + test.socket.matter:__expect_send({mock_device_no_user_defined_zone.id, subscribe_request_no_user_defined_zone}) + test.socket.device_lifecycle:__queue_receive({ mock_device_no_user_defined_zone.id, "doConfigure" }) + mock_device_no_user_defined_zone:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + local additional_subscribed_attributes = { clusters.CameraAvStreamManagement.attributes.HDRModeEnabled, clusters.CameraAvStreamManagement.attributes.ImageRotation, @@ -485,6 +546,69 @@ local function update_device_profile_no_per_zone_sensitivity() test.socket.matter:__expect_send({mock_device_no_per_zone_sensitivity.id, subscribe_request_no_per_zone_sensitivity}) end +local additional_subscribed_attributes_no_user_defined_zone = { + clusters.CameraAvStreamManagement.attributes.StatusLightBrightness, + clusters.CameraAvStreamManagement.attributes.StatusLightEnabled, + clusters.CameraAvStreamManagement.attributes.RateDistortionTradeOffPoints, + clusters.CameraAvStreamManagement.attributes.MaxEncodedPixelRate, + clusters.CameraAvStreamManagement.attributes.VideoSensorParams, + clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams, + clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams, + clusters.CameraAvStreamManagement.attributes.MinViewportResolution, + clusters.CameraAvStreamManagement.attributes.Viewport, + clusters.CameraAvStreamManagement.attributes.AttributeList, + clusters.OnOff.attributes.OnOff, +} + +local function update_device_profile_no_user_defined_zone() + local expected_metadata = { + optional_component_capabilities = { + { + "main", + { + "videoCapture2", + "cameraViewportSettings", + "videoStreamSettings", + } + }, + { + "statusLed", + { + "switch", + "mode" + } + } + }, + profile = "camera" + } + + test.socket.matter:__queue_receive({ + mock_device_no_user_defined_zone.id, + clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device_no_user_defined_zone, CAMERA_EP, { + uint32(clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID), + uint32(clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) + }) + }) + mock_device_no_user_defined_zone:expect_metadata_update(expected_metadata) + 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_no_user_defined_zone:generate_info_changed({ profile = updated_device_profile })) + + test.socket.capability:__expect_send( + mock_device_no_user_defined_zone:generate_test_message("main", capabilities.videoStreamSettings.supportedFeatures( + {"liveStreaming", "clipRecording", "perStreamViewports"} + )) + ) + + for _, attr in ipairs(additional_subscribed_attributes_no_user_defined_zone) do + subscribe_request_no_user_defined_zone:merge(attr:subscribe(mock_device_no_user_defined_zone)) + end + test.socket.matter:__expect_send({mock_device_no_user_defined_zone.id, subscribe_request_no_user_defined_zone}) +end + -- Matter Handler UTs test.register_coroutine_test( @@ -3185,49 +3309,61 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "Zone Management trigger reports should omit sensitivity when per-zone sensitivity is unsupported, even if provided by client", + "Camera profile should include zoneManagement when USER_DEFINED feature is present", function() update_device_profile_no_per_zone_sensitivity() test.wait_for_events() - -- Create a trigger with + end, + { + test_init = test_init_no_per_zone_sensitivity, + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Camera profile should not include zoneManagement when USER_DEFINED feature is missing", + function() + update_device_profile_no_user_defined_zone() + test.wait_for_events() + end, + { + test_init = test_init_no_user_defined_zone, + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Zone Management trigger command should be ignored when zoneManagement capability is not exposed", + function() + update_device_profile_no_user_defined_zone() + test.wait_for_events() test.socket.capability:__queue_receive({ - mock_device_no_per_zone_sensitivity.id, + mock_device_no_user_defined_zone.id, { capability = "zoneManagement", component = "main", command = "createOrUpdateTrigger", args = { 1, 10, 3, 15, 3, 5 }} }) - test.socket.matter:__expect_send({ - mock_device_no_per_zone_sensitivity.id, clusters.ZoneManagement.server.commands.CreateOrUpdateTrigger(mock_device_no_per_zone_sensitivity, CAMERA_EP, { - zone_id = 1, - initial_duration = 10, - augmentation_duration = 3, - max_duration = 15, - blind_duration = 3 - }) - }) + test.wait_for_events() end, { - test_init = test_init_no_per_zone_sensitivity, + test_init = test_init_no_user_defined_zone, min_api_version = 17 } ) test.register_coroutine_test( - "Zone Management setSensitivity command should handle global sensitivity when per-zone sensitivity is unsupported", + "Zone Management setSensitivity command should be ignored when zoneManagement capability is not exposed", function() - update_device_profile_no_per_zone_sensitivity() + update_device_profile_no_user_defined_zone() test.wait_for_events() test.socket.capability:__queue_receive({ - mock_device_no_per_zone_sensitivity.id, + mock_device_no_user_defined_zone.id, { capability = "zoneManagement", component = "main", command = "setSensitivity", args = { 5 } } }) - test.socket.matter:__expect_send({ - mock_device_no_per_zone_sensitivity.id, - clusters.ZoneManagement.attributes.Sensitivity:write(mock_device_no_per_zone_sensitivity, CAMERA_EP, 5) - }) + test.wait_for_events() end, { - test_init = test_init_no_per_zone_sensitivity, + test_init = test_init_no_user_defined_zone, min_api_version = 17 } ) From e566c1bcf08b71af02bdad529c23da7795e5300b Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 20 Jul 2026 16:00:55 -0500 Subject: [PATCH 472/538] Added verification to min/max-heat/coolSetpointLimit tests --- .../src/test/test_zenwithin_thermostat.lua | 68 ++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua index 721853579a..c16757366d 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua @@ -523,57 +523,61 @@ test.register_coroutine_test( } ) -test.register_message_test( +test.register_coroutine_test( "MinHeatSetpointLimit report stores device field", - { - { - channel = "zigbee", - direction = "receive", - message = { mock_device.id, Thermostat.attributes.MinHeatSetpointLimit:build_test_attr_report(mock_device, 500) } - } - }, + function () + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.MinHeatSetpointLimit:build_test_attr_report(mock_device, 500) + }) + test.wait_for_events() + assert(mock_device:get_field("minHeatSetpoint") == 500, "minHeatSetpoint field not stored correctly") + end, { min_api_version = 17 } ) -test.register_message_test( +test.register_coroutine_test( "MaxHeatSetpointLimit report stores device field", - { - { - channel = "zigbee", - direction = "receive", - message = { mock_device.id, Thermostat.attributes.MaxHeatSetpointLimit:build_test_attr_report(mock_device, 3500) } - } - }, + function () + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.MaxHeatSetpointLimit:build_test_attr_report(mock_device, 3500) + }) + test.wait_for_events() + assert(mock_device:get_field("maxHeatSetpoint") == 3500, "maxHeatSetpoint field not stored correctly") + end, { min_api_version = 17 } ) -test.register_message_test( +test.register_coroutine_test( "MinCoolSetpointLimit report stores device field", - { - { - channel = "zigbee", - direction = "receive", - message = { mock_device.id, Thermostat.attributes.MinCoolSetpointLimit:build_test_attr_report(mock_device, 1600) } - } - }, + function () + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.MinCoolSetpointLimit:build_test_attr_report(mock_device, 1600) + }) + test.wait_for_events() + assert(mock_device:get_field("minCoolSetpoint") == 1600, "minCoolSetpoint field not stored correctly") + end, { min_api_version = 17 } ) -test.register_message_test( +test.register_coroutine_test( "MaxCoolSetpointLimit report stores device field", - { - { - channel = "zigbee", - direction = "receive", - message = { mock_device.id, Thermostat.attributes.MaxCoolSetpointLimit:build_test_attr_report(mock_device, 3200) } - } - }, + function () + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.MaxCoolSetpointLimit:build_test_attr_report(mock_device, 3200) + }) + test.wait_for_events() + assert(mock_device:get_field("maxCoolSetpoint") == 3200, "maxCoolSetpoint field not stored correctly") + end, { min_api_version = 17 } From 60a0ce7d02e457c91fff277c592e3f4ff3444a2d Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 21 Jul 2026 12:32:52 -0500 Subject: [PATCH 473/538] WWSTCERT-12695 Smart WiFi Outdoor Plug (#3123) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index d6a367c685..6a4321c1a5 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -4199,6 +4199,11 @@ matterManufacturer: vendorId: 0x1392 productId: 0x0106 deviceProfileName: plug-power-energy-powerConsumption + - id: "5010/276" + deviceLabel: Smart WiFi Outdoor Plug + vendorId: 0x1392 + productId: 0x0114 + deviceProfileName: plug-energy-powerConsumption #GIANT LIGHTING - id: "5707/1" deviceLabel: Floor Lamp From 1d58e176f7185a4e5845cc2362b7cf1cada786c9 Mon Sep 17 00:00:00 2001 From: zs Date: Thu, 9 Jul 2026 18:59:59 +0800 Subject: [PATCH 474/538] Add step function for Tuya Zigbee curtains --- .../profiles/window-treatment-reverse.yml | 2 + .../tuya-zigbee/src/curtain/init.lua | 41 +++++ .../src/test/test_tuya_curtain.lua | 166 +++++++++++++++++- 3 files changed, 208 insertions(+), 1 deletion(-) diff --git a/drivers/Unofficial/tuya-zigbee/profiles/window-treatment-reverse.yml b/drivers/Unofficial/tuya-zigbee/profiles/window-treatment-reverse.yml index 9712eb6849..d0f47cdafd 100644 --- a/drivers/Unofficial/tuya-zigbee/profiles/window-treatment-reverse.yml +++ b/drivers/Unofficial/tuya-zigbee/profiles/window-treatment-reverse.yml @@ -6,6 +6,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: windowShadePreset version: 1 - id: firmwareUpdate diff --git a/drivers/Unofficial/tuya-zigbee/src/curtain/init.lua b/drivers/Unofficial/tuya-zigbee/src/curtain/init.lua index 03c34cac65..284486e88b 100644 --- a/drivers/Unofficial/tuya-zigbee/src/curtain/init.lua +++ b/drivers/Unofficial/tuya-zigbee/src/curtain/init.lua @@ -23,6 +23,14 @@ local packet_id = 0 local PRESET_LEVEL = 50 local PRESET_LEVEL_KEY = "_presetLevel" +-- When the curtain is moving, LATEST_TARGET_LEVEL is used to store the latest target position value, +-- which will be cleared after 3 seconds timeout +local LATEST_TARGET_LEVEL = "_latestTargetLevel" +-- Field name for the timer that clears the target position after timeout +local TARGET_LEVEL_TIME_OUT = "_targetLevelTimeOut" +-- Timeout for the stateless step to accumulate +local TARGET_LEVEL_TIME_OUT_SECONDS = 15 + local FINGERPRINTS = { { mfr = "_TZE284_nladmfvf", model = "TS0601"} } @@ -153,6 +161,36 @@ local function tuya_cluster_handler(driver, device, zb_rx) end end +local function handle_step_shade_level(driver, device, command) + local step = command.args.stepSize or command.args[1] or (command.positional_args and command.positional_args.stepSize) + + -- When LATEST_TARGET_LEVEL is empty, it means the curtain motor is not moving, + -- and capabilities.windowShadeLevel.shadeLevel is used as the reference for position offset calculation; + -- when LATEST_TARGET_LEVEL is not empty, it means the curtain motor is moving, + -- and LATEST_TARGET_LEVEL is used as the reference for position offset calculation to obtain a more accurate position calculation. + local latest_target_level = device:get_field(LATEST_TARGET_LEVEL) + local current_level = latest_target_level or + device:get_latest_state("main", capabilities.windowShadeLevel.ID, + capabilities.windowShadeLevel.shadeLevel.NAME) or 0 + + local target_level = utils.clamp_value(current_level + step, 0, 100) + target_level = utils.round(target_level) + -- Cancel any previous timer and set a 15 second timeout timer to ensure target_level is cleared + -- This is to prevent the stateless step from accumulating indefinitely, while ensuring that a single + -- "scroll" action can accumulate multiple steps within a short time frame + if device:get_field(TARGET_LEVEL_TIME_OUT) then + device.thread:cancel_timer(device:get_field(TARGET_LEVEL_TIME_OUT)) + end + local timer = device.thread:call_with_delay(TARGET_LEVEL_TIME_OUT_SECONDS, function(d) + device:set_field(LATEST_TARGET_LEVEL, nil) + end) + device:set_field(TARGET_LEVEL_TIME_OUT, timer) + device:set_field(LATEST_TARGET_LEVEL, target_level) + + local new_command = { args = { shadeLevel = target_level }, component = command.component } + window_shade_level(driver, device, new_command) +end + local tuya_curtain_driver = { NAME = "tuya curtain", lifecycle_handlers = { @@ -170,6 +208,9 @@ local tuya_curtain_driver = { [capabilities.windowShadeLevel.ID] = { [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = window_shade_level }, + [capabilities.statelessWindowShadeLevelStep.ID] = { + [capabilities.statelessWindowShadeLevelStep.commands.stepShadeLevel.NAME] = handle_step_shade_level + }, [capabilities.windowShadePreset.ID] = { [capabilities.windowShadePreset.commands.presetPosition.NAME] = window_shade_preset, [capabilities.windowShadePreset.commands.setPresetPosition.NAME] = set_preset_position_cmd 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 ca09608347..1cf3335d9e 100644 --- a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_curtain.lua +++ b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_curtain.lua @@ -244,4 +244,168 @@ test.register_message_test( } } ) -test.run_registered_tests() + +test.register_message_test( + "Handle Window Shade step level command - step up", + { + { + channel = "capability", + direction = "receive", + message = { mock_simple_device.id, { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 }, stepSize = 10 } } + }, + { + channel = "zigbee", + direction = "send", + -- For _TZE284_nladmfvf, level is inverted: 100 - 10 = 90 (0x5A) + message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x5a', 0) } + } + } +) + +test.register_message_test( + "Handle Window Shade step level command - step down", + { + { + channel = "capability", + direction = "receive", + message = { mock_simple_device.id, { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -10 }, stepSize = -10 } } + }, + { + channel = "zigbee", + direction = "send", + -- For _TZE284_nladmfvf, level is inverted: 100 - 0 = 100 (0x64), but clamped to 0 so 100 - 0 = 100 + message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x64', 0) } + } + } +) + +test.register_coroutine_test( + "Handle Window Shade step level command - step up from current level", + function() + -- First, simulate a current shade level of 40 + test.socket.zigbee:__queue_receive({ + mock_simple_device.id, + tuya_utils.build_test_attr_report(mock_simple_device, '\x03', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x28', 0x01) + }) + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(40)) + ) + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.windowShade.windowShade("partially open")) + ) + test.wait_for_events() + + -- Then send step command with stepSize 20, should result in level 60 + -- For _TZE284_nladmfvf, level is inverted: 100 - 60 = 40 (0x28) + test.socket.capability:__queue_receive({ + mock_simple_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 20 }, stepSize = 20 } + }) + test.socket.zigbee:__expect_send({ + mock_simple_device.id, + tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x28', 0) + }) + end +) + +test.register_coroutine_test( + "Handle Window Shade step level command - step up clamped to 100", + function() + -- First, simulate a current shade level of 90 + test.socket.zigbee:__queue_receive({ + mock_simple_device.id, + tuya_utils.build_test_attr_report(mock_simple_device, '\x03', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x5a', 0x01) + }) + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(90)) + ) + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.windowShade.windowShade("partially open")) + ) + test.wait_for_events() + + -- Then send step command with stepSize 20, should be clamped to 100 + -- For _TZE284_nladmfvf, level is inverted: 100 - 100 = 0 (0x00) + test.socket.capability:__queue_receive({ + mock_simple_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 20 }, stepSize = 20 } + }) + test.socket.zigbee:__expect_send({ + mock_simple_device.id, + tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x00', 0) + }) + end +) + +test.register_coroutine_test( + "Handle Window Shade step level command - step down clamped to 0", + function() + -- First, simulate a current shade level of 10 + test.socket.zigbee:__queue_receive({ + mock_simple_device.id, + tuya_utils.build_test_attr_report(mock_simple_device, '\x03', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x0a', 0x01) + }) + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(10)) + ) + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.windowShade.windowShade("partially open")) + ) + test.wait_for_events() + + -- Then send step command with stepSize -20, should be clamped to 0 + -- For _TZE284_nladmfvf, level is inverted: 100 - 0 = 100 (0x64) + test.socket.capability:__queue_receive({ + mock_simple_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -20 }, stepSize = -20 } + }) + test.socket.zigbee:__expect_send({ + mock_simple_device.id, + tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x64', 0) + }) + end +) + +test.register_coroutine_test( + "Handle Window Shade step level command - uses latest_target_level when available", + function() + -- First, simulate a current shade level of 40 + test.socket.zigbee:__queue_receive({ + mock_simple_device.id, + tuya_utils.build_test_attr_report(mock_simple_device, '\x03', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x28', 0x01) + }) + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(40)) + ) + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.windowShade.windowShade("partially open")) + ) + test.wait_for_events() + + -- Send first step command to set latest_target_level to 50 + -- For _TZE284_nladmfvf, level is inverted: 100 - 50 = 50 (0x32) + test.socket.capability:__queue_receive({ + mock_simple_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 }, stepSize = 10 } + }) + test.socket.zigbee:__expect_send({ + mock_simple_device.id, + tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x32', 0) + }) + test.wait_for_events() + + -- Send second step command before timeout - should use latest_target_level (50) as base + -- Target = 50 + 10 = 60, For _TZE284_nladmfvf: 100 - 60 = 40 (0x28) + -- Packet ID is 1 because it was incremented after the first step command + test.socket.capability:__queue_receive({ + mock_simple_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 }, stepSize = 10 } + }) + test.socket.zigbee:__expect_send({ + mock_simple_device.id, + tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x28', 1) + }) + end +) + +test.run_registered_tests() \ No newline at end of file From 0b239eb6aad13a83146cc90ace1f10ba333e7309 Mon Sep 17 00:00:00 2001 From: Hong Ye/MDE Lab /SRC-Nanjing/Staff Engineer/Samsung Electronics Date: Thu, 25 Jun 2026 20:46:45 +0800 Subject: [PATCH 475/538] Update matter blind edge driver Signed-off-by: Hong Ye/MDE Lab /SRC-Nanjing/Staff Engineer/Samsung Electronics --- .../profiles/window-covering-battery.yml | 2 + .../profiles/window-covering-batteryLevel.yml | 2 + .../profiles/window-covering-profile.yml | 2 + .../profiles/window-covering-tilt-battery.yml | 2 + .../profiles/window-covering-tilt.yml | 2 + .../profiles/window-covering.yml | 2 + .../matter-window-covering/src/init.lua | 70 +++++- .../src/test/test_matter_window_covering.lua | 228 ++++++++++++++++++ 8 files changed, 307 insertions(+), 3 deletions(-) diff --git a/drivers/SmartThings/matter-window-covering/profiles/window-covering-battery.yml b/drivers/SmartThings/matter-window-covering/profiles/window-covering-battery.yml index a1896ca634..ad2dc6f0ac 100644 --- a/drivers/SmartThings/matter-window-covering/profiles/window-covering-battery.yml +++ b/drivers/SmartThings/matter-window-covering/profiles/window-covering-battery.yml @@ -8,6 +8,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: battery version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-window-covering/profiles/window-covering-batteryLevel.yml b/drivers/SmartThings/matter-window-covering/profiles/window-covering-batteryLevel.yml index fa46d87cc6..c541d47ae8 100644 --- a/drivers/SmartThings/matter-window-covering/profiles/window-covering-batteryLevel.yml +++ b/drivers/SmartThings/matter-window-covering/profiles/window-covering-batteryLevel.yml @@ -8,6 +8,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: batteryLevel version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-window-covering/profiles/window-covering-profile.yml b/drivers/SmartThings/matter-window-covering/profiles/window-covering-profile.yml index 903565e68c..94e98998b3 100644 --- a/drivers/SmartThings/matter-window-covering/profiles/window-covering-profile.yml +++ b/drivers/SmartThings/matter-window-covering/profiles/window-covering-profile.yml @@ -9,6 +9,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: battery version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-window-covering/profiles/window-covering-tilt-battery.yml b/drivers/SmartThings/matter-window-covering/profiles/window-covering-tilt-battery.yml index 27f7f1f61a..bca45d37af 100644 --- a/drivers/SmartThings/matter-window-covering/profiles/window-covering-tilt-battery.yml +++ b/drivers/SmartThings/matter-window-covering/profiles/window-covering-tilt-battery.yml @@ -8,6 +8,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: windowShadeTiltLevel version: 1 - id: battery diff --git a/drivers/SmartThings/matter-window-covering/profiles/window-covering-tilt.yml b/drivers/SmartThings/matter-window-covering/profiles/window-covering-tilt.yml index c6a759b610..99a579e2c6 100644 --- a/drivers/SmartThings/matter-window-covering/profiles/window-covering-tilt.yml +++ b/drivers/SmartThings/matter-window-covering/profiles/window-covering-tilt.yml @@ -8,6 +8,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: windowShadeTiltLevel version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-window-covering/profiles/window-covering.yml b/drivers/SmartThings/matter-window-covering/profiles/window-covering.yml index fbd40ed08d..e90a3fdb23 100644 --- a/drivers/SmartThings/matter-window-covering/profiles/window-covering.yml +++ b/drivers/SmartThings/matter-window-covering/profiles/window-covering.yml @@ -8,6 +8,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-window-covering/src/init.lua b/drivers/SmartThings/matter-window-covering/src/init.lua index 66581d0ae8..bfde1e3fc8 100644 --- a/drivers/SmartThings/matter-window-covering/src/init.lua +++ b/drivers/SmartThings/matter-window-covering/src/init.lua @@ -9,6 +9,7 @@ local im = require "st.matter.interaction_model" local log = require "log" local clusters = require "st.matter.clusters" local MatterDriver = require "st.matter.driver" +local utils = require "st.utils" local CURRENT_LIFT = "__current_lift" local CURRENT_TILT = "__current_tilt" @@ -20,6 +21,11 @@ local battery_support = { local REVERSE_POLARITY = "__reverse_polarity" local PRESET_LEVEL_KEY = "__preset_level_key" local DEFAULT_PRESET_LEVEL = 50 +local LATEST_TARGET_LEVEL = "_latest_target_level" +local TARGET_LEVEL_TIME_OUT = "_target_level_timeout" +local TARGET_LEVEL_TIME_OUT_SECONDS = 15 + +local TARGET_REACH_TOLERANCE = 1 local function find_default_endpoint(device, cluster) local res = device.MATTER_DEFAULT_ENDPOINT @@ -175,15 +181,44 @@ local function handle_shade_level(driver, device, cmd) device:send(req) end +local function handle_step_shade_level(driver, device, cmd) + local step = cmd.args.stepSize + + local current_level = device:get_field(LATEST_TARGET_LEVEL) or + device:get_latest_state("main", capabilities.windowShadeLevel.ID, + capabilities.windowShadeLevel.shadeLevel.NAME) or 0 + + local target_level = utils.clamp_value(current_level + step, 0, 100) + + device:set_field(LATEST_TARGET_LEVEL, target_level) + + local old_timer = device:get_field(TARGET_LEVEL_TIME_OUT) + if old_timer ~= nil then + device.thread:cancel_timer(old_timer) + end + + -- Set 15 second timeout timer to ensure target_level is cleared + local timer = device.thread:call_with_delay(TARGET_LEVEL_TIME_OUT_SECONDS, function(d) + device:set_field(LATEST_TARGET_LEVEL, nil) + end) + device:set_field(TARGET_LEVEL_TIME_OUT, timer) + + driver:inject_capability_command(device, { + capability = capabilities.windowShadeLevel.ID, + component = cmd.component, + command = capabilities.windowShadeLevel.commands.setShadeLevel.NAME, + named_args = { shadeLevel = target_level } + }) +end + -- move to shade tilt level between 0-100 local function handle_shade_tilt_level(driver, device, cmd) local endpoint_id = device:component_to_endpoint(cmd.component) local tilt_percentage_value = 100 - cmd.args.level local hundredths_tilt_percentage = tilt_percentage_value * 100 - local req = clusters.WindowCovering.server.commands.GoToTiltPercentage( + device:send(clusters.WindowCovering.server.commands.GoToTiltPercentage( device, endpoint_id, hundredths_tilt_percentage - ) - device:send(req) + )) end -- current lift/tilt percentage, changed to 100ths percent @@ -194,6 +229,22 @@ local current_pos_handler = function(attribute) end local windowShade = capabilities.windowShade.windowShade local position = 100 - math.floor(ib.data.value / 100) + + -- Step control logic + local target_level_field = device:get_field(LATEST_TARGET_LEVEL) + if target_level_field and attribute == capabilities.windowShadeLevel.shadeLevel then + -- Allow ±1 degree tolerance for reaching target + if math.abs(position - target_level_field) <= TARGET_REACH_TOLERANCE then + -- Device reached target position, clear target marker and timeout timer + device:set_field(LATEST_TARGET_LEVEL, nil) + local timer = device:get_field(TARGET_LEVEL_TIME_OUT) + if timer ~= nil then + device.thread:cancel_timer(timer) + device:set_field(TARGET_LEVEL_TIME_OUT, nil) + end + end + end + local reverse = device:get_field(REVERSE_POLARITY) device:emit_event_for_endpoint(ib.endpoint_id, attribute(position)) @@ -253,6 +304,16 @@ local function current_status_handler(driver, device, ib, response) device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closing() or windowShade.opening()) elseif state == 2 then -- closing device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.opening() or windowShade.closing()) + elseif state == 0 then -- stopped (idle) + -- Clear target_level when device stops moving + local target_level_field = device:get_field(LATEST_TARGET_LEVEL) + if target_level_field then + device:set_field(LATEST_TARGET_LEVEL, nil) + local timer = device:get_field(TARGET_LEVEL_TIME_OUT) + if timer ~= nil then + device.thread:cancel_timer(timer) + end + end elseif state ~= 0 then -- unknown device:emit_event_for_endpoint(ib.endpoint_id, windowShade.unknown()) end @@ -354,6 +415,9 @@ local matter_driver_template = { [capabilities.windowShadeLevel.ID] = { [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = handle_shade_level, }, + [capabilities.statelessWindowShadeLevelStep.ID] = { + [capabilities.statelessWindowShadeLevelStep.commands.stepShadeLevel.NAME] = handle_step_shade_level + }, [capabilities.windowShadeTiltLevel.ID] = { [capabilities.windowShadeTiltLevel.commands.setShadeTiltLevel.NAME] = handle_shade_tilt_level, }, 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 9f273037f3..ae91d7c9cd 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 @@ -1222,4 +1222,232 @@ test.register_coroutine_test( } ) +-- stepShadeLevel tests + +test.register_coroutine_test( + "WindowShade stepShadeLevel cmd handler - step up", function() + test.socket.capability:__queue_receive( + { + mock_device.id, + {capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 }}, + } + ) + test.socket.matter:__expect_send( + {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 9000)} + ) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "WindowShade stepShadeLevel cmd handler - step down", function() + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 5000 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeLevel.shadeLevel(50) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShade.windowShade.partially_open() + ) + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + {capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -20 }}, + } + ) + test.socket.matter:__expect_send( + {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 7000)} + ) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "WindowShade stepShadeLevel cmd handler - continuous step with target tracking", function() + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 7000 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeLevel.shadeLevel(30) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShade.windowShade.partially_open() + ) + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + {capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 }}, + } + ) + test.socket.matter:__expect_send( + {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 6000)} + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + {capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 }}, + } + ) + test.socket.matter:__expect_send( + {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 5000)} + ) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "WindowShade stepShadeLevel - target reached clears target marker", function() + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 5000 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeLevel.shadeLevel(50) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShade.windowShade.partially_open() + ) + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + {capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 }}, + } + ) + test.socket.matter:__expect_send( + {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 4000)} + ) + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 4000 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeLevel.shadeLevel(60) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShade.windowShade.partially_open() + ) + ) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "WindowShade stepShadeLevel - step up to maximum (100)", function() + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 500 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeLevel.shadeLevel(95) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShade.windowShade.partially_open() + ) + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + {capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 }}, + } + ) + test.socket.matter:__expect_send( + {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 0)} + ) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "WindowShade stepShadeLevel - step down to minimum (0)", function() + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 9500 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeLevel.shadeLevel(5) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShade.windowShade.partially_open() + ) + ) + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + {capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -10 }}, + } + ) + test.socket.matter:__expect_send( + {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 10000)} + ) + end, + { + min_api_version = 19 + } +) + test.run_registered_tests() From 25c8bb36f574e3e5a9e6aff6939a9318882cf254 Mon Sep 17 00:00:00 2001 From: zhangshen001 Date: Wed, 22 Jul 2026 23:51:42 +0800 Subject: [PATCH 476/538] Zigbee Window Treatment: Add step shade level functionality to blinds (#3108) * Add step shade level functionality to Zigbee curtains using Common Handler. --------- Co-authored-by: Harrison Carter --- ...ndow-treatment-aqara-curtain-driver-e1.yml | 2 + ...ow-treatment-aqara-roller-shade-rotate.yml | 2 + .../profiles/window-treatment-aqara.yml | 2 + .../profiles/window-treatment-battery.yml | 2 + .../profiles/window-treatment-no-preset.yml | 2 + .../profiles/window-treatment-powerSource.yml | 2 + ...w-treatment-profile-no-firmware-update.yml | 2 + .../profiles/window-treatment-profile.yml | 2 + .../profiles/window-treatment-reverse.yml | 2 + .../zigbee-window-treatment/src/init.lua | 6 +- .../src/test/test_zigbee_window_treatment.lua | 255 +++++++++++ ..._zigbee_window_treatment_inverted_step.lua | 346 +++++++++++++++ ...ee_window_treatment_level_cluster_step.lua | 400 ++++++++++++++++++ .../src/window_shade_utils.lua | 40 ++ 14 files changed, 1064 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_inverted_step.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_level_cluster_step.lua diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-curtain-driver-e1.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-curtain-driver-e1.yml index dbd219e523..05e3fe0085 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-curtain-driver-e1.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-curtain-driver-e1.yml @@ -6,6 +6,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: stse.initializedStateWithGuide version: 1 - id: stse.hookLockState diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-roller-shade-rotate.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-roller-shade-rotate.yml index c454c4d590..e7f5e8d8af 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-roller-shade-rotate.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-roller-shade-rotate.yml @@ -6,6 +6,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: stse.initializedStateWithGuide version: 1 - id: stse.shadeRotateState diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara.yml index 6c2cc967e7..4dc959ef93 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara.yml @@ -6,6 +6,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: stse.deviceInitialization version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-battery.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-battery.yml index be0ae73d76..e9b6178ff8 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-battery.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-battery.yml @@ -8,6 +8,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: battery version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-no-preset.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-no-preset.yml index 7a65434c46..67ba3c186d 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-no-preset.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-no-preset.yml @@ -8,6 +8,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-powerSource.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-powerSource.yml index 7323ee0218..d72dc33308 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-powerSource.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-powerSource.yml @@ -6,6 +6,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: windowShadePreset version: 1 - id: powerSource diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile-no-firmware-update.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile-no-firmware-update.yml index 8e641e6b42..dfeeed7aa1 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile-no-firmware-update.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile-no-firmware-update.yml @@ -8,6 +8,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: refresh version: 1 categories: diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile.yml index b10a5ea101..c9fce872c0 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile.yml @@ -8,6 +8,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-reverse.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-reverse.yml index 0eb666a5d2..54b4312210 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-reverse.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-reverse.yml @@ -6,6 +6,8 @@ components: version: 1 - id: windowShadeLevel version: 1 + - id: statelessWindowShadeLevelStep + version: 1 - id: windowShadePreset version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zigbee-window-treatment/src/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/init.lua index 0a093645f8..b58c20a5be 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/init.lua @@ -33,6 +33,7 @@ local zigbee_window_treatment_driver_template = { capabilities.windowShade, capabilities.windowShadePreset, capabilities.windowShadeLevel, + capabilities.statelessWindowShadeLevelStep, capabilities.powerSource, capabilities.battery }, @@ -40,7 +41,10 @@ local zigbee_window_treatment_driver_template = { [capabilities.windowShadePreset.ID] = { [capabilities.windowShadePreset.commands.setPresetPosition.NAME] = window_shade_utils.set_preset_position_cmd, [capabilities.windowShadePreset.commands.presetPosition.NAME] = window_shade_utils.window_shade_preset_cmd, - } + }, + [capabilities.statelessWindowShadeLevelStep.ID] = { + [capabilities.statelessWindowShadeLevelStep.commands.stepShadeLevel.NAME] = window_shade_utils.step_shade_level_handler + }, }, lifecycle_handlers = { init = init_handler, diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua index ed01a87249..145876ff64 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua @@ -337,4 +337,259 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "statelessWindowShadeLevelStep stepShadeLevel - positive step", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 50) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + mock_device:set_field("_latestTargetLevel", 50) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 } } + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 60) + }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "statelessWindowShadeLevelStep stepShadeLevel - negative step", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 30) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 30 } } + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + mock_device:set_field("_latestTargetLevel", 30) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -20 } } + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10) + }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "statelessWindowShadeLevelStep stepShadeLevel - accumulated steps with mixed directions", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.zigbee:__queue_receive({ + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 50) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + mock_device:set_field("_latestTargetLevel", 50) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 60) + }) + test.wait_for_events() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -5 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 55) + }) + test.wait_for_events() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 15 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 70) + }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "statelessWindowShadeLevelStep stepShadeLevel - clamped to 100", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.zigbee:__queue_receive({ + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 90) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + mock_device:set_field("_latestTargetLevel", 90) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 50 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 100) + }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "statelessWindowShadeLevelStep stepShadeLevel - clamped to 0", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.zigbee:__queue_receive({ + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 10 } } + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + mock_device:set_field("_latestTargetLevel", 10) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -50 } } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 0) + }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "statelessWindowShadeLevelStep stepShadeLevel - zero stepSize", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 50) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + mock_device:set_field("_latestTargetLevel", 50) + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 0 } } + }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + +-- Test step_shade_level_handler with zero step +test.register_coroutine_test( + "step_shade_level_handler returns early when stepSize is zero", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 50) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + mock_device:set_field("_latestTargetLevel", 50) + + -- Send zero step - should return early without sending any command + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 0 } } + }) + test.wait_for_events() + end, + { + min_api_version = 17 + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_inverted_step.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_inverted_step.lua new file mode 100644 index 0000000000..3c7c0cbee2 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_inverted_step.lua @@ -0,0 +1,346 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Test for statelessWindowShadeLevelStep capability with Somfy (reverse) devices +-- Tests devices like Somfy Glydea that use inverted lift percentage + +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local t_utils = require "integration_test.utils" +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local WindowCovering = clusters.WindowCovering + +-- Create mock Somfy Glydea device using WindowCovering cluster with inverted logic +-- This device matches the somfy sub-driver which inverts the lift percentage +local somfy_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("window-treatment-profile.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "SOMFY", + model = "Glydea Ultra Curtain", + server_clusters = { WindowCovering.ID } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(somfy_device) + test.socket.capability:__expect_send( + somfy_device:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}})) + ) + test.socket.capability:__expect_send( + somfy_device:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}})) + ) +end + +test.set_test_init_function(test_init) + +-- Test 1: Boundary condition - stepSize = 0 should not send command +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Somfy (reverse) - stepSize 0 does not send command", + function() + -- Set initial state via WindowCovering cluster report: 50% (inverted: device reports 50 for 50% shade) + test.socket.zigbee:__queue_receive({ + somfy_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(somfy_device, 50) + }) + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + -- Somfy sub-driver emits "opening" for initial report when no previous state exists + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + somfy_device:set_field("_latestTargetLevel", 50) + + -- Send stepShadeLevel command with stepSize = 0 + test.socket.capability:__queue_receive({ + somfy_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 0 } } + }) + + -- Should NOT send any Zigbee command (stepSize 0 is ignored) + test.wait_for_events() + end, + { min_api_version = 17 } +) + +-- Test 2: Normal step up operation with Somfy (reverse) device +-- Somfy inverts the value: shadeLevel 60 means device goes to 100-60=40 +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Somfy (reverse) - normal step up", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + -- Set initial state: WindowCovering cluster reports 50 (inverted: 100-50=50 shade level) + test.socket.zigbee:__queue_receive({ + somfy_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(somfy_device, 50) + }) + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + -- Somfy sub-driver emits "opening" for initial report when no previous state exists + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + somfy_device:set_field("_latestTargetLevel", 50) + + -- Send stepShadeLevel command with stepSize = 10 + -- new_target_level = clamp(50 + 10, 0, 100) = 60 + test.socket.capability:__queue_receive({ + somfy_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 } } + }) + + -- Note: Somfy sub-driver's window_shade_level_cmd directly sends Zigbee command without emitting events + -- Somfy sub-driver's window_shade_level_cmd inverts the value: 100 - 60 = 40 + test.socket.zigbee:__expect_send({ + somfy_device.id, + WindowCovering.server.commands.GoToLiftPercentage(somfy_device, 40) + }) + end, + { min_api_version = 17 } +) + +-- Test 3: Normal step down operation with Somfy (reverse) device +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Somfy (reverse) - normal step down", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + -- Set initial state: WindowCovering cluster reports 25 (inverted: 100-25=75 shade level) + test.socket.zigbee:__queue_receive({ + somfy_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(somfy_device, 25) + }) + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 75 } } + }) + -- Somfy sub-driver emits "opening" for initial report when no previous state exists + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + somfy_device:set_field("_latestTargetLevel", 75) + + -- Send stepShadeLevel command with stepSize = -25 + -- new_target_level = clamp(75 + (-25), 0, 100) = 50 + test.socket.capability:__queue_receive({ + somfy_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -25 } } + }) + + -- Note: Somfy sub-driver's window_shade_level_cmd directly sends Zigbee command without emitting events + -- Somfy sub-driver's window_shade_level_cmd inverts the value: 100 - 50 = 50 + test.socket.zigbee:__expect_send({ + somfy_device.id, + WindowCovering.server.commands.GoToLiftPercentage(somfy_device, 50) + }) + end, + { min_api_version = 17 } +) + +-- Test 4: Boundary condition - value clamped to 100 (maximum) +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Somfy (reverse) - value clamped to 100", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + -- Set initial state: WindowCovering cluster reports 10 (inverted: 100-10=90 shade level) + test.socket.zigbee:__queue_receive({ + somfy_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(somfy_device, 10) + }) + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + -- Somfy sub-driver emits "opening" for initial report when no previous state exists + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + somfy_device:set_field("_latestTargetLevel", 90) + + -- Send stepShadeLevel command with stepSize = 50 + -- new_target_level = clamp(90 + 50, 0, 100) = 100 + test.socket.capability:__queue_receive({ + somfy_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 50 } } + }) + + -- Note: Somfy sub-driver's window_shade_level_cmd directly sends Zigbee command without emitting events + -- Somfy sub-driver's window_shade_level_cmd inverts the value: 100 - 100 = 0 + test.socket.zigbee:__expect_send({ + somfy_device.id, + WindowCovering.server.commands.GoToLiftPercentage(somfy_device, 0) + }) + end, + { min_api_version = 17 } +) + +-- Test 5: Boundary condition - value clamped to 0 (minimum) +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Somfy (reverse) - value clamped to 0", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + -- Set initial state: WindowCovering cluster reports 80 (inverted: 100-80=20 shade level) + test.socket.zigbee:__queue_receive({ + somfy_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(somfy_device, 80) + }) + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 20 } } + }) + -- Somfy sub-driver emits "opening" for initial report when no previous state exists + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + somfy_device:set_field("_latestTargetLevel", 20) + + -- Send stepShadeLevel command with stepSize = -50 + -- new_target_level = clamp(20 + (-50), 0, 100) = 0 + test.socket.capability:__queue_receive({ + somfy_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -50 } } + }) + + -- Note: Somfy sub-driver's window_shade_level_cmd directly sends Zigbee command without emitting events + -- Somfy sub-driver's window_shade_level_cmd inverts the value: 100 - 0 = 100 + test.socket.zigbee:__expect_send({ + somfy_device.id, + WindowCovering.server.commands.GoToLiftPercentage(somfy_device, 100) + }) + end, + { min_api_version = 17 } +) + +-- Test 6: Continuous step operations with mixed directions +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Somfy (reverse) - continuous steps with mixed directions", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + -- Set initial state: WindowCovering cluster reports 50 (inverted: 100-50=50 shade level) + test.socket.zigbee:__queue_receive({ + somfy_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(somfy_device, 50) + }) + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + -- Somfy sub-driver emits "opening" for initial report when no previous state exists + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + somfy_device:set_field("_latestTargetLevel", 50) + + -- Step 1: stepSize = 10 (up) -> 60% + test.socket.capability:__queue_receive({ + somfy_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 } } + }) + -- Note: Somfy sub-driver's window_shade_level_cmd directly sends Zigbee command without emitting events + -- Somfy sub-driver's window_shade_level_cmd inverts the value: 100 - 60 = 40 + test.socket.zigbee:__expect_send({ + somfy_device.id, + WindowCovering.server.commands.GoToLiftPercentage(somfy_device, 40) + }) + test.wait_for_events() + + -- Step 2: stepSize = -5 (down) -> 55% + test.socket.capability:__queue_receive({ + somfy_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -5 } } + }) + -- Note: Somfy sub-driver's window_shade_level_cmd directly sends Zigbee command without emitting events + -- Somfy sub-driver's window_shade_level_cmd inverts the value: 100 - 55 = 45 + test.socket.zigbee:__expect_send({ + somfy_device.id, + WindowCovering.server.commands.GoToLiftPercentage(somfy_device, 45) + }) + test.wait_for_events() + + -- Step 3: stepSize = 20 (up) -> 75% + test.socket.capability:__queue_receive({ + somfy_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 20 } } + }) + -- Note: Somfy sub-driver's window_shade_level_cmd directly sends Zigbee command without emitting events + -- Somfy sub-driver's window_shade_level_cmd inverts the value: 100 - 75 = 25 + test.socket.zigbee:__expect_send({ + somfy_device.id, + WindowCovering.server.commands.GoToLiftPercentage(somfy_device, 25) + }) + test.wait_for_events() + end, + { min_api_version = 17 } +) + +-- Test 7: nil stepSize should not send command +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Somfy (reverse) - nil stepSize does not send command", + function() + -- Set initial state: WindowCovering cluster reports 50 (inverted: 100-50=50 shade level) + test.socket.zigbee:__queue_receive({ + somfy_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(somfy_device, 50) + }) + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + -- Somfy sub-driver emits "opening" for initial report when no previous state exists + test.socket.capability:__expect_send({ + somfy_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + somfy_device:set_field("_latestTargetLevel", 50) + + -- Send stepShadeLevel command with nil stepSize + test.socket.capability:__queue_receive({ + somfy_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = {} } + }) + + -- Should NOT send any Zigbee command + test.wait_for_events() + end, + { min_api_version = 17 } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_level_cluster_step.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_level_cluster_step.lua new file mode 100644 index 0000000000..602aa34bd4 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_level_cluster_step.lua @@ -0,0 +1,400 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Test for statelessWindowShadeLevelStep capability with Level cluster devices +-- Tests devices like AXIS Gear that use Level cluster for window covering control + +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local t_utils = require "integration_test.utils" +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local Level = clusters.Level +local WindowCovering = clusters.WindowCovering + +-- Create mock AXIS Gear device using Level cluster +-- This device matches the axis sub-driver which uses Level cluster +local axis_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("window-treatment-profile.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "AXIS", + model = "Gear", + server_clusters = { Level.ID, WindowCovering.ID } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(axis_device) + test.socket.capability:__expect_send( + axis_device:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}})) + ) + test.socket.capability:__expect_send( + axis_device:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}})) + ) +end + +test.set_test_init_function(test_init) + +-- Test 1: Boundary condition - stepSize = 0 should not send command +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Level cluster - stepSize 0 does not send command", + function() + -- Set initial state via Level cluster report: 50% (127 out of 254) + test.socket.zigbee:__queue_receive({ + axis_device.id, + Level.attributes.CurrentLevel:build_test_attr_report(axis_device, 127) + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "partially open" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + axis_device:set_field("_latestTargetLevel", 50) + + -- Send stepShadeLevel command with stepSize = 0 + test.socket.capability:__queue_receive({ + axis_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 0 } } + }) + + -- Should NOT send any Zigbee command (stepSize 0 is ignored) + test.wait_for_events() + end, + { min_api_version = 17 } +) + +-- Test 2: Normal step up operation with Level cluster +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Level cluster - normal step up", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + -- Set initial state: Level cluster reports 50% (127 out of 254) + test.socket.zigbee:__queue_receive({ + axis_device.id, + Level.attributes.CurrentLevel:build_test_attr_report(axis_device, 127) + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "partially open" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + axis_device:set_field("_latestTargetLevel", 50) + + -- Send stepShadeLevel command with stepSize = 10 + -- new_target_level = clamp(50 + 10, 0, 100) = 60 + test.socket.capability:__queue_receive({ + axis_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 } } + }) + + -- AXIS sub-driver emits shadeLevel event when handling setShadeLevel (injected by main driver) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 60 } } + }) + -- AXIS sub-driver also emits windowShade event (opening because 60 > 50) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + + -- Should send MoveToLevelWithOnOff with level 60 (152 out of 254, rounded from 152.4) + test.socket.zigbee:__expect_send({ + axis_device.id, + Level.server.commands.MoveToLevelWithOnOff(axis_device, 152) + }) + end, + { min_api_version = 17 } +) + +-- Test 3: Normal step down operation with Level cluster +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Level cluster - normal step down", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + -- Set initial state: Level cluster reports 75% (191 out of 254) + test.socket.zigbee:__queue_receive({ + axis_device.id, + Level.attributes.CurrentLevel:build_test_attr_report(axis_device, 191) + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 75 } } + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "partially open" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + axis_device:set_field("_latestTargetLevel", 75) + + -- Send stepShadeLevel command with stepSize = -25 + -- new_target_level = clamp(75 + (-25), 0, 100) = 50 + test.socket.capability:__queue_receive({ + axis_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -25 } } + }) + + -- AXIS sub-driver emits shadeLevel event when handling setShadeLevel (injected by main driver) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + -- AXIS sub-driver also emits windowShade event (closing because 50 < 75) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "closing" } } + }) + + -- Should send MoveToLevelWithOnOff with level 50 (127 out of 254) + test.socket.zigbee:__expect_send({ + axis_device.id, + Level.server.commands.MoveToLevelWithOnOff(axis_device, 127) + }) + end, + { min_api_version = 17 } +) + +-- Test 4: Boundary condition - value clamped to 100 (maximum) +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Level cluster - value clamped to 100", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + -- Set initial state: Level cluster reports 90% (229 out of 254) + test.socket.zigbee:__queue_receive({ + axis_device.id, + Level.attributes.CurrentLevel:build_test_attr_report(axis_device, 229) + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "partially open" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + axis_device:set_field("_latestTargetLevel", 90) + + -- Send stepShadeLevel command with stepSize = 50 + -- new_target_level = clamp(90 + 50, 0, 100) = 100 + test.socket.capability:__queue_receive({ + axis_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 50 } } + }) + + -- AXIS sub-driver emits shadeLevel event when handling setShadeLevel (injected by main driver) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 100 } } + }) + -- AXIS sub-driver also emits windowShade event (opening because 100 > 90) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + + -- Should send MoveToLevelWithOnOff with level 100 (254 out of 254) + test.socket.zigbee:__expect_send({ + axis_device.id, + Level.server.commands.MoveToLevelWithOnOff(axis_device, 254) + }) + end, + { min_api_version = 17 } +) + +-- Test 5: Boundary condition - value clamped to 0 (minimum) +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Level cluster - value clamped to 0", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + -- Set initial state: Level cluster reports 20% (51 out of 254) + test.socket.zigbee:__queue_receive({ + axis_device.id, + Level.attributes.CurrentLevel:build_test_attr_report(axis_device, 51) + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 20 } } + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "partially open" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + axis_device:set_field("_latestTargetLevel", 20) + + -- Send stepShadeLevel command with stepSize = -50 + -- new_target_level = clamp(20 + (-50), 0, 100) = 0 + test.socket.capability:__queue_receive({ + axis_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -50 } } + }) + + -- AXIS sub-driver emits shadeLevel event when handling setShadeLevel (injected by main driver) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 0 } } + }) + -- AXIS sub-driver also emits windowShade event (closing because 0 < 20) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "closing" } } + }) + + -- Should send MoveToLevelWithOnOff with level 0 (0 out of 254) + test.socket.zigbee:__expect_send({ + axis_device.id, + Level.server.commands.MoveToLevelWithOnOff(axis_device, 0) + }) + end, + { min_api_version = 17 } +) + +-- Test 6: Continuous step operations with mixed directions +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Level cluster - continuous steps with mixed directions", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + -- Set initial state: Level cluster reports 50% (127 out of 254) + test.socket.zigbee:__queue_receive({ + axis_device.id, + Level.attributes.CurrentLevel:build_test_attr_report(axis_device, 127) + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "partially open" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + axis_device:set_field("_latestTargetLevel", 50) + + -- Step 1: stepSize = 10 (up) -> 60% + test.socket.capability:__queue_receive({ + axis_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 10 } } + }) + -- AXIS sub-driver emits shadeLevel and windowShade events + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 60 } } + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.socket.zigbee:__expect_send({ + axis_device.id, + Level.server.commands.MoveToLevelWithOnOff(axis_device, 152) + }) + test.wait_for_events() + + -- Step 2: stepSize = -5 (down) -> 55% + test.socket.capability:__queue_receive({ + axis_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { -5 } } + }) + -- AXIS sub-driver emits shadeLevel and windowShade events + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 55 } } + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "closing" } } + }) + test.socket.zigbee:__expect_send({ + axis_device.id, + Level.server.commands.MoveToLevelWithOnOff(axis_device, 140) + }) + test.wait_for_events() + + -- Step 3: stepSize = 20 (up) -> 75% + test.socket.capability:__queue_receive({ + axis_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = { 20 } } + }) + -- AXIS sub-driver emits shadeLevel and windowShade events + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 75 } } + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "opening" } } + }) + test.socket.zigbee:__expect_send({ + axis_device.id, + Level.server.commands.MoveToLevelWithOnOff(axis_device, 191) + }) + test.wait_for_events() + end, + { min_api_version = 17 } +) + +-- Test 7: nil stepSize should not send command +test.register_coroutine_test( + "statelessWindowShadeLevelStep with Level cluster - nil stepSize does not send command", + function() + -- Set initial state: Level cluster reports 50% (127 out of 254) + test.socket.zigbee:__queue_receive({ + axis_device.id, + Level.attributes.CurrentLevel:build_test_attr_report(axis_device, 127) + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 50 } } + }) + test.socket.capability:__expect_send({ + axis_device.id, + { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "partially open" } } + }) + test.wait_for_events() + + -- Manually set LATEST_TARGET_LEVEL to simulate the reported value + axis_device:set_field("_latestTargetLevel", 50) + + -- Send stepShadeLevel command with nil stepSize + test.socket.capability:__queue_receive({ + axis_device.id, + { capability = "statelessWindowShadeLevelStep", component = "main", command = "stepShadeLevel", args = {} } + }) + + -- Should NOT send any Zigbee command + test.wait_for_events() + end, + { min_api_version = 17 } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/window_shade_utils.lua b/drivers/SmartThings/zigbee-window-treatment/src/window_shade_utils.lua index f3e09c20a6..fd07648021 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/window_shade_utils.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/window_shade_utils.lua @@ -1,6 +1,7 @@ -- Copyright 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local st_utils = require "st.utils" local capabilities = require "st.capabilities" local zcl_clusters = require "st.zigbee.zcl.clusters" @@ -9,6 +10,15 @@ local utils = {} utils.PRESET_LEVEL = 50 utils.PRESET_LEVEL_KEY = "_presetLevel" +-- LATEST_TARGET_LEVEL stores the latest target position during stateless step operations. +-- It is cleared after TARGET_LEVEL_TIME_OUT_SECONDS timeout to allow accumulated steps +-- within a short time frame while preventing indefinite accumulation. +local LATEST_TARGET_LEVEL = "_latestTargetLevel" +-- Timeout in seconds for the stateless step accumulation window +local TARGET_LEVEL_TIME_OUT_SECONDS = 15 +-- Field name for the timer that clears LATEST_TARGET_LEVEL after timeout +local TARGET_LEVEL_TIME_OUT = "_targetLevelTimeOut" + utils.get_preset_level = function(device, component) local level = device:get_latest_state(component, "windowShadePreset", "position") or device:get_field(utils.PRESET_LEVEL_KEY) or @@ -31,4 +41,34 @@ utils.set_preset_position_cmd = function(driver, device, command) device:set_field(utils.PRESET_LEVEL_KEY, command.args.position, {persist = true}) end +-- Step shade level handler for statelessWindowShadeLevelStep capability +utils.step_shade_level_handler = function(driver, device, command) + local step = command.args.stepSize or 0 + if step == 0 then return end + + -- Step from the latest target level if it exists, or from the current shade level + local latest_target_level = device:get_field(LATEST_TARGET_LEVEL) or + device:get_latest_state("main", capabilities.windowShadeLevel.ID, capabilities.windowShadeLevel.shadeLevel.NAME, 0) + local new_target_level = st_utils.clamp_value(latest_target_level + step, 0, 100) + + -- Cancel any previous timer and set a 15 second timeout timer to ensure LATEST_TARGET_LEVEL is cleared + -- This is to prevent the stateless step from accumulating indefinitely, while ensuring that a single + -- "scroll" action can accumulate multiple steps within a short time frame + if device:get_field(TARGET_LEVEL_TIME_OUT) then + device.thread:cancel_timer(device:get_field(TARGET_LEVEL_TIME_OUT)) + end + local timer = device.thread:call_with_delay(TARGET_LEVEL_TIME_OUT_SECONDS, function(d) + device:set_field(LATEST_TARGET_LEVEL, nil) + end) + device:set_field(TARGET_LEVEL_TIME_OUT, timer) + device:set_field(LATEST_TARGET_LEVEL, new_target_level) + + driver:inject_capability_command(device, { + capability = capabilities.windowShadeLevel.ID, + component = command.component, + command = capabilities.windowShadeLevel.commands.setShadeLevel.NAME, + named_args = { shadeLevel = new_target_level } + }) +end + return utils From f7de112d3dae67e29a23975a23abead307b0cfe8 Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Thu, 23 Jul 2026 01:24:10 +0900 Subject: [PATCH 477/538] Matter Lock: Update deprecated fingerprints (#3138) --------- Signed-off-by: Hunsup Jung --- .../SmartThings/matter-lock/fingerprints.yml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index bfa19905a5..75f2fcf37d 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -117,48 +117,48 @@ matterManufacturer: deviceLabel: Level Lock Plus (Matter) vendorId: 0x129F productId: 0x0001 - deviceProfileName: lock-nocodes-notamper-batteryLevel + deviceProfileName: lock - id: "4767/3" deviceLabel: Level Bolt (Matter) vendorId: 0x129F productId: 0x0003 - deviceProfileName: lock-nocodes-notamper-batteryLevel + deviceProfileName: lock - id: "4767/4" deviceLabel: Level Lock Pro vendorId: 0x129F productId: 0x0004 - deviceProfileName: lock-nocodes-notamper-batteryLevel + deviceProfileName: lock - id: "4767/5" deviceLabel: Level Lock (Matter) vendorId: 0x129F productId: 0x0005 - deviceProfileName: lock-nocodes-notamper-batteryLevel + deviceProfileName: lock #Nuki - id: "4957/161" deviceLabel: Nuki Smart Lock Ultra vendorId: 0x135D productId: 0x00A1 - deviceProfileName: lock-nocodes-notamper + deviceProfileName: lock - id: "Nuki Smart Lock" deviceLabel: Nuki Smart Lock vendorId: 0x135d productId: 0xb0 - deviceProfileName: lock-nocodes-notamper + deviceProfileName: lock - id: "Nuki Smart Lock Pro" deviceLabel: Nuki Smart Lock Pro vendorId: 0x135d productId: 0xb1 - deviceProfileName: lock-nocodes-notamper + deviceProfileName: lock - id: "4957/178" deviceLabel: Nuki Smart Lock Pro vendorId: 0x135D productId: 0x00B2 - deviceProfileName: lock-nocodes-notamper + deviceProfileName: lock - id: "4957/193" deviceLabel: Nuki Smart Lock Go vendorId: 0x135D productId: 0x00C1 - deviceProfileName: lock-nocodes-notamper + deviceProfileName: lock #U-tec - id: "5247/1" deviceLabel: ULTRALOQ Bolt Fingerprint Smart Matter Deadbolt @@ -202,4 +202,4 @@ matterGeneric: deviceLabel: Matter Door Lock deviceTypes: - id: 0x000A # Door Lock - deviceProfileName: base-lock + deviceProfileName: lock From 37dead4f3d1b1f9c34372fd000a70f9da2766a21 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:05:05 -0500 Subject: [PATCH 478/538] GHA: Fix publish test results for multiple drivers (#3143) --- .github/workflows/publish-test-results.yml | 61 +++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-test-results.yml b/.github/workflows/publish-test-results.yml index 57c8812913..099ac90648 100644 --- a/.github/workflows/publish-test-results.yml +++ b/.github/workflows/publish-test-results.yml @@ -22,9 +22,66 @@ jobs: event_file: event-file/event.json event_name: ${{ github.event.workflow_run.event }} files: "tests/*.xml" - - name: Publish coverage results + + # 5monkeys/cobertura-action has a bug where the `files` array that builds + # each per-XML markdown table is declared once outside the loop over reports + # and is never reset. When multiple XML files are passed via a glob, every + # subsequent driver's table accumulates all files from earlier drivers. + # + # Fix: determine which XML files exist at runtime, then fan out into a matrix + # job so each driver's XML is processed by its own isolated action invocation. + list-coverage-files: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.build-matrix.outputs.matrix }} + steps: + - name: Download coverage artifact + continue-on-error: true + uses: dawidd6/action-download-artifact@v6 + with: + workflow: run-tests.yml + run_id: ${{ github.event.workflow_run.id }} + name: coverage + path: coverage + - name: Build matrix of coverage files + id: build-matrix + run: | + files=$(find coverage -maxdepth 1 -name '*_coverage.xml' -printf '%f\n' 2>/dev/null | sort) + if [ -z "$files" ]; then + echo 'matrix=[]' >> $GITHUB_OUTPUT + else + json=$(echo "$files" | python3 -c 'import sys,json; print(json.dumps([l.strip() for l in sys.stdin if l.strip()]))') + echo "matrix=$json" >> $GITHUB_OUTPUT + fi + + publish-coverage: + needs: list-coverage-files + if: needs.list-coverage-files.outputs.matrix != '[]' && needs.list-coverage-files.outputs.matrix != '' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + coverage_file: ${{ fromJson(needs.list-coverage-files.outputs.matrix) }} + steps: + - name: Download coverage artifact + uses: dawidd6/action-download-artifact@v6 + with: + workflow: run-tests.yml + run_id: ${{ github.event.workflow_run.id }} + name: coverage + path: coverage + - name: Download pr_number artifact + uses: dawidd6/action-download-artifact@v6 + with: + workflow: run-tests.yml + run_id: ${{ github.event.workflow_run.id }} + name: pr_number + path: pr_number + - run: echo "pr_number=$(cat pr_number/pr_number.txt)" >> $GITHUB_ENV + - name: Publish coverage for ${{ matrix.coverage_file }} uses: 5monkeys/cobertura-action@master with: pull_request_number: ${{ env.pr_number }} - path: "coverage/*.xml" + path: coverage/${{ matrix.coverage_file }} minimum_coverage: 90 + report_name: ${{ matrix.coverage_file }} From dad39acf14e84f71c3caba4fdb45483a69f222f7 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:23:11 -0500 Subject: [PATCH 479/538] Luacov Config: Update Exclude Pathing for Embedded Clusters (#3141) --- tools/config.luacov | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/config.luacov b/tools/config.luacov index b3edf6673b..26034bfb27 100644 --- a/tools/config.luacov +++ b/tools/config.luacov @@ -3,6 +3,8 @@ configuration = { "scripting-engine", "lua_libs", "test", + "embedded_clusters", + "embedded_cluster_utils", "unofficial", "DishwasherAlarm", "DishwasherMode", From 9adc33ddff02cfd81b4df57e2510227134b2b4a8 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:24:42 -0500 Subject: [PATCH 480/538] GHA: Fix driver test run warnings (#3139) --- tools/run_driver_tests_p.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/run_driver_tests_p.py b/tools/run_driver_tests_p.py index 16180f7682..20f22938e9 100644 --- a/tools/run_driver_tests_p.py +++ b/tools/run_driver_tests_p.py @@ -29,8 +29,8 @@ def per_driver_task(driver_dir): else: failure_output = None if driver_dir.name in CHANGED_DRIVERS: - with driver_dir.parent.parent.parent.joinpath("tools/coverage_output").joinpath(driver_dir.name+"_coverage.xml") as outfile: - subprocess.run("luacov-cobertura -o {} -c {}".format(outfile, LUACOV_CONFIG), shell=True) + outfile = driver_dir.parent.parent.parent.joinpath("tools/coverage_output").joinpath(driver_dir.name+"_coverage.xml") + subprocess.run("luacov-cobertura -o {} -c {}".format(outfile, LUACOV_CONFIG), shell=True) return failure_output def run_test(test_file): From 40f6c19d2525d07418625310c5e2ac76463efde7 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Mon, 13 Jul 2026 12:59:12 -0500 Subject: [PATCH 481/538] Matter Sensor: Handle invalid sensor unit conversions, add more conversions --- .../attribute_handlers.lua | 18 +++--- .../air_quality_sensor_utils/fields.lua | 61 ++++++++++++++---- .../air_quality_sensor_utils/utils.lua | 13 ++++ .../test/test_unsupported_unit_conversion.lua | 63 +++++++++++++++++++ 4 files changed, 132 insertions(+), 23 deletions(-) create mode 100644 drivers/SmartThings/matter-sensor/src/test/test_unsupported_unit_conversion.lua 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 index 345f7c5782..79315fc999 100644 --- 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 @@ -4,6 +4,7 @@ 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 aqs_utils = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.utils" local AirQualitySensorAttributeHandlers = {} @@ -24,19 +25,16 @@ 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 not ib.data.value then return end + local reporting_unit = device:get_field(capability_name.."_unit") or aqs_fields.unit_default[capability_name] + local converted_value = aqs_utils.convert_value_to_unit(ib.data.value, reporting_unit, target_unit, capability_name) + + if converted_value then + 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 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 index 1e4658c99e..538af70968 100644 --- 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 @@ -1,8 +1,9 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local log = require "log" local version = require "version" -local utils = require "st.utils" +local st_utils = require "st.utils" local clusters = require "st.matter.clusters" local capabilities = require "st.capabilities" @@ -149,30 +150,64 @@ AirQualitySensorFields.level_strings = { [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.CRITICAL] = "hazardous", } -AirQualitySensorFields.conversion_tables = { +AirQualitySensorFields.MGM3_PPM_CONVERSION_FACTOR = 24.45 + +-- measured in g/mol +AirQualitySensorFields.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 function is_valid_molecular_weight(molecular_weight) + if type(molecular_weight) ~= "number" or molecular_weight <= 0 then + log.warn_with({hub_logs = true}, string.format("unit conversion molecular weight (%s) is not a valid number", molecular_weight)) + return false + end + return true +end + +AirQualitySensorFields.unit_conversion = { [units.PPM] = { - [units.PPM] = function(value) return utils.round(value) end, - [units.PPB] = function(value) return utils.round(value * (10^3)) end - }, + [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) if is_valid_molecular_weight(molecular_weight) then + return st_utils.round((value * molecular_weight * 10^3) / AirQualitySensorFields.MGM3_PPM_CONVERSION_FACTOR) end + end, + [units.MGM3] = function(value, molecular_weight) if is_valid_molecular_weight(molecular_weight) then + return st_utils.round((value * molecular_weight) / AirQualitySensorFields.MGM3_PPM_CONVERSION_FACTOR) end + end,}, [units.PPB] = { - [units.PPM] = function(value) return utils.round(value/(10^3)) end, - [units.PPB] = function(value) return utils.round(value) end + [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 utils.round(value/(10^6)) end + [units.PPM] = function(value) return st_utils.round(value/(10^6)) end }, [units.MGM3] = { - [units.UGM3] = function(value) return utils.round(value * (10^3)) end + [units.UGM3] = function(value) return st_utils.round(value * (10^3)) end, + [units.PPM] = function(value, molecular_weight) if is_valid_molecular_weight(molecular_weight) then + return st_utils.round((value * AirQualitySensorFields.MGM3_PPM_CONVERSION_FACTOR) / molecular_weight) end + end, }, [units.UGM3] = { - [units.UGM3] = function(value) return utils.round(value) end + [units.UGM3] = function(value) return st_utils.round(value) end, + [units.PPM] = function(value, molecular_weight) if is_valid_molecular_weight(molecular_weight) then + return st_utils.round((value * AirQualitySensorFields.MGM3_PPM_CONVERSION_FACTOR) / (molecular_weight * 10^3)) end + end, }, [units.NGM3] = { - [units.UGM3] = function(value) return utils.round(value/(10^3)) end + [units.UGM3] = function(value) return st_utils.round(value/(10^3)) end }, [units.BQM3] = { - [units.PCIL] = function(value) return utils.round(value/37) end - } + [units.PCIL] = function(value) return st_utils.round(value/37) end + }, } return AirQualitySensorFields 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 index c11ac46605..2e8d9e487b 100644 --- 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 @@ -1,6 +1,7 @@ -- 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 embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" @@ -77,6 +78,18 @@ function AirQualitySensorUtils.set_supported_health_concern_values(device) end end +function AirQualitySensorUtils.convert_value_to_unit(value, from_unit, to_unit, capability_name) + local conversion_function = fields.unit_conversion[from_unit] and fields.unit_conversion[from_unit][to_unit] + 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 + elseif type(value) ~= "number" then + log.info_with( {hub_logs = true} , string.format("unit conversion value (%s) is not a number", value)) + return + end + return conversion_function(value, fields.molecular_weights[capability_name]) +end + --- Deeply compare two values. --- Handles metatables. Can optionally ignore cycle checking and/or function differences. --- diff --git a/drivers/SmartThings/matter-sensor/src/test/test_unsupported_unit_conversion.lua b/drivers/SmartThings/matter-sensor/src/test/test_unsupported_unit_conversion.lua new file mode 100644 index 0000000000..2e79f88dd7 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/test/test_unsupported_unit_conversion.lua @@ -0,0 +1,63 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local capabilities = require "st.capabilities" + +-- Unit conversion utilities for sensor +local air_quality_sensor_utils = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.utils" +local air_quality_sensor_fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" + +local units = air_quality_sensor_fields.units + +test.register_coroutine_test( + "Unsupported unit conversion from PPT to MGM3 should return nil", + function() + local result = air_quality_sensor_utils.convert_value_to_unit(100, units.PPT, units.MGM3, capabilities.ozoneMeasurement.NAME) + assert(result == nil, "Expected nil for unsupported PPT to MGM3 conversion, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "Unsupported unit conversion from PPB to UGM3 should return nil", + function() + local result = air_quality_sensor_utils.convert_value_to_unit(100, units.PPB, units.UGM3, capabilities.carbonMonoxideMeasurement.NAME) + assert(result == nil, "Expected nil for unsupported PPB to UGM3 conversion, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "Unsupported unit conversion from NGM3 to PPM should return nil", + function() + local result = air_quality_sensor_utils.convert_value_to_unit(100, units.NGM3, units.PPM, capabilities.carbonMonoxideMeasurement.NAME) + assert(result == nil, "Expected nil for unsupported NGM3 to PPM conversion, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "Supported unit conversion from PPM to PPB should return correct value", + function() + local result = air_quality_sensor_utils.convert_value_to_unit(100, units.PPM, units.PPB, capabilities.carbonMonoxideMeasurement.NAME) + assert(result ~= nil, "Expected a value for supported PPM to PPB conversion") + assert(result == 100000, "Expected 100000 for PPM to PPB conversion, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "Supported unit conversion from PPB to PPM should return correct value", + function() + local result = air_quality_sensor_utils.convert_value_to_unit(1000, units.PPB, units.PPM, capabilities.carbonMonoxideMeasurement.NAME) + assert(result ~= nil, "Expected a value for supported PPB to PPM conversion") + assert(result == 1, "Expected 1 for PPB to PPM conversion, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "Unit conversion with non-numeric value should return nil", + function() + local result = air_quality_sensor_utils.convert_value_to_unit("not a number", units.PPM, units.PPB, capabilities.carbonMonoxideMeasurement.NAME) + assert(result == nil, "Expected nil when value is not a number, got: " .. tostring(result)) + end +) + +test.run_registered_tests() From 296226b93577eb424fcfd4639ce703846d8650db Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Mon, 13 Jul 2026 12:59:45 -0500 Subject: [PATCH 482/538] Matter Thermostat: Handle invalid sensor unit conversions --- .../test/test_unsupported_unit_conversion.lua | 63 +++++++++++++++++++ .../attribute_handlers.lua | 20 ++---- .../src/thermostat_utils/fields.lua | 28 +++++++-- .../src/thermostat_utils/utils.lua | 11 ++-- 4 files changed, 95 insertions(+), 27 deletions(-) create mode 100644 drivers/SmartThings/matter-thermostat/src/test/test_unsupported_unit_conversion.lua diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_unsupported_unit_conversion.lua b/drivers/SmartThings/matter-thermostat/src/test/test_unsupported_unit_conversion.lua new file mode 100644 index 0000000000..6c29de18a9 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/test/test_unsupported_unit_conversion.lua @@ -0,0 +1,63 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local capabilities = require "st.capabilities" + +-- Unit conversion utilities for thermostat +local thermostat_utils = require "thermostat_utils.utils" +local thermostat_fields = require "thermostat_utils.fields" + +local units = thermostat_fields.units + +test.register_coroutine_test( + "Unsupported unit conversion from PPT to MGM3 should return nil", + function() + local result = thermostat_utils.convert_value_to_unit(100, units.PPT, units.MGM3, capabilities.ozoneMeasurement.NAME) + assert(result == nil, "Expected nil for unsupported PPT to MGM3 conversion, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "Unsupported unit conversion from PPB to UGM3 should return nil", + function() + local result = thermostat_utils.convert_value_to_unit(100, units.PPB, units.UGM3, capabilities.carbonMonoxideMeasurement.NAME) + assert(result == nil, "Expected nil for unsupported PPB to UGM3 conversion, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "Unsupported unit conversion from NGM3 to PPM should return nil", + function() + local result = thermostat_utils.convert_value_to_unit(100, units.NGM3, units.PPM, capabilities.carbonMonoxideMeasurement.NAME) + assert(result == nil, "Expected nil for unsupported NGM3 to PPM conversion, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "Supported unit conversion from PPM to PPB should return correct value", + function() + local result = thermostat_utils.convert_value_to_unit(100, units.PPM, units.PPB, capabilities.carbonMonoxideMeasurement.NAME) + assert(result ~= nil, "Expected a value for supported PPM to PPB conversion") + assert(result == 100000, "Expected 100000 for PPM to PPB conversion, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "Supported unit conversion from PPB to PPM should return correct value", + function() + local result = thermostat_utils.convert_value_to_unit(1000, units.PPB, units.PPM, capabilities.carbonMonoxideMeasurement.NAME) + assert(result ~= nil, "Expected a value for supported PPB to PPM conversion") + assert(result == 1, "Expected 1 for PPB to PPM conversion, got: " .. tostring(result)) + end +) + +test.register_coroutine_test( + "Unit conversion with non-numeric value should return nil", + function() + local result = thermostat_utils.convert_value_to_unit("not a number", units.PPM, units.PPB, capabilities.carbonMonoxideMeasurement.NAME) + assert(result == nil, "Expected nil when value is not a number, got: " .. tostring(result)) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua index f0a210bc16..e5b3728b61 100644 --- a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua @@ -494,23 +494,15 @@ 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 ib.data.value then return end + local reporting_unit = device:get_field(capability_name.."_unit") or fields.unit_default[capability_name] + local converted_value = thermostat_utils.convert_value_to_unit(ib.data.value, reporting_unit, target_unit, capability_name) - 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]})) + if converted_value then + device:emit_event_for_endpoint(ib.endpoint_id, attribute({value = converted_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]})) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.dustSensor.fineDustLevel({value = converted_value, unit = fields.unit_strings[target_unit]})) end end end diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/fields.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/fields.lua index 770fd7d65e..ddedab385d 100644 --- a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/fields.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/fields.lua @@ -1,6 +1,7 @@ -- 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" @@ -205,13 +206,24 @@ ThermostatFields.molecular_weights = { [capabilities.tvocMeasurement.NAME] = "N/A", } -ThermostatFields.conversion_tables = { +local function is_valid_molecular_weight(molecular_weight) + if type(molecular_weight) ~= "number" or molecular_weight <= 0 then + log.warn_with({hub_logs = true}, string.format("unit conversion molecular weight (%s) is not a valid number", molecular_weight)) + return false + end + return true +end + +ThermostatFields.unit_conversion = { [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.UGM3] = function(value, molecular_weight) if is_valid_molecular_weight(molecular_weight) then + return st_utils.round((value * molecular_weight * 10^3) / ThermostatFields.MGM3_PPM_CONVERSION_FACTOR) end + end, + [units.MGM3] = function(value, molecular_weight) if is_valid_molecular_weight(molecular_weight) then + return st_utils.round((value * molecular_weight) / ThermostatFields.MGM3_PPM_CONVERSION_FACTOR) end + 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, @@ -221,11 +233,15 @@ ThermostatFields.conversion_tables = { }, [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.PPM] = function(value, molecular_weight) if is_valid_molecular_weight(molecular_weight) then + return st_utils.round((value * ThermostatFields.MGM3_PPM_CONVERSION_FACTOR) / molecular_weight) end + 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.PPM] = function(value, molecular_weight) if is_valid_molecular_weight(molecular_weight) then + return st_utils.round((value * ThermostatFields.MGM3_PPM_CONVERSION_FACTOR) / (molecular_weight * 10^3)) end + end, }, [units.NGM3] = { [units.UGM3] = function(value) return st_utils.round(value/(10^3)) end diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua index a233a0c0e7..940fe9835e 100644 --- a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua @@ -180,18 +180,15 @@ function ThermostatUtils.get_device_type(device) 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 +function ThermostatUtils.convert_value_to_unit(value, from_unit, to_unit, capability_name) + local conversion_function = fields.unit_conversion[from_unit] and fields.unit_conversion[from_unit][to_unit] 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") + elseif type(value) ~= "number" then + log.info_with( {hub_logs = true} , string.format("unit conversion value (%s) is not a number", value)) return end - return conversion_function(value, fields.molecular_weights[capability_name]) end From 311711d614c67df789b4307a22b592e5988cc68a Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:21:47 -0500 Subject: [PATCH 483/538] Matter Window Covering: Always report accurate capability operational state (#3133) --- .../matter-window-covering/src/init.lua | 414 +++++---- .../src/sub_drivers.lua | 1 - .../can_handle.lua | 19 - .../fingerprints.lua | 8 - .../init.lua | 126 --- .../src/test/test_matter_window_covering.lua | 822 +++++++----------- 6 files changed, 550 insertions(+), 840 deletions(-) delete mode 100644 drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/can_handle.lua delete mode 100644 drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/fingerprints.lua delete mode 100644 drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua diff --git a/drivers/SmartThings/matter-window-covering/src/init.lua b/drivers/SmartThings/matter-window-covering/src/init.lua index bfde1e3fc8..ba03737a73 100644 --- a/drivers/SmartThings/matter-window-covering/src/init.lua +++ b/drivers/SmartThings/matter-window-covering/src/init.lua @@ -6,26 +6,134 @@ --Note: No support for setting device into calibration mode, it must be done manually local capabilities = require "st.capabilities" local im = require "st.matter.interaction_model" -local log = require "log" local clusters = require "st.matter.clusters" local MatterDriver = require "st.matter.driver" local utils = require "st.utils" -local CURRENT_LIFT = "__current_lift" -local CURRENT_TILT = "__current_tilt" local battery_support = { NO_BATTERY = "NO_BATTERY", BATTERY_LEVEL = "BATTERY_LEVEL", BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE" } -local REVERSE_POLARITY = "__reverse_polarity" -local PRESET_LEVEL_KEY = "__preset_level_key" +local OP_STATUS_BITMAP = { + idle = 0x00, + opening = 0x01, + closing = 0x02 +} local DEFAULT_PRESET_LEVEL = 50 -local LATEST_TARGET_LEVEL = "_latest_target_level" -local TARGET_LEVEL_TIME_OUT = "_target_level_timeout" -local TARGET_LEVEL_TIME_OUT_SECONDS = 15 -local TARGET_REACH_TOLERANCE = 1 +local TARGET_LIFT_PERCENT = { + TIMEOUT_DELAY_S = 120, + TIMEOUT = "__target_lift_percent_timeout", + STATE = "__target_lift_percent" +} + +local TARGET_TILT_PERCENT = { + TIMEOUT_DELAY_S = 120, + TIMEOUT = "__target_tilt_percent_timeout", + STATE = "__target_tilt_percent" +} + +local TARGET_REACH_TOLERANCE = 0.5 + +--- Gets the current window shade status based on the lift and tilt positions. +---@param device any a Matter device object +---@return any status the current window shade status +local function get_idle_window_shade_status(device) + -- Update the window shade status according to the lift and tilt positions. + -- LIFT TILT Window Shade + -- 100 any Open + -- 1-99 any Partially Open + -- 0 1-100 Partially Open + -- 0 0 Closed + -- 0 nil Closed + -- nil 100 Open + -- nil 1-99 Partially Open + -- nil 0 Closed + -- Note that lift or tilt may be nil if either the window shade does not + -- support them or if they haven't been received from a device report yet. + + local lift_position = device:get_latest_state( + "main", + capabilities.windowShadeLevel.ID, + capabilities.windowShadeLevel.shadeLevel.NAME + ) + local tilt_position = device:get_latest_state( + "main", + capabilities.windowShadeTiltLevel.ID, + capabilities.windowShadeTiltLevel.shadeTiltLevel.NAME + ) + local reverse = device.preferences.reverse + local windowShade = capabilities.windowShade.windowShade + local status_event = windowShade.unknown() + + if lift_position == nil then + if tilt_position == 0 then + status_event = reverse and windowShade.open() or windowShade.closed() + elseif tilt_position == 100 then + status_event = reverse and windowShade.closed() or windowShade.open() + else + status_event = windowShade.partially_open() + end + elseif lift_position == 100 then + status_event = reverse and windowShade.closed() or windowShade.open() + elseif lift_position > 0 then + status_event = windowShade.partially_open() + elseif lift_position == 0 then + if tilt_position == nil or tilt_position == 0 then + status_event = reverse and windowShade.open() or windowShade.closed() + elseif tilt_position > 0 then + status_event = windowShade.partially_open() + end + end + return status_event +end + +--- Clears the cached target percentage and cancels any associated timeout. +--- @param device any a Matter device object +--- @param op_fields table a table containing operational field constants used for caching target percentages and timeouts. +local function clear_cached_data(device, op_fields) + local current_timer = device:get_field(op_fields.TIMEOUT) + if current_timer then + device.thread:cancel_timer(current_timer) + end + device:set_field(op_fields.STATE, nil) + device:set_field(op_fields.TIMEOUT, nil) +end + +--- Tries to emit the idle window shade status if no target percentages are set. +--- @param device any a Matter device object +local function try_emit_idle_window_shade_status(device) + if not device:get_field(TARGET_LIFT_PERCENT.STATE) and + not device:get_field(TARGET_TILT_PERCENT.STATE) then + device:emit_event(get_idle_window_shade_status(device)) + end +end + +--- Caches the target percentage for a device and sets a timeout to clear it. +--- @param device any a Matter device object +--- @param data number the target percentage to cache +--- @param op_fields table a table containing operational field constants used for caching target percentages and timeouts. +local function cache_data_with_timeout(device, data, op_fields) + device:set_field(op_fields.STATE, data) + local previous_timer = device:get_field(op_fields.TIMEOUT) + if previous_timer then + device.thread:cancel_timer(previous_timer) + end + local new_timer = device.thread:call_with_delay(op_fields.TIMEOUT_DELAY_S, function() + device:set_field(op_fields.STATE, nil) + device:set_field(op_fields.TIMEOUT, nil) + try_emit_idle_window_shade_status(device) + end) + device:set_field(op_fields.TIMEOUT, new_timer) +end + +local function is_target_value_reached(current_value, target_value) + if (target_value and math.abs(current_value - target_value) <= TARGET_REACH_TOLERANCE) then + return true + end + return false +end local function find_default_endpoint(device, cluster) local res = device.MATTER_DEFAULT_ENDPOINT @@ -66,17 +174,18 @@ local function match_profile(device, battery_supported) end local function device_init(driver, device) + -- set unused fields to nil. These field settings can be removed after a single driver update. + device:set_field("__preset_level_key", nil) + device:set_field("__reverse_polarity", nil) + device:set_component_to_endpoint_fn(component_to_endpoint) if device:supports_capability_by_id(capabilities.windowShadePreset.ID) and device:get_latest_state("main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.position.NAME) == nil then -- These should only ever be nil once (and at the same time) for already-installed devices -- It can be removed after migration is complete 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 - DEFAULT_PRESET_LEVEL + local preset_position = device.preferences.presetPosition or 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 device:subscribe() end @@ -94,17 +203,8 @@ end local function info_changed(driver, device, event, args) if device.profile.id ~= args.old_st_store.profile.id then - -- Profile has changed, resubscribe device:subscribe() - elseif args.old_st_store.preferences.reverse ~= device.preferences.reverse then - if device.preferences.reverse then - device:set_field(REVERSE_POLARITY, true, { persist = true }) - else - device:set_field(REVERSE_POLARITY, false, { persist = true }) - end - else - -- Something else has changed info (SW update, reinterview, etc.), so - -- try updating profile as needed + elseif device.matter_version.software ~= args.old_st_store.matter_version.software then 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, {}) @@ -120,17 +220,18 @@ local function device_added(driver, device) device:emit_event( capabilities.windowShade.supportedWindowShadeCommands({"open", "close", "pause"}, {visibility = {displayed = false}}) ) - device:set_field(REVERSE_POLARITY, false, { persist = true }) end -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 preset_target_percent = device:get_latest_state( + "main", + capabilities.windowShadePreset.ID, + capabilities.windowShadePreset.position.NAME, + DEFAULT_PRESET_LEVEL + ) + cache_data_with_timeout(device, preset_target_percent, TARGET_LIFT_PERCENT) + local hundredths_lift_percent = (100 - preset_target_percent) * 100 local endpoint_id = device:component_to_endpoint(cmd.component) device:send(clusters.WindowCovering.server.commands.GoToLiftPercentage( device, endpoint_id, hundredths_lift_percent @@ -139,191 +240,157 @@ end local function handle_set_preset(driver, device, cmd) local endpoint_id = device:component_to_endpoint(cmd.component) - device:set_field(PRESET_LEVEL_KEY, cmd.args.position) device:emit_event_for_endpoint(endpoint_id, capabilities.windowShadePreset.position(cmd.args.position)) end --- close covering local function handle_close(driver, device, cmd) local endpoint_id = device:component_to_endpoint(cmd.component) - local req = clusters.WindowCovering.server.commands.DownOrClose(device, endpoint_id) - if device:get_field(REVERSE_POLARITY) then - req = clusters.WindowCovering.server.commands.UpOrOpen(device, endpoint_id) + if #device:get_endpoints(clusters.WindowCovering.ID, {feature_bitmap = clusters.WindowCovering.types.Feature.LIFT}) > 0 then + cache_data_with_timeout(device, device.preferences.reverse and 100 or 0, TARGET_LIFT_PERCENT) end - device:send(req) + if #device:get_endpoints(clusters.WindowCovering.ID, {feature_bitmap = clusters.WindowCovering.types.Feature.TILT}) > 0 then + cache_data_with_timeout(device, device.preferences.reverse and 100 or 0, TARGET_TILT_PERCENT) + end + device:send(device.preferences.reverse and + clusters.WindowCovering.server.commands.UpOrOpen(device, endpoint_id) or + clusters.WindowCovering.server.commands.DownOrClose(device, endpoint_id) + ) end --- open covering local function handle_open(driver, device, cmd) local endpoint_id = device:component_to_endpoint(cmd.component) - local req = clusters.WindowCovering.server.commands.UpOrOpen(device, endpoint_id) - if device:get_field(REVERSE_POLARITY) then - req = clusters.WindowCovering.server.commands.DownOrClose(device, endpoint_id) + if #device:get_endpoints(clusters.WindowCovering.ID, {feature_bitmap = clusters.WindowCovering.types.Feature.LIFT}) > 0 then + cache_data_with_timeout(device, device.preferences.reverse and 0 or 100, TARGET_LIFT_PERCENT) + end + if #device:get_endpoints(clusters.WindowCovering.ID, {feature_bitmap = clusters.WindowCovering.types.Feature.TILT}) > 0 then + cache_data_with_timeout(device, device.preferences.reverse and 0 or 100, TARGET_TILT_PERCENT) end - device:send(req) + device:send(device.preferences.reverse and + clusters.WindowCovering.server.commands.DownOrClose(device, endpoint_id) or + clusters.WindowCovering.server.commands.UpOrOpen(device, endpoint_id) + ) end --- pause covering local function handle_pause(driver, device, cmd) local endpoint_id = device:component_to_endpoint(cmd.component) - local req = clusters.WindowCovering.server.commands.StopMotion(device, endpoint_id) - device:send(req) + device:send(clusters.WindowCovering.server.commands.StopMotion(device, endpoint_id)) end -- move to shade level between 0-100 local function handle_shade_level(driver, device, cmd) local endpoint_id = device:component_to_endpoint(cmd.component) - local lift_percentage_value = 100 - cmd.args.shadeLevel - local hundredths_lift_percentage = lift_percentage_value * 100 - local req = clusters.WindowCovering.server.commands.GoToLiftPercentage( - device, endpoint_id, hundredths_lift_percentage - ) - device:send(req) + local hundredths_lift_percentage = (100 - cmd.args.shadeLevel) * 100 + cache_data_with_timeout(device, cmd.args.shadeLevel, TARGET_LIFT_PERCENT) + device:send(clusters.WindowCovering.server.commands.GoToLiftPercentage( + device, endpoint_id, hundredths_lift_percentage + )) end local function handle_step_shade_level(driver, device, cmd) local step = cmd.args.stepSize - - local current_level = device:get_field(LATEST_TARGET_LEVEL) or - device:get_latest_state("main", capabilities.windowShadeLevel.ID, - capabilities.windowShadeLevel.shadeLevel.NAME) or 0 - - local target_level = utils.clamp_value(current_level + step, 0, 100) - - device:set_field(LATEST_TARGET_LEVEL, target_level) - - local old_timer = device:get_field(TARGET_LEVEL_TIME_OUT) - if old_timer ~= nil then - device.thread:cancel_timer(old_timer) - end - - -- Set 15 second timeout timer to ensure target_level is cleared - local timer = device.thread:call_with_delay(TARGET_LEVEL_TIME_OUT_SECONDS, function(d) - device:set_field(LATEST_TARGET_LEVEL, nil) - end) - device:set_field(TARGET_LEVEL_TIME_OUT, timer) - + local latest_lift_percentage = device:get_latest_state( + "main", + capabilities.windowShadeLevel.ID, + capabilities.windowShadeLevel.shadeLevel.NAME, + 0 + ) + local target_lift_percent = device:get_field(TARGET_LIFT_PERCENT.STATE) or latest_lift_percentage + local updated_target_lift = utils.clamp_value(target_lift_percent + step, 0, 100) + cache_data_with_timeout(device, updated_target_lift, TARGET_LIFT_PERCENT) driver:inject_capability_command(device, { capability = capabilities.windowShadeLevel.ID, component = cmd.component, command = capabilities.windowShadeLevel.commands.setShadeLevel.NAME, - named_args = { shadeLevel = target_level } + named_args = { shadeLevel = updated_target_lift } }) end -- move to shade tilt level between 0-100 local function handle_shade_tilt_level(driver, device, cmd) local endpoint_id = device:component_to_endpoint(cmd.component) - local tilt_percentage_value = 100 - cmd.args.level - local hundredths_tilt_percentage = tilt_percentage_value * 100 + local hundredths_tilt_percentage = (100 - cmd.args.level) * 100 + cache_data_with_timeout(device, cmd.args.level, TARGET_TILT_PERCENT) device:send(clusters.WindowCovering.server.commands.GoToTiltPercentage( device, endpoint_id, hundredths_tilt_percentage )) end --- current lift/tilt percentage, changed to 100ths percent -local current_pos_handler = function(attribute) - return function(driver, device, ib, response) - if ib.data.value == nil then - return - end - local windowShade = capabilities.windowShade.windowShade - local position = 100 - math.floor(ib.data.value / 100) - - -- Step control logic - local target_level_field = device:get_field(LATEST_TARGET_LEVEL) - if target_level_field and attribute == capabilities.windowShadeLevel.shadeLevel then - -- Allow ±1 degree tolerance for reaching target - if math.abs(position - target_level_field) <= TARGET_REACH_TOLERANCE then - -- Device reached target position, clear target marker and timeout timer - device:set_field(LATEST_TARGET_LEVEL, nil) - local timer = device:get_field(TARGET_LEVEL_TIME_OUT) - if timer ~= nil then - device.thread:cancel_timer(timer) - device:set_field(TARGET_LEVEL_TIME_OUT, nil) - end - end - end +-- attribute handlers +local function current_position_lift_percent_100ths_handler(driver, device, ib, response) + if not ib.data.value then return end + local lift_percent = 100 - math.floor(ib.data.value / 100) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windowShadeLevel.shadeLevel(lift_percent)) + + if is_target_value_reached(lift_percent, device:get_field(TARGET_LIFT_PERCENT.STATE)) then + clear_cached_data(device, TARGET_LIFT_PERCENT) + try_emit_idle_window_shade_status(device) + end +end - local reverse = device:get_field(REVERSE_POLARITY) - device:emit_event_for_endpoint(ib.endpoint_id, attribute(position)) +local function current_position_tilt_percent_100ths_handler(driver, device, ib, response) + if not ib.data.value then return end + local tilt_percent = 100 - math.floor(ib.data.value / 100) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windowShadeTiltLevel.shadeTiltLevel(tilt_percent)) - if attribute == capabilities.windowShadeLevel.shadeLevel then - device:set_field(CURRENT_LIFT, position) + if is_target_value_reached(tilt_percent, device:get_field(TARGET_TILT_PERCENT.STATE)) then + clear_cached_data(device, TARGET_TILT_PERCENT) + try_emit_idle_window_shade_status(device) + end +end + +local function target_position_lift_percent_100ths_handler(driver, device, ib, response) + if type(ib.data.value) == "number" then + local target_lift_percent = 100 - math.floor(ib.data.value / 100) + local latest_lift_percentage = device:get_latest_state( + "main", + capabilities.windowShadeLevel.ID, + capabilities.windowShadeLevel.shadeLevel.NAME + ) + if latest_lift_percentage and is_target_value_reached(latest_lift_percentage, target_lift_percent) then + clear_cached_data(device, TARGET_LIFT_PERCENT) + try_emit_idle_window_shade_status(device) else - device:set_field(CURRENT_TILT, position) + cache_data_with_timeout(device, target_lift_percent, TARGET_LIFT_PERCENT) end + end +end - local lift_position = device:get_field(CURRENT_LIFT) - local tilt_position = device:get_field(CURRENT_TILT) - - -- Update the window shade status according to the lift and tilt positions. - -- LIFT TILT Window Shade - -- 100 any Open - -- 1-99 any Partially Open - -- 0 1-100 Partially Open - -- 0 0 Closed - -- 0 nil Closed - -- nil 100 Open - -- nil 1-99 Partially Open - -- nil 0 Closed - -- Note that lift or tilt may be nil if either the window shade does not - -- support them or if they haven't been received from a device report yet. - - if lift_position == nil then - if tilt_position == 0 then - device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.open() or windowShade.closed()) - elseif tilt_position == 100 then - device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closed() or windowShade.open()) - else - device:emit_event_for_endpoint(ib.endpoint_id, windowShade.partially_open()) - end - - elseif lift_position == 100 then - device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closed() or windowShade.open()) - - elseif lift_position > 0 then - device:emit_event_for_endpoint(ib.endpoint_id, windowShade.partially_open()) - - elseif lift_position == 0 then - if tilt_position == nil or tilt_position == 0 then - device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.open() or windowShade.closed()) - elseif tilt_position > 0 then - device:emit_event_for_endpoint(ib.endpoint_id, windowShade.partially_open()) - end +local function target_position_tilt_percent_100ths_handler(driver, device, ib, response) + if type(ib.data.value) == "number" then + local target_tilt_percent = 100 - math.floor(ib.data.value / 100) + local latest_tilt_percent = device:get_latest_state( + "main", + capabilities.windowShadeTiltLevel.ID, + capabilities.windowShadeTiltLevel.shadeTiltLevel.NAME + ) + if latest_tilt_percent and is_target_value_reached(latest_tilt_percent, target_tilt_percent) then + clear_cached_data(device, TARGET_TILT_PERCENT) + try_emit_idle_window_shade_status(device) + else + cache_data_with_timeout(device, target_tilt_percent, TARGET_TILT_PERCENT) end end end -- checks the current position of the shade -local function current_status_handler(driver, device, ib, response) +local function operational_status_handler(driver, device, ib, response) + if not ib.data.value then return end + local global_op_status = ib.data.value & clusters.WindowCovering.types.OperationalStatus.GLOBAL + + local reverse = device.preferences.reverse local windowShade = capabilities.windowShade.windowShade - local reverse = device:get_field(REVERSE_POLARITY) - local state = ib.data.value & clusters.WindowCovering.types.OperationalStatus.GLOBAL - if state == 1 then -- opening - device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closing() or windowShade.opening()) - elseif state == 2 then -- closing - device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.opening() or windowShade.closing()) - elseif state == 0 then -- stopped (idle) - -- Clear target_level when device stops moving - local target_level_field = device:get_field(LATEST_TARGET_LEVEL) - if target_level_field then - device:set_field(LATEST_TARGET_LEVEL, nil) - local timer = device:get_field(TARGET_LEVEL_TIME_OUT) - if timer ~= nil then - device.thread:cancel_timer(timer) - end - end - elseif state ~= 0 then -- unknown - device:emit_event_for_endpoint(ib.endpoint_id, windowShade.unknown()) + local status_event + if global_op_status == OP_STATUS_BITMAP.idle then + try_emit_idle_window_shade_status(device) + elseif global_op_status == OP_STATUS_BITMAP.opening then + status_event = reverse and windowShade.closing() or windowShade.opening() + elseif global_op_status == OP_STATUS_BITMAP.closing then + status_event = reverse and windowShade.opening() or windowShade.closing() + else + status_event = windowShade.unknown() end -end - -local function level_attr_handler(driver, device, ib, response) - if ib.data.value ~= nil then - --TODO should we invert this like we do for CurrentLiftPercentage100ths? - local level = math.floor((ib.data.value / 254.0 * 100) + 0.5) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windowShadeLevel.shadeLevel(level)) + if status_event then + device:emit_event_for_endpoint(ib.endpoint_id, status_event) end end @@ -344,7 +411,7 @@ local function battery_charge_level_attr_handler(driver, device, ib, response) end local function power_source_attribute_list_handler(driver, device, ib, response) - for _, attr in ipairs(ib.data.elements) do + for _, attr in ipairs(ib.data.elements or {}) do -- Re-profile the device if BatPercentRemaining (Attribute ID 0x0C) is present. if attr.value == 0x0C then match_profile(device, battery_support.BATTERY_PERCENTAGE) @@ -359,23 +426,18 @@ end local matter_driver_template = { lifecycle_handlers = { init = device_init, - removed = device_removed, added = device_added, infoChanged = info_changed, doConfigure = do_configure }, matter_handlers = { attr = { - --TODO LevelControl may not be needed for certified devices since - -- certified should use CurrentPositionLiftPercent100ths attr - [clusters.LevelControl.ID] = { - [clusters.LevelControl.attributes.CurrentLevel.ID] = level_attr_handler, - }, [clusters.WindowCovering.ID] = { - --uses percent100ths more often - [clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID] = current_pos_handler(capabilities.windowShadeLevel.shadeLevel), - [clusters.WindowCovering.attributes.CurrentPositionTiltPercent100ths.ID] = current_pos_handler(capabilities.windowShadeTiltLevel.shadeTiltLevel), - [clusters.WindowCovering.attributes.OperationalStatus.ID] = current_status_handler, + [clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID] = current_position_lift_percent_100ths_handler, + [clusters.WindowCovering.attributes.CurrentPositionTiltPercent100ths.ID] = current_position_tilt_percent_100ths_handler, + [clusters.WindowCovering.attributes.TargetPositionLiftPercent100ths.ID] = target_position_lift_percent_100ths_handler, + [clusters.WindowCovering.attributes.TargetPositionTiltPercent100ths.ID] = target_position_tilt_percent_100ths_handler, + [clusters.WindowCovering.attributes.OperationalStatus.ID] = operational_status_handler, }, [clusters.PowerSource.ID] = { [clusters.PowerSource.attributes.AttributeList.ID] = power_source_attribute_list_handler, @@ -391,9 +453,11 @@ local matter_driver_template = { [capabilities.windowShadeLevel.ID] = { clusters.LevelControl.attributes.CurrentLevel, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths, + clusters.WindowCovering.attributes.TargetPositionLiftPercent100ths, }, [capabilities.windowShadeTiltLevel.ID] = { clusters.WindowCovering.attributes.CurrentPositionTiltPercent100ths, + clusters.WindowCovering.attributes.TargetPositionTiltPercent100ths, }, [capabilities.battery.ID] = { clusters.PowerSource.attributes.BatPercentRemaining diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers.lua index ad3e53e4b0..3a833f39bc 100644 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers.lua @@ -4,6 +4,5 @@ local lazy_load_if_possible = require "lazy_load_subdriver" local sub_drivers = { lazy_load_if_possible("sub_drivers.closure"), - lazy_load_if_possible("sub_drivers.matter-window-covering-position-updates-while-moving"), } return sub_drivers diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/can_handle.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/can_handle.lua deleted file mode 100644 index e3e12116e1..0000000000 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/can_handle.lua +++ /dev/null @@ -1,19 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local function is_matter_window_covering_position_updates_while_moving(opts, driver, device) - local device_lib = require "st.device" - if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then - return false - end - local FINGERPRINTS = require("sub_drivers.matter-window-covering-position-updates-while-moving.fingerprints") - for i, v in ipairs(FINGERPRINTS) do - if device.manufacturer_info.vendor_id == v[1] and - device.manufacturer_info.product_id == v[2] then - return true, require("sub_drivers.matter-window-covering-position-updates-while-moving") - end - end - return false -end - -return is_matter_window_covering_position_updates_while_moving diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/fingerprints.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/fingerprints.lua deleted file mode 100644 index 37800fc680..0000000000 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/fingerprints.lua +++ /dev/null @@ -1,8 +0,0 @@ --- Copyright 2025 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - -local SUB_WINDOW_COVERING_VID_PID = { - {0x10e1, 0x1005} -- VDA -} - -return SUB_WINDOW_COVERING_VID_PID diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua deleted file mode 100644 index 5db95ba28c..0000000000 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua +++ /dev/null @@ -1,126 +0,0 @@ --- Copyright 2023 SmartThings, Inc. --- Licensed under the Apache License, Version 2.0 - - -local capabilities = require "st.capabilities" -local clusters = require "st.matter.clusters" - -local DEFAULT_LEVEL = 0 -local STATE_MACHINE = "__state_machine" -local REVERSE_POLARITY = "__reverse_polarity" - -local StateMachineEnum = { - STATE_IDLE = 0x00, - STATE_MOVING = 0x01, - STATE_OPERATIONAL_STATE_FIRED = 0x02, - STATE_CURRENT_POSITION_FIRED = 0x03 -} - - - -local function device_init(driver, device) - device:subscribe() -end - --- current lift percentage, changed to 100ths percent -local function current_pos_handler(driver, device, ib, response) - if ib.data.value == nil then - return - end - local position = 100 - math.floor(ib.data.value / 100) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windowShadeLevel.shadeLevel(position)) - local windowShade = capabilities.windowShade.windowShade - local reverse = device:get_field(REVERSE_POLARITY) - local state_machine = device:get_field(STATE_MACHINE) - -- When state_machine is STATE_IDLE or STATE_CURRENT_POSITION_FIRED, nothing to do - if state_machine == StateMachineEnum.STATE_MOVING then - device:set_field(STATE_MACHINE, StateMachineEnum.STATE_CURRENT_POSITION_FIRED) - elseif state_machine == StateMachineEnum.STATE_OPERATIONAL_STATE_FIRED or state_machine == nil then - if position == 0 then - device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.open() or windowShade.closed()) - elseif position == 100 then - device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closed() or windowShade.open()) - elseif position > 0 and position < 100 then - device:emit_event_for_endpoint(ib.endpoint_id, windowShade.partially_open()) - else - device:emit_event_for_endpoint(ib.endpoint_id, windowShade.unknown()) - end - device:set_field(STATE_MACHINE, StateMachineEnum.STATE_IDLE) - end -end - --- checks the current position of the shade -local function current_status_handler(driver, device, ib, response) - local attr = capabilities.windowShade.windowShade - local position = device:get_latest_state( - "main", capabilities.windowShadeLevel.ID, - capabilities.windowShadeLevel.shadeLevel.NAME - ) or DEFAULT_LEVEL - for _, rb in ipairs(response.info_blocks) do - if rb.info_block.attribute_id == clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID and - rb.info_block.cluster_id == clusters.WindowCovering.ID and - rb.info_block.data ~= nil and - rb.info_block.data.value ~= nil then - position = math.floor(rb.info_block.data.value / 100) - end - end - position = 100 - position - local reverse = device:get_field(REVERSE_POLARITY) - local state = ib.data.value & clusters.WindowCovering.types.OperationalStatus.GLOBAL - local state_machine = device:get_field(STATE_MACHINE) - -- When state_machine is STATE_OPERATIONAL_STATE_FIRED, nothing to do - if state_machine == StateMachineEnum.STATE_IDLE then - if state == 1 then -- opening - device:emit_event_for_endpoint(ib.endpoint_id, reverse and attr.closing() or attr.opening()) - device:set_field(STATE_MACHINE, StateMachineEnum.STATE_MOVING) - elseif state == 2 then -- closing - device:emit_event_for_endpoint(ib.endpoint_id, reverse and attr.opening() or attr.closing()) - device:set_field(STATE_MACHINE, StateMachineEnum.STATE_MOVING) - end - elseif state_machine == StateMachineEnum.STATE_MOVING then - if state == 0 then -- not moving - device:set_field(STATE_MACHINE, StateMachineEnum.STATE_OPERATIONAL_STATE_FIRED) - elseif state == 1 then -- opening - device:emit_event_for_endpoint(ib.endpoint_id, reverse and attr.closing() or attr.opening()) - elseif state == 2 then -- closing - device:emit_event_for_endpoint(ib.endpoint_id, reverse and attr.opening() or attr.closing()) - else - device:emit_event_for_endpoint(ib.endpoint_id, attr.unknown()) - device:set_field(STATE_MACHINE, StateMachineEnum.STATE_IDLE) - end - elseif state_machine == StateMachineEnum.STATE_CURRENT_POSITION_FIRED then - if state == 0 then -- not moving - if position == 100 then - device:emit_event_for_endpoint(ib.endpoint_id, reverse and attr.closed() or attr.open()) - elseif position == 0 then - device:emit_event_for_endpoint(ib.endpoint_id, reverse and attr.open() or attr.closed()) - else - device:emit_event_for_endpoint(ib.endpoint_id, attr.partially_open()) - end - else - device:emit_event_for_endpoint(ib.endpoint_id, attr.unknown()) - end - device:set_field(STATE_MACHINE, StateMachineEnum.STATE_IDLE) - end -end - -local matter_window_covering_position_updates_while_moving_handler = { - NAME = "matter-window-covering-position-updates-while-moving", - lifecycle_handlers = { - init = device_init, - }, - matter_handlers = { - attr = { - [clusters.WindowCovering.ID] = { - [clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID] = current_pos_handler, - [clusters.WindowCovering.attributes.OperationalStatus.ID] = current_status_handler, - } - } - }, - capability_handlers = { - }, - can_handle = require("sub_drivers.matter-window-covering-position-updates-while-moving.can_handle"), - shared_device_thread_enabled = true, -} - -return matter_window_covering_position_updates_while_moving_handler 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 b65a621c83..8376e3a6d4 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 @@ -21,6 +21,7 @@ local mock_device = test.mock_device.build_test_matter_device( { profile = t_utils.get_profile_definition("window-covering-tilt-battery.yml"), manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, + matter_version = {software = 1}, endpoints = { { endpoint_id = 2, @@ -83,6 +84,8 @@ local CLUSTER_SUBSCRIBE_LIST = { clusters.LevelControl.server.attributes.CurrentLevel, WindowCovering.server.attributes.CurrentPositionLiftPercent100ths, WindowCovering.server.attributes.CurrentPositionTiltPercent100ths, + WindowCovering.server.attributes.TargetPositionLiftPercent100ths, + WindowCovering.server.attributes.TargetPositionTiltPercent100ths, WindowCovering.server.attributes.OperationalStatus, clusters.PowerSource.server.attributes.BatPercentRemaining } @@ -91,6 +94,7 @@ local CLUSTER_SUBSCRIBE_LIST_NO_BATTERY = { clusters.LevelControl.server.attributes.CurrentLevel, WindowCovering.server.attributes.CurrentPositionLiftPercent100ths, WindowCovering.server.attributes.OperationalStatus, + WindowCovering.server.attributes.TargetPositionLiftPercent100ths, } local function set_preset(device) @@ -156,24 +160,18 @@ end test.set_test_init_function(test_init) test.register_coroutine_test( - "WindowCovering OperationalStatus state closed following lift position update", function() - test.socket.capability:__set_channel_ordering("relaxed") + "Idle WindowCovering OperationalStatus state triggers 'open' capability emission if lift position is 100", function() test.socket.matter:__queue_receive( { mock_device.id, WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, 10000 + mock_device, 10, 0 ), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(0) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.closed() + "main", capabilities.windowShadeLevel.shadeLevel(100) ) ) test.socket.matter:__queue_receive( @@ -182,6 +180,11 @@ test.register_coroutine_test( WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), } ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShade.windowShade.open() + ) + ) end, { min_api_version = 17 @@ -189,24 +192,18 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "WindowCovering OperationalStatus state closed following tilt position update", function() - test.socket.capability:__set_channel_ordering("relaxed") + "Idle WindowCovering OperationalStatus state triggers 'closed' capability emission if lift position is 0", function() test.socket.matter:__queue_receive( { mock_device.id, - WindowCovering.attributes.CurrentPositionTiltPercent100ths:build_test_report_data( + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( mock_device, 10, 10000 ), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(0) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.closed() + "main", capabilities.windowShadeLevel.shadeLevel(0) ) ) test.socket.matter:__queue_receive( @@ -215,6 +212,11 @@ test.register_coroutine_test( WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), } ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShade.windowShade.closed() + ) + ) end, { min_api_version = 17 @@ -222,30 +224,29 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "WindowCovering OperationalStatus state closed before lift position 0", function() - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), - } - ) + "Idle WindowCovering OperationalStatus state triggers 'partially open' capability emission if lift position is 50", function() test.socket.matter:__queue_receive( { mock_device.id, WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, 10000 + mock_device, 10, 5000 ), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(0) + "main", capabilities.windowShadeLevel.shadeLevel(50) ) ) + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), + } + ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.closed() + "main", capabilities.windowShade.windowShade.partially_open() ) ) end, @@ -255,30 +256,30 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "WindowCovering OperationalStatus state closed before tilt position 0", function() + "Idle WindowCovering OperationalStatus state triggers 'partially open' capability emission if tilt position is between 1-100", function() test.socket.capability:__set_channel_ordering("relaxed") - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), - } - ) test.socket.matter:__queue_receive( { mock_device.id, WindowCovering.attributes.CurrentPositionTiltPercent100ths:build_test_report_data( - mock_device, 10, 10000 + mock_device, 10, ((100 - 15) *100) ), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(0) + "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(15) ) ) + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), + } + ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.closed() + "main", capabilities.windowShade.windowShade.partially_open() ) ) end, @@ -288,32 +289,18 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "WindowCovering OperationalStatus state open following lift position update", function() - test.socket.capability:__set_channel_ordering("relaxed") + "Unknown WindowCovering OperationalStatus state triggers 'unknown' capability emission", function() test.socket.matter:__queue_receive( { mock_device.id, - WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, 0 - ), + WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 99), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(100) + "main", capabilities.windowShade.windowShade.unknown() ) ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.open() - ) - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), - } - ) end, { min_api_version = 17 @@ -321,32 +308,18 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "WindowCovering OperationalStatus state open following tilt position update", function() - test.socket.capability:__set_channel_ordering("relaxed") + "Opening WindowCovering OperationalStatus state triggers 'closing' capability emission", function() test.socket.matter:__queue_receive( { mock_device.id, - WindowCovering.attributes.CurrentPositionTiltPercent100ths:build_test_report_data( - mock_device, 10, 0 - ), + WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 2), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(100) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.open() + "main", capabilities.windowShade.windowShade.closing() ) ) - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), - } - ) end, { min_api_version = 17 @@ -354,65 +327,66 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "WindowCovering OperationalStatus state open before lift position event", function() - test.socket.capability:__set_channel_ordering("relaxed") + "Closing WindowCovering OperationalStatus state triggers 'opening' capability emission", function() test.socket.matter:__queue_receive( { mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), + WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 1), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(100) + "main", capabilities.windowShade.windowShade.opening() ) ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.open() - ) - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, 0 - ), - } - ) end, { min_api_version = 17 } ) + test.register_coroutine_test( - "WindowCovering OperationalStatus state open before tilt position event", function() - test.socket.capability:__set_channel_ordering("relaxed") + "WindowCovering CurrentPositionLiftPercent100ths triggers appropriate capability emission", function() test.socket.matter:__queue_receive( { mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 10000 + ), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(100) + "main", capabilities.windowShadeLevel.shadeLevel(0) ) ) + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 0 + ), + } + ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.open() + "main", capabilities.windowShadeLevel.shadeLevel(100) ) ) test.socket.matter:__queue_receive( { mock_device.id, - WindowCovering.attributes.CurrentPositionTiltPercent100ths:build_test_report_data( - mock_device, 10, 0 + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, ((100 - 25) *100) ), } ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeLevel.shadeLevel(25) + ) + ) end, { min_api_version = 17 @@ -420,41 +394,33 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "WindowCovering OperationalStatus partially open following lift position update", function() - test.socket.capability:__set_channel_ordering("relaxed") + "WindowCovering CurrentPositionTiltPercent100ths triggers appropriate capability emission", function() test.socket.matter:__queue_receive( { mock_device.id, - WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, ((100 - 25) *100) + WindowCovering.attributes.CurrentPositionTiltPercent100ths:build_test_report_data( + mock_device, 10, 10000 ), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(25) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() + "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(0) ) ) test.socket.matter:__queue_receive( { mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), + WindowCovering.attributes.CurrentPositionTiltPercent100ths:build_test_report_data( + mock_device, 10, 0 + ), } ) - end, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "WindowCovering OperationalStatus partially open following tilt position update", function() - test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(100) + ) + ) test.socket.matter:__queue_receive( { mock_device.id, @@ -468,17 +434,38 @@ test.register_coroutine_test( "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(15) ) ) + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionTiltPercent100ths:build_test_report_data( + mock_device, 10, ((100 - 65) *100) + ), + } + ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() + "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(65) ) ) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "WindowCovering TargetPositionLiftPercent100ths sets field appropriately", + function() test.socket.matter:__queue_receive( { mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), + WindowCovering.attributes.TargetPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 5000 + ), } ) + test.wait_for_events() + assert(mock_device:get_field("__target_lift_percent") == 50) end, { min_api_version = 17 @@ -486,47 +473,59 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "WindowCovering OperationalStatus partially open before lift position event", function() - test.socket.capability:__set_channel_ordering("relaxed") + "WindowCovering TargetPositionTiltPercent100ths sets field appropriately", + function() test.socket.matter:__queue_receive( { mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), + WindowCovering.attributes.TargetPositionTiltPercent100ths:build_test_report_data( + mock_device, 10, 5000 + ), } ) + test.wait_for_events() + assert(mock_device:get_field("__target_tilt_percent") == 50) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "WindowCovering TargetPositionLiftPercent100ths triggers operational status update when appropriate", + function() test.socket.matter:__queue_receive( { mock_device.id, WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, ((100 - 25) *100) + mock_device, 10, ((100 - 65) *100) ), } ) test.socket.capability:__expect_send( mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(25) + "main", capabilities.windowShadeLevel.shadeLevel(65) ) ) + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.OperationalStatus:build_test_report_data( + mock_device, 10, 0 + ), + } + ) test.socket.capability:__expect_send( mock_device:generate_test_message( "main", capabilities.windowShade.windowShade.partially_open() ) ) - end, - { - min_api_version = 17 - } + end ) test.register_coroutine_test( - "WindowCovering OperationalStatus partially open before tilt position event", function() - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), - } - ) + "WindowCovering TargetPositionTiltPercent100ths triggers operational status update when appropriate", + function() test.socket.matter:__queue_receive( { mock_device.id, @@ -540,6 +539,14 @@ test.register_coroutine_test( "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(65) ) ) + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.OperationalStatus:build_test_report_data( + mock_device, 10, 0 + ), + } + ) test.socket.capability:__expect_send( mock_device:generate_test_message( "main", capabilities.windowShade.windowShade.partially_open() @@ -551,129 +558,51 @@ test.register_coroutine_test( } ) -test.register_coroutine_test("WindowCovering OperationalStatus opening", function() - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, ((100 - 25) *100) - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(25) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 1), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.opening() - ) - ) -end, -{ - min_api_version = 17 -} -) - -test.register_coroutine_test("WindowCovering OperationalStatus closing", function() - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, ((100 - 25) *100) - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(25) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() +test.register_coroutine_test( + "Open capability command triggers appropriate Matter command", function() + test.socket.capability:__queue_receive( + { + mock_device.id, + {capability = "windowShade", component = "main", command = "open", args = {}}, + } ) - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 2), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.closing() + test.socket.matter:__expect_send( + {mock_device.id, WindowCovering.server.commands.UpOrOpen(mock_device, 10)} ) - ) -end, -{ - min_api_version = 17 -} + end, + { + min_api_version = 17 + } ) -test.register_coroutine_test("WindowCovering OperationalStatus unknown", function() - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, ((100 - 25) *100) - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(25) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() +test.register_coroutine_test( + "Close capability command triggers appropriate Matter command", function() + test.socket.capability:__queue_receive( + { + mock_device.id, + {capability = "windowShade", component = "main", command = "close", args = {}}, + } ) - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 3), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.unknown() + test.socket.matter:__expect_send( + {mock_device.id, WindowCovering.server.commands.DownOrClose(mock_device, 10)} ) - ) -end, -{ - min_api_version = 17 -} + end, + { + min_api_version = 17 + } ) test.register_coroutine_test( - "WindowShade open cmd handler", function() + "Pause capability command triggers appropriate Matter command", function() test.socket.capability:__queue_receive( { mock_device.id, - {capability = "windowShade", component = "main", command = "open", args = {}}, + {capability = "windowShade", component = "main", command = "pause", args = {}}, } ) test.socket.matter:__expect_send( - {mock_device.id, WindowCovering.server.commands.UpOrOpen(mock_device, 10)} + {mock_device.id, WindowCovering.server.commands.StopMotion(mock_device, 10)} ) - test.wait_for_events() end, { min_api_version = 17 @@ -681,17 +610,53 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "WindowShade close cmd handler", function() + "Cached target lift/tilt position timeouts trigger one operational status update", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionTiltPercent100ths:build_test_report_data( + mock_device, 10, 50 * 100 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(50) + ) + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 5000 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeLevel.shadeLevel(50) + ) + ) test.socket.capability:__queue_receive( { mock_device.id, - {capability = "windowShade", component = "main", command = "close", args = {}}, + {capability = "windowShade", component = "main", command = "open", args = {}}, } ) + test.timer.__create_and_queue_test_time_advance_timer(120, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(120, "oneshot") test.socket.matter:__expect_send( - {mock_device.id, WindowCovering.server.commands.DownOrClose(mock_device, 10)} + {mock_device.id, WindowCovering.server.commands.UpOrOpen(mock_device, 10)} ) + test.wait_for_events() + test.mock_time.advance_time(120) + + test.socket.capability:__expect_send({ + mock_device.id, { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "partially open" } }, + } + ) end, { min_api_version = 17 @@ -699,17 +664,79 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "WindowShade pause cmd handler", function() + "Cached target lift timeout triggers one operational status update if tilt target and idle state are first reached appropriately", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionTiltPercent100ths:build_test_report_data( + mock_device, 10, 5000 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(50) + ) + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 5000 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeLevel.shadeLevel(50) + ) + ) test.socket.capability:__queue_receive( { mock_device.id, - {capability = "windowShade", component = "main", command = "pause", args = {}}, + {capability = "windowShade", component = "main", command = "open", args = {}}, + } + ) + -- two timers are made when open is called + test.timer.__create_and_queue_test_time_advance_timer(120, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(120, "oneshot") + test.socket.matter:__expect_send( + {mock_device.id, WindowCovering.server.commands.UpOrOpen(mock_device, 10)} + ) + + -- ensure target state is cached before queueing responses + test.wait_for_events() + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionTiltPercent100ths:build_test_report_data( + mock_device, 10, 0 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(100) + ) + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.OperationalStatus:build_test_report_data( + mock_device, 10, 0 + ), } ) - test.socket.matter:__expect_send( - {mock_device.id, WindowCovering.server.commands.StopMotion(mock_device, 10)} - ) + + -- advance time to trigger the single remaining timer test.wait_for_events() + test.mock_time.advance_time(120) + test.socket.capability:__expect_send({ + mock_device.id, { capability_id = "windowShade", component_id = "main", attribute_id = "windowShade", state = { value = "partially open" } }, + } + ) + end, { min_api_version = 17 @@ -748,7 +775,7 @@ test.register_coroutine_test( } ) -test.register_coroutine_test("WindowShade setShadeLevel cmd handler", function() +test.register_coroutine_test("SetShadeLevel capability command triggers appropriate Matter command", function() test.socket.capability:__queue_receive( { mock_device.id, @@ -764,7 +791,7 @@ end, } ) -test.register_coroutine_test("WindowShade setShadeTiltLevel cmd handler", function() +test.register_coroutine_test("SetShadeTiltLevel capability command triggers appropriate Matter command", function() test.socket.capability:__queue_receive( { mock_device.id, @@ -780,24 +807,6 @@ end, } ) -test.register_coroutine_test("LevelControl CurrentLevel handler", function() - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.LevelControl.attributes.CurrentLevel:build_test_report_data(mock_device, 10, 100), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(math.floor((100 / 254.0 * 100) + .5)) - ) - ) -end, -{ - min_api_version = 17 -} -) - --test battery test.register_coroutine_test( "Battery percent reports should generate correct messages", function() @@ -820,29 +829,6 @@ test.register_coroutine_test( } ) -test.register_coroutine_test("OperationalStatus report contains current position report", function() - test.socket.capability:__set_channel_ordering("relaxed") - local report = WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, ((100 - 25) *100) - ) - table.insert(report.info_blocks, WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0).info_blocks[1]) - test.socket.matter:__queue_receive({ mock_device.id, report}) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(25) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) -end, -{ - min_api_version = 17 -} -) - test.register_coroutine_test( "Handle preset commands", function() @@ -911,9 +897,9 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "InfoChanged event checks for new profile match if device has changed (i.e. through reinterview or SW update)", + "InfoChanged event updates new profile if sw updated has occurred", function() - test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({})) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ matter_version = {software = 2} })) local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read() test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) test.wait_for_events() @@ -925,198 +911,10 @@ test.register_coroutine_test( } ) -test.register_coroutine_test( - "WindowCovering shade level adjusted by greater than 2%; status reflects Closing followed by Partially Open", function() - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), - } - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, ((100 - 25) *100) - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(25) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) - test.wait_for_events() - test.socket.capability:__queue_receive( - { - mock_device.id, - {capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 19 }}, - } - ) - test.socket.matter:__expect_send( - {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 8100)} - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 10), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.closing() - ) - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, ((100 - 23) *100) - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(23) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, ((100 - 21) *100) - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(21) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, ((100 - 19) *100) - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(19) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) - end, - { - min_api_version = 17 - } -) - -test.register_coroutine_test( - "WindowCovering shade level adjusted by less than or equal to 2%; status reflects Closing followed by Partially Open", function() - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), - } - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, ((100 - 25) *100) - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(25) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) - test.wait_for_events() - test.socket.capability:__queue_receive( - { - mock_device.id, - {capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 23 }}, - } - ) - test.socket.matter:__expect_send( - {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 7700)} - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 10), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.closing() - ) - ) - test.wait_for_events() - test.socket.matter:__queue_receive( - { - mock_device.id, - WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( - mock_device, 10, ((100 - 23) *100) - ), - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShadeLevel.shadeLevel(23) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) - end, - { - min_api_version = 17 - } -) - test.register_coroutine_test( "Check that preference updates to reverse polarity after being set to true and that the shade lift operates as expected when opening and closing", function() test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({ preferences = { reverse = "true" } })) test.wait_for_events() - local reverse_preference_set = mock_device:get_field("__reverse_polarity") - assert(reverse_preference_set == true, "reverse_preference_set is True") test.socket.matter:__queue_receive({ mock_device.id, WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( @@ -1128,11 +926,19 @@ test.register_coroutine_test( "main", capabilities.windowShadeLevel.shadeLevel(0) ) ) + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + WindowCovering.attributes.OperationalStatus:build_test_report_data( + mock_device, 10, 0 + ) + }) test.socket.capability:__expect_send( mock_device:generate_test_message( "main", capabilities.windowShade.windowShade.open() ) ) + test.wait_for_events() test.socket.matter:__queue_receive({ mock_device.id, WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( @@ -1144,11 +950,19 @@ test.register_coroutine_test( "main", capabilities.windowShadeLevel.shadeLevel(100) ) ) + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + WindowCovering.attributes.OperationalStatus:build_test_report_data( + mock_device, 10, 0 + ) + }) test.socket.capability:__expect_send( mock_device:generate_test_message( "main", capabilities.windowShade.windowShade.closed() ) ) + test.wait_for_events() test.socket.capability:__queue_receive({ mock_device.id, {capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 85 }}, @@ -1173,8 +987,6 @@ test.register_coroutine_test( "Check that preference updates to reverse polarity after being set to true and that the shade tilt operates as expected when opening and closing", function() test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({ preferences = { reverse = "true" } })) test.wait_for_events() - local reverse_preference_set = mock_device:get_field("__reverse_polarity") - assert(reverse_preference_set == true, "reverse_preference_set is True") test.socket.matter:__queue_receive({ mock_device.id, WindowCovering.attributes.CurrentPositionTiltPercent100ths:build_test_report_data( @@ -1186,6 +998,12 @@ test.register_coroutine_test( "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(0) ) ) + test.socket.matter:__queue_receive({ + mock_device.id, + WindowCovering.attributes.OperationalStatus:build_test_report_data( + mock_device, 10, 0 + ) + }) test.socket.capability:__expect_send( mock_device:generate_test_message( "main", capabilities.windowShade.windowShade.open() @@ -1197,6 +1015,12 @@ test.register_coroutine_test( mock_device, 10, 0 ) }) + test.socket.matter:__queue_receive({ + mock_device.id, + WindowCovering.attributes.OperationalStatus:build_test_report_data( + mock_device, 10, 0 + ) + }) test.socket.capability:__expect_send( mock_device:generate_test_message( "main", capabilities.windowShadeTiltLevel.shadeTiltLevel(100) @@ -1207,6 +1031,7 @@ test.register_coroutine_test( "main", capabilities.windowShade.windowShade.closed() ) ) + test.wait_for_events() test.socket.capability:__queue_receive({ mock_device.id, {capability = "windowShadeTiltLevel", component = "main", command = "setShadeTiltLevel", args = { 15 }}, @@ -1261,11 +1086,6 @@ test.register_coroutine_test( "main", capabilities.windowShadeLevel.shadeLevel(50) ) ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) test.wait_for_events() test.socket.capability:__queue_receive( { @@ -1297,11 +1117,6 @@ test.register_coroutine_test( "main", capabilities.windowShadeLevel.shadeLevel(30) ) ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) test.wait_for_events() test.socket.capability:__queue_receive( { @@ -1343,11 +1158,6 @@ test.register_coroutine_test( "main", capabilities.windowShadeLevel.shadeLevel(50) ) ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) test.wait_for_events() test.socket.capability:__queue_receive( { @@ -1398,11 +1208,6 @@ test.register_coroutine_test( "main", capabilities.windowShadeLevel.shadeLevel(95) ) ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) test.wait_for_events() test.socket.capability:__queue_receive( { @@ -1434,11 +1239,6 @@ test.register_coroutine_test( "main", capabilities.windowShadeLevel.shadeLevel(5) ) ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.windowShade.windowShade.partially_open() - ) - ) test.wait_for_events() test.socket.capability:__queue_receive( { From 010ab9a4aa7fd8776e37eaffb268e17b176fa970 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:53:03 -0500 Subject: [PATCH 484/538] GHA: Check for category existence on the main component (#3136) --- .github/scripts/check_duplicates.py | 89 +++++++++---------- .github/scripts/check_profile_categories.py | 82 +++++++++++++++++ .../check-profile-categories-comment.yml | 34 +++++++ .../workflows/check-profile-categories.yml | 41 +++++++++ .github/workflows/duplicate-profiles.yml | 17 ++-- 5 files changed, 205 insertions(+), 58 deletions(-) create mode 100644 .github/scripts/check_profile_categories.py create mode 100644 .github/workflows/check-profile-categories-comment.yml create mode 100644 .github/workflows/check-profile-categories.yml diff --git a/.github/scripts/check_duplicates.py b/.github/scripts/check_duplicates.py index 65e2ae2ea6..e335f6083a 100644 --- a/.github/scripts/check_duplicates.py +++ b/.github/scripts/check_duplicates.py @@ -1,4 +1,3 @@ -import csv import os import yaml from pathlib import Path @@ -101,51 +100,49 @@ def compare_components(prof1, prof2): return True -with open(str(Path.home()) + '/files.csv', 'r') as csvfile: - csvreader = csv.reader(csvfile) - changed_files = next(csvreader) - - for file in changed_files: - file_basename = os.path.basename(file) - file_directory = os.path.dirname(file) - - if '/profiles/' in file: - print('\nNEW PROFILE:\n%s is a profile! Comparing to other profiles...' % file) - - os.chdir(file_directory) - new_profile = file_basename - - # Skip deleted files and track them for warning - if not os.path.exists(new_profile): - print("Skipping %s - file was deleted" % new_profile) - deleted_profiles.append(file) - os.chdir(cwd) - continue - - for current_profile in os.listdir("./"): - # compare to YAML files that are not the same file - # Compare only .yml files and only files that have not already been found to be a duplicate - if current_profile != new_profile and Path(current_profile).suffix == ".yml" and (current_profile, new_profile) not in duplicate_pairs: - print("Comparing %s vs %s" % (new_profile, current_profile)) - with open(new_profile) as new_data, open(current_profile) as current_data: - new_profile_map = yaml.safe_load(new_data) - current_profile_map = yaml.safe_load(current_data) - - ''' Compare profiles. A duplicate is defined as follows: - - categories must be the same - - capabilities must be the same, with some ordering restrictions - - top capability must match, but subsequent ordering does not matter - - embedded configs must be the same, but certain values can be ordered differently (i.e. enabledValues) - - preferences must be the same - ''' - if(compare_preferences(new_profile_map, current_profile_map) == True and - compare_metadata(new_profile_map, current_profile_map) == True and - compare_components(new_profile_map, current_profile_map) == True): - print("%s and %s are duplicates!\n" % (new_profile, current_profile)) - duplicate_pairs.append((new_profile, current_profile)) - - # return to original directory - os.chdir(cwd) +changed_files = os.environ.get('ALL_CHANGED_FILES', '').split() + +for file in changed_files: + file_basename = os.path.basename(file) + file_directory = os.path.dirname(file) + + if '/profiles/' in file: + print('\nNEW PROFILE:\n%s is a profile! Comparing to other profiles...' % file) + + os.chdir(file_directory) + new_profile = file_basename + + # Skip deleted files and track them for warning + if not os.path.exists(new_profile): + print("Skipping %s - file was deleted" % new_profile) + deleted_profiles.append(file) + os.chdir(cwd) + continue + + for current_profile in os.listdir("./"): + # compare to YAML files that are not the same file + # Compare only .yml files and only files that have not already been found to be a duplicate + if current_profile != new_profile and Path(current_profile).suffix == ".yml" and (current_profile, new_profile) not in duplicate_pairs: + print("Comparing %s vs %s" % (new_profile, current_profile)) + with open(new_profile) as new_data, open(current_profile) as current_data: + new_profile_map = yaml.safe_load(new_data) + current_profile_map = yaml.safe_load(current_data) + + ''' Compare profiles. A duplicate is defined as follows: + - categories must be the same + - capabilities must be the same, with some ordering restrictions + - top capability must match, but subsequent ordering does not matter + - embedded configs must be the same, but certain values can be ordered differently (i.e. enabledValues) + - preferences must be the same + ''' + if(compare_preferences(new_profile_map, current_profile_map) == True and + compare_metadata(new_profile_map, current_profile_map) == True and + compare_components(new_profile_map, current_profile_map) == True): + print("%s and %s are duplicates!\n" % (new_profile, current_profile)) + duplicate_pairs.append((new_profile, current_profile)) + + # return to original directory + os.chdir(cwd) with open("profile-comment-body.md", "w") as f: if duplicate_pairs: diff --git a/.github/scripts/check_profile_categories.py b/.github/scripts/check_profile_categories.py new file mode 100644 index 0000000000..31609cbe7e --- /dev/null +++ b/.github/scripts/check_profile_categories.py @@ -0,0 +1,82 @@ +import os +import sys +import yaml +from pathlib import Path + +cwd = os.getcwd() +missing_category_profiles = [] +deleted_profiles = [] + +changed_files = os.environ.get('ALL_CHANGED_FILES', '').split() + +for file in changed_files: + file_basename = os.path.basename(file) + file_directory = os.path.dirname(file) + + if '/profiles/' in file and file.endswith('.yml'): + print('\nCHECKING PROFILE:\n%s' % file) + + os.chdir(file_directory) + + if not os.path.exists(file_basename): + print("Skipping %s - file was deleted" % file_basename) + deleted_profiles.append(file) + os.chdir(cwd) + continue + + with open(file_basename) as fp: + try: + profile = yaml.safe_load(fp) + except yaml.YAMLError as e: + print("Error parsing %s: %s" % (file_basename, e)) + os.chdir(cwd) + continue + + if not profile or 'components' not in profile: + print("Skipping %s - no components found" % file_basename) + os.chdir(cwd) + continue + + # Find the main component and verify it has a categories field + main_component = next( + (c for c in profile['components'] if c.get('id') == 'main'), + None + ) + + if main_component is None: + print("Warning: %s has no 'main' component" % file_basename) + os.chdir(cwd) + continue + + if not main_component.get('categories'): + print("MISSING CATEGORY: %s" % file) + missing_category_profiles.append(file) + else: + print("OK: %s has categories: %s" % ( + file_basename, + [c['name'] for c in main_component['categories']] + )) + + os.chdir(cwd) + +with open("profile-categories-comment-body.md", "w") as f: + if missing_category_profiles: + f.write("Profile category check: :x: **Missing categories detected.**\n\n") + f.write("The following profiles are missing a `categories` field on the `main` component:\n\n") + for profile in missing_category_profiles: + f.write("- `%s`\n" % profile) + f.write("\nPlease add a `categories` entry to the `main` component. Example:\n") + f.write("```yaml\ncomponents:\n - id: main\n categories:\n - name: Switch\n capabilities:\n ...\n```\n") + else: + f.write("Profile category check: :white_check_mark: Passed - all profiles have a category defined.\n") + + if deleted_profiles: + f.write("\n:warning: **Deleted profile files detected:**\n") + for deleted in deleted_profiles: + f.write("- `%s`\n" % deleted) + +with open("profile-categories-comment-body.md", "r") as f: + print("\n" + f.read()) + +if missing_category_profiles: + sys.exit(1) diff --git a/.github/workflows/check-profile-categories-comment.yml b/.github/workflows/check-profile-categories-comment.yml new file mode 100644 index 0000000000..cf207144ef --- /dev/null +++ b/.github/workflows/check-profile-categories-comment.yml @@ -0,0 +1,34 @@ +name: Post profile categories comment +on: + workflow_run: + workflows: [Check profile categories] + types: + - completed + +jobs: + comment-on-pr: + runs-on: ubuntu-latest + steps: + - name: Download comment artifact + uses: dawidd6/action-download-artifact@v6 + with: + workflow: check-profile-categories.yml + run_id: ${{ github.event.workflow_run.id }} + + - run: echo "pr_number=$(cat pr_number/pr_number.txt)" >> $GITHUB_ENV + + - name: Find comment + uses: peter-evans/find-comment@v2 + id: fc + with: + body-includes: Profile category check + comment-author: 'github-actions[bot]' + issue-number: ${{ env.pr_number }} + + - name: Post comment + uses: peter-evans/create-or-update-comment@v2 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + body-file: 'profile_categories_comment/profile-categories-comment-body.md' + edit-mode: replace + issue-number: ${{ env.pr_number }} diff --git a/.github/workflows/check-profile-categories.yml b/.github/workflows/check-profile-categories.yml new file mode 100644 index 0000000000..6340175810 --- /dev/null +++ b/.github/workflows/check-profile-categories.yml @@ -0,0 +1,41 @@ +name: Check profile categories +on: + pull_request: + types: [opened, synchronize] + paths: + - 'drivers/**/profiles/*.yml' + +jobs: + check-profile-categories: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Gather file changes + id: changed-files + uses: tj-actions/changed-files@v47 + + - name: Run python script + env: + ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} + run: | + python ./.github/scripts/check_profile_categories.py + + - name: Upload profile categories comment artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: profile_categories_comment + path: | + profile-categories-comment-body.md + + - run: echo ${{ github.event.number }} > pr_number.txt + + - name: Upload PR info + if: always() + uses: actions/upload-artifact@v4 + with: + name: pr_number + path: | + pr_number.txt diff --git a/.github/workflows/duplicate-profiles.yml b/.github/workflows/duplicate-profiles.yml index ae524cc1c2..5f66269cc1 100644 --- a/.github/workflows/duplicate-profiles.yml +++ b/.github/workflows/duplicate-profiles.yml @@ -13,20 +13,13 @@ jobs: - name: Checkout uses: actions/checkout@v3 - # Creates file "$/files.csv", among others - - id: file_changes - name: Gather file changes - uses: trilom/file-changes-action@1.2.4 - with: - output: ',' - fileOutput: ',' - - # For verification - - name: Show files changed - run: | - cat $HOME/files.csv + - name: Gather file changes + id: changed-files + uses: tj-actions/changed-files@v47 - name: Run python script + env: + ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} run: | python ./.github/scripts/check_duplicates.py From fdd699a5b32dfea93587b998868ff34887d1bfdb Mon Sep 17 00:00:00 2001 From: SiHAS <71238736+shinasys@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:50:06 +0900 Subject: [PATCH 485/538] Add shinasystem's SiHAS Smoke Detector for wwst (#3134) * Add shinasystem's SiHAS Smoke Detector for wwst * Remove unused variable capabilities * update year --- .../zigbee-smoke-detector/fingerprints.yml | 5 +++ .../src/shinasystem/can_handle.lua | 11 ++++++ .../src/shinasystem/init.lua | 34 +++++++++++++++++++ .../zigbee-smoke-detector/src/sub_drivers.lua | 1 + 4 files changed, 51 insertions(+) create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/shinasystem/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/shinasystem/init.lua diff --git a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml index c1f859037b..6fce8d0d3a 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml +++ b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml @@ -59,3 +59,8 @@ zigbeeManufacturer: manufacturer: MultIR model: MIR-SM200 deviceProfileName: smoke-battery-tamper-no-fw-update + - id: ShinaSystem/FAM-300Z + deviceLabel: SiHAS Smoke Detector + manufacturer: ShinaSystem + model: FAM-300Z + deviceProfileName: smoke-battery diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/shinasystem/can_handle.lua b/drivers/SmartThings/zigbee-smoke-detector/src/shinasystem/can_handle.lua new file mode 100644 index 0000000000..c13aebce6a --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/shinasystem/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function shinasystem_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "ShinaSystem" and (device:get_model() == "FAM-300Z") then + return true, require("shinasystem") + end + return false +end + +return shinasystem_can_handle diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/shinasystem/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/shinasystem/init.lua new file mode 100644 index 0000000000..19e59e4aaf --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/shinasystem/init.lua @@ -0,0 +1,34 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local zcl_clusters = require "st.zigbee.zcl.clusters" +local IASZone = zcl_clusters.IASZone + +local CONFIGURATIONS = { + { + cluster = IASZone.ID, + attribute = IASZone.attributes.ZoneStatus.ID, + minimum_interval = 1, + maximum_interval = 1200, -- Zigbee poll interval is 600s. Added because the default reporting maximum_interval (180s) must be greater than 600s. + data_type = IASZone.attributes.ZoneStatus.base_type, + reportable_change = 1 + } +} + +local function device_init(driver, device) + if CONFIGURATIONS ~= nil then + for _, attribute in ipairs(CONFIGURATIONS) do + device:add_configured_attribute(attribute) + end + end +end + +local shinasystem_smoke_sensor = { + NAME = "shinasystem smoke sensor", + lifecycle_handlers = { + init = device_init + }, + can_handle = require("shinasystem.can_handle"), +} +return shinasystem_smoke_sensor diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua b/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua index 2b917b85dc..2f7a4b29ff 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/sub_drivers.lua @@ -7,5 +7,6 @@ local sub_drivers = { lazy_load_if_possible("aqara-gas"), lazy_load_if_possible("aqara"), lazy_load_if_possible("MultiIR"), + lazy_load_if_possible("shinasystem"), } return sub_drivers From 4026115e52a2ceb27b03dde8c31fe3e14480cb00 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Fri, 24 Jul 2026 14:47:12 -0500 Subject: [PATCH 486/538] Branching registryCredentialsId and registryURL based off region and nodeLabel --- Jenkinsfile | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 425da986db..a7a143ae64 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,11 +24,42 @@ def getChangedDrivers() { return drivers } +def get_region() { + def uri = new URI(env.JENKINS_URL) + def region = uri.host.endsWith('.cn') ? 'cn' : 'global' + return region +} + +// Gate artifactory-credentials for prod nodes in cn due to a docker authentication error arising in that environment +def getDockerCredentialId() { + def nodeLabel = params.NODE_LABEL ?: 'production' + def region = get_region() + if (nodeLabel == 'production' && region == 'cn') { + return 'artifactory-credentials' + } + else { + return 'artifactory' + } +} +// Gate RegistryUrl for prod nodes in cn due to a docker authentication error arising in that environment +def getRegistryUrl() { + def nodeLabel = params.NODE_LABEL ?: 'production' + def region = get_region() + if (nodeLabel == 'production' && region == 'cn') { + return 'https://registry.artifactoryedge.streleng.cn' + } else { + // Default + return '' + } +} + pipeline { agent { docker { image 'python:3.10' label "${params.NODE_LABEL ?: 'production'}" + registryUrl getRegistryUrl() + registryCredentialsId getDockerCredentialId() args '--entrypoint= -u 0:0' } } From 3083caa47e7741b04eaa44ce2050845c4b866c37 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 29 Jul 2026 11:24:20 -0500 Subject: [PATCH 487/538] Avoiding URI permissions error and defaulted credentials ID string to empty --- Jenkinsfile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a7a143ae64..752d3d358c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -25,8 +25,12 @@ def getChangedDrivers() { } def get_region() { - def uri = new URI(env.JENKINS_URL) - def region = uri.host.endsWith('.cn') ? 'cn' : 'global' + def url = env.JENKINS_URL?.trim() + if (url?.endsWith('/')) { + url = url[0..-2] + } + + def region = url?.endsWith('.cn') ? 'cn' : 'global' return region } @@ -38,7 +42,7 @@ def getDockerCredentialId() { return 'artifactory-credentials' } else { - return 'artifactory' + return '' } } // Gate RegistryUrl for prod nodes in cn due to a docker authentication error arising in that environment From eb505a9268c1835a5c9d8a378247c6ba5096a52b Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 30 Jul 2026 12:44:29 -0500 Subject: [PATCH 488/538] WWSTCERT-12681 Lockly Smart Lock (#3114) * WWSTCERT-12681 Lockly Smart Look * Update fingerprints.yml * Update fingerprints.yml --- drivers/SmartThings/matter-lock/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 75f2fcf37d..7eb674bce9 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -197,6 +197,12 @@ matterManufacturer: vendorId: 0x152C productId: 0x9501 deviceProfileName: lock + #Lockly + - id: "5212/2" + deviceLabel: Lockly Smart Lock + vendorId: 0x145C + productId: 0x0002 + deviceProfileName: lock-modular matterGeneric: - id: "matter/door-lock" deviceLabel: Matter Door Lock From 2bd5faa6f70203898709d5c323730121e41a2637 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:04:51 -0500 Subject: [PATCH 489/538] Matter Switch: Fix Electrical profiling coroutine errors and improve fallback handling (#3150) --- .../types/EnergyMeasurementStruct.lua | 24 +++++++++++++++---- .../SmartThings/matter-switch/src/init.lua | 5 ++++ .../switch_handlers/attribute_handlers.lua | 5 ++++ .../src/switch_utils/device_configuration.lua | 9 ++++++- .../matter-switch/src/switch_utils/utils.lua | 20 ++++++++++++---- .../src/test/test_aqara_light_switch_h2.lua | 5 ++++ .../src/test/test_electrical_sensor_set.lua | 5 ++++ .../src/test/test_electrical_sensor_tree.lua | 5 ++++ 8 files changed, 68 insertions(+), 10 deletions(-) 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 a4c58a3646..5452b287ac 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 @@ -42,6 +42,20 @@ EnergyMeasurementStruct.field_defs = { is_optional = true, data_type = require "st.matter.data_types.Uint64", }, + { + name = "apparent_energy", + field_id = 5, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Int64", + }, + { + name = "reactive_energy", + field_id = 6, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Int64", + }, } EnergyMeasurementStruct.init = function(cls, tbl) @@ -49,10 +63,10 @@ EnergyMeasurementStruct.init = function(cls, tbl) o.elements = {} o.num_elements = 0 setmetatable(o, new_mt) - for idx, field_def in ipairs(cls.field_defs) do + for _idx, field_def in ipairs(cls.field_defs) do if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then error("Missing non optional or non_nullable field: " .. field_def.name) - else + elseif not (field_def.is_optional and tbl[field_def.name] == nil) then o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) o.elements[field_def.name].field_id = field_def.field_id o.num_elements = o.num_elements + 1 @@ -71,7 +85,7 @@ new_mt.__index.serialize = EnergyMeasurementStruct.serialize EnergyMeasurementStruct.augment_type = function(self, val) local elems = {} local num_elements = 0 - for _, v in pairs(val.elements) do + for _, v in pairs(val.elements or {}) do for _, field_def in ipairs(self.field_defs) do if field_def.field_id == v.field_id and field_def.is_nullable and @@ -79,11 +93,11 @@ EnergyMeasurementStruct.augment_type = function(self, val) elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) num_elements = num_elements + 1 elseif field_def.field_id == v.field_id and not - (field_def.is_optional and v.value == nil) then + (field_def.is_optional and v.value == nil and v.elements == nil) then elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) num_elements = num_elements + 1 if field_def.element_type ~= nil then - for i, e in ipairs(elems[field_def.name].elements) do + for i, e in ipairs(elems[field_def.name].elements or {}) do elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) end end diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 9d4e22fb15..adebbb0ec1 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -29,6 +29,11 @@ if version.api < 16 then clusters.Descriptor = require "embedded_clusters.Descriptor" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +end + local SwitchLifecycleHandlers = {} function SwitchLifecycleHandlers.device_added(driver, device) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index b7feed6344..753c76992e 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -18,6 +18,11 @@ if version.api < 11 then clusters.PowerTopology = require "embedded_clusters.PowerTopology" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +end + local AttributeHandlers = {} -- [[ ON OFF CLUSTER ATTRIBUTES ]] -- diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 9c5235d1e2..130fd1c1e4 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -15,6 +15,11 @@ if version.api < 11 then clusters.ValveConfigurationAndControl = require "embedded_clusters.ValveConfigurationAndControl" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +end + local DeviceConfiguration = {} local ChildConfiguration = {} local SwitchDeviceConfiguration = {} @@ -87,7 +92,9 @@ function SwitchDeviceConfiguration.assign_profile_for_onoff_ep(device, server_on 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 + if type(static_electrical_tags) == "string" then + -- if no associated profile is found for the device type and static electrical tags are available, use "plug-binary" as a fallback + generic_profile = generic_profile or "plug-binary" -- 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 diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index d2457bfb4d..1d12f41781 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -21,6 +21,11 @@ if version.api < 16 then clusters.Descriptor = require "embedded_clusters.Descriptor" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +end + local utils = {} function utils.tbl_contains(array, value) @@ -545,16 +550,14 @@ function utils.subscribe(device) 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 the type of battery support has not yet been determined, add the PowerSource AttributeList to the list of -- subscribed attributes in order to determine which if any battery capability should be used. if device:get_field(fields.profiling_data.BATTERY_SUPPORT) == nil then local ib = im.InteractionInfoBlock(nil, clusters.PowerSource.ID, clusters.PowerSource.attributes.AttributeList.ID) subscribe_request:with_info_block(ib) + attributes_seen[clusters.PowerSource.ID] = attributes_seen[clusters.PowerSource.ID] or {} + attributes_seen[clusters.PowerSource.ID][clusters.PowerSource.attributes.AttributeList.ID] = ib end -- If the power topology of the device has not yet been determined, add the AvailableEndpoints (for SET topology) @@ -566,12 +569,21 @@ function utils.subscribe(device) if clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.SET_TOPOLOGY, endpoint_power_topology_cluster.feature_map or 0) then local ib = im.InteractionInfoBlock(nil, clusters.PowerTopology.ID, clusters.PowerTopology.attributes.AvailableEndpoints.ID) subscribe_request:with_info_block(ib) + attributes_seen[clusters.PowerTopology.ID] = attributes_seen[clusters.PowerTopology.ID] or {} + attributes_seen[clusters.PowerTopology.ID][clusters.PowerTopology.attributes.AvailableEndpoints.ID] = ib elseif clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.TREE_TOPOLOGY, endpoint_power_topology_cluster.feature_map or 0) then local ib = im.InteractionInfoBlock(nil, clusters.Descriptor.ID, clusters.Descriptor.attributes.PartsList.ID) subscribe_request:with_info_block(ib) + attributes_seen[clusters.Descriptor.ID] = attributes_seen[clusters.Descriptor.ID] or {} + attributes_seen[clusters.Descriptor.ID][clusters.Descriptor.attributes.PartsList.ID] = ib 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 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 f763cc769f..468232cbc3 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 @@ -15,6 +15,11 @@ if version.api < 11 then clusters.PowerTopology = require "embedded_clusters.PowerTopology" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +end + local aqara_parent_ep = 4 local aqara_child1_ep = 1 local aqara_child2_ep = 2 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 index dae6b09a6c..18c44f7b9c 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -15,6 +15,11 @@ if version.api < 11 then clusters.PowerTopology = require "embedded_clusters.PowerTopology" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +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 = { diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua index d691f2af27..2abfe6e9c5 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua @@ -19,6 +19,11 @@ if version.api < 16 then clusters.Descriptor = require "embedded_clusters.Descriptor" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +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 = { From 1451f18aaf00bc74f1c013260185a1f24f10c498 Mon Sep 17 00:00:00 2001 From: Zach Varberg Date: Wed, 29 Jul 2026 10:35:07 -0500 Subject: [PATCH 490/538] Grab latest capability definitions from API for tests --- .github/workflows/run-tests.yml | 24 ++ tools/fetch_capability_definitions.py | 583 ++++++++++++++++++++++++++ tools/run_driver_tests.py | 8 +- tools/run_driver_tests_p.py | 8 +- 4 files changed, 619 insertions(+), 4 deletions(-) create mode 100644 tools/fetch_capability_definitions.py diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index f6a03154cb..de88fc02d1 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -4,6 +4,8 @@ on: types: [opened, synchronize, labeled, unlabeled] paths: - 'drivers/**' + - 'tools/run_driver_tests.py' + - 'tools/run_driver_tests_p.py' jobs: # Two separate jobs for finding the right artifact to run tests with @@ -91,11 +93,33 @@ jobs: - run: echo ${{ steps.changed-drivers.outputs.all_modified_files }} - name: Install Python requirements run: pip install -r tools/requirements.txt + - name: Fetch capability definitions + continue-on-error: true + run: | + python3 tools/fetch_capability_definitions.py \ + --drivers-dir ${{ github.workspace }}/drivers \ + --output-dir ${{ github.workspace }}/capability_json \ + --failed-output-file ${{ github.workspace }}/capability_failures_comment.md + env: + CAPABILITY_PAT: ${{ secrets.CAPABILITY_DEFINITIONS_PAT }} + - name: Report capability download failures + if: hashFiles('capability_failures_comment.md') != '' && always() + uses: marocchino/sticky-pull-request-comment@v2 + with: + path: ${{ github.workspace }}/capability_failures_comment.md + header: capability-download-failures + - name: Clear capability download failures comment + if: hashFiles('capability_failures_comment.md') == '' && always() + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: capability-download-failures + delete: true - name: Run the tests id: run-tests run: python tools/run_driver_tests_p.py ${{ steps.changed-drivers.outputs.all_modified_files }} env: LUA_PATH: ${{ steps.lua_path.outputs.lua_path }} + ST_CAPABILITY_JSON_DIR: ${{ github.workspace }}/capability_json - name: Upload test artifact if: always() uses: actions/upload-artifact@v4 diff --git a/tools/fetch_capability_definitions.py b/tools/fetch_capability_definitions.py new file mode 100644 index 0000000000..45ef47b9cd --- /dev/null +++ b/tools/fetch_capability_definitions.py @@ -0,0 +1,583 @@ +#!/usr/bin/env python3 +# 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. + +"""fetch_capability_definitions.py + +Scans driver profile YAML files to discover all capability IDs and versions in +use, then downloads their fully-inlined JSON definitions from the SmartThings +cloud API and writes each to a separate file in the output directory. + +The downloaded JSON files are pre-inlined by the cloud (no $ref strings remain) +and can be used directly by the integration test framework's capability_json_loader +module via the ST_CAPABILITY_JSON_DIR environment variable. + +Capability definitions are fetched individually via GET /capabilities// +by default, which ensures that a failure for one capability does not affect others. +A known allowlist of capabilities (QUERY_ENDPOINT_IDS) that are confirmed to work +with the bulk POST /capabilities/query endpoint are fetched in a single batch +request instead, for efficiency. + +Usage +----- + python3 tools/fetch_capability_definitions.py \\ + --drivers-dir drivers/ \\ + --drivers-dir ~/projects/SmartThingsEdgeDrivers/drivers \\ + --output-dir ~/cap_cache \\ + [--overwrite] \\ + [--failed-output-file failures_comment.md] + +Arguments +--------- + --drivers-dir PATH Root of a driver tree to scan for profiles. + May be specified multiple times. + --output-dir PATH Directory to write _.json files. + Created if it does not exist. + --overwrite When set, re-download and overwrite all capability + files even if they already exist in --output-dir. + When absent (default), only missing files are fetched; + capabilities already on disk are skipped entirely, + and no API request is made for them. + --failed-output-file PATH When set, write a markdown-formatted report of any + capabilities that could not be downloaded to this file. + The file is only created when there are failures, making + it suitable for use as a PR comment via hashFiles() checks + in CI workflows. + +Environment +----------- + CAPABILITY_PAT Required. SmartThings personal access token used for + the Authorization: Bearer header on all API requests. +""" + +import argparse +import json +import os +import sys +import time +from pathlib import Path +from typing import Dict, List, Set, Tuple + +import requests +import yaml + +API_BASE = "https://api.smartthings.com/v1" +QUERY_ENDPOINT = f"{API_BASE}/capabilities/query" + +# These two IDs appear in the /capabilities list but are rejected by both the +# /capabilities/query endpoint and the individual GET endpoint. They are +# lowercase/deprecated duplicates of alarmSensor and samsungTV respectively. +EXCLUDED_IDS = frozenset(["alarmsensor", "samsungTv"]) + +# These capability IDs are confirmed to work with the bulk POST /capabilities/query +# endpoint and will be fetched in a single batch request for efficiency. All other +# capability IDs (e.g. namespaced capabilities such as stse.*, sec.*, samsungim.*) +# are fetched individually via GET /capabilities// by default. This +# ensures that a new capability added to a profile that is not supported by the +# query endpoint will only cause an isolated failure rather than affecting all +# capabilities in the batch. +QUERY_ENDPOINT_IDS = frozenset([ + "accelerationSensor", + "activitySensor", + "airConditionerFanMode", + "airConditionerMode", + "airPurifierFanMode", + "airQualityHealthConcern", + "airQualitySensor", + "alarm", + "atmosphericPressureMeasurement", + "audioMute", + "audioNotification", + "audioRecording", + "audioTrackData", + "audioVolume", + "battery", + "batteryLevel", + "bodyWeightMeasurement", + "bridge", + "button", + "cameraPrivacyMode", + "cameraViewportSettings", + "carbonDioxideHealthConcern", + "carbonDioxideMeasurement", + "carbonMonoxideDetector", + "carbonMonoxideHealthConcern", + "carbonMonoxideMeasurement", + "chargingState", + "chime", + "colorControl", + "colorTemperature", + "configuration", + "contactSensor", + "cookTime", + "currentMeasurement", + "dewPoint", + "doorControl", + "dustHealthConcern", + "dustSensor", + "elevatorCall", + "energyMeter", + "evseChargingSession", + "evseState", + "fanMode", + "fanOscillationMode", + "fanSpeed", + "fanSpeedPercent", + "feederOperatingState", + "feederPortion", + "filterState", + "filterStatus", + "fineDustHealthConcern", + "fineDustSensor", + "firmwareUpdate", + "flowMeasurement", + "formaldehydeHealthConcern", + "formaldehydeMeasurement", + "gasDetector", + "gasMeter", + "hardwareFault", + "hdr", + "illuminanceMeasurement", + "imageCapture", + "imageControl", + "keypadInput", + "knob", + "laundryWasherRinseMode", + "laundryWasherSpinSpeed", + "level", + "localMediaStorage", + "lock", + "lockAlarm", + "lockAliro", + "lockCodes", + "lockCredentials", + "lockSchedules", + "lockUsers", + "mechanicalPanTiltZoom", + "mediaGroup", + "mediaPlayback", + "mediaPresets", + "mediaTrackControl", + "mode", + "moldHealthConcern", + "momentary", + "motionSensor", + "movementSensor", + "multipleZonePresence", + "nightVision", + "nitrogenDioxideHealthConcern", + "nitrogenDioxideMeasurement", + "operationalState", + "ozoneHealthConcern", + "ozoneMeasurement", + "panicAlarm", + "pestControl", + "powerConsumptionReport", + "powerMeter", + "powerSource", + "presenceSensor", + "pumpControlMode", + "pumpOperationMode", + "radonHealthConcern", + "radonMeasurement", + "rainSensor", + "refresh", + "relativeHumidityMeasurement", + "remoteControlStatus", + "robotCleanerOperatingState", + "safetyValve", + "serviceArea", + "signalStrength", + "smokeDetector", + "soundSensor", + "sounds", + "statelessColorTemperatureStep", + "statelessCurtainPowerButton", + "statelessSwitchLevelStep", + "switch", + "switchLevel", + "tamperAlert", + "temperatureAlarm", + "temperatureLevel", + "temperatureMeasurement", + "temperatureSetpoint", + "thermostatCoolingSetpoint", + "thermostatFanMode", + "thermostatHeatingSetpoint", + "thermostatMode", + "thermostatOperatingState", + "threadBorderRouter", + "threadNetwork", + "threeAxis", + "tone", + "tvocHealthConcern", + "tvocMeasurement", + "ultravioletIndex", + "valve", + "veryFineDustHealthConcern", + "veryFineDustSensor", + "videoCapture2", + "videoStreamSettings", + "voltageMeasurement", + "waterFlowAlarm", + "waterMeter", + "waterSensor", + "webrtc", + "wifiInformation", + "windMode", + "windowShade", + "windowShadeLevel", + "windowShadePreset", + "windowShadeTiltLevel", + "zoneManagement", + "zwMultichannel", +]) + + +def build_headers(pat: str) -> Dict: + return { + "Authorization": f"Bearer {pat}", + "Accept": "application/vnd.smartthings+json;v=1, application/json", + "Content-Type": "application/json", + } + + +def scan_profiles(drivers_dirs: List[Path]) -> Set[Tuple[str, int]]: + """ + Walk every drivers_dir, find all *.yml files under any profiles/ subdirectory, + parse them with PyYAML, and extract (capabilityId, version) pairs. + + Returns a deduplicated set of (id, version) tuples. + """ + found: Set[Tuple[str, int]] = set() + profile_count = 0 + error_count = 0 + + for drivers_dir in drivers_dirs: + for profile_path in drivers_dir.rglob("profiles/*.yml"): + profile_count += 1 + try: + with open(profile_path, encoding="utf-8") as f: + profile = yaml.safe_load(f) + if not profile or not isinstance(profile.get("components"), list): + continue + for component in profile["components"]: + for cap in component.get("capabilities") or []: + cap_id = cap.get("id") + cap_ver = cap.get("version", 1) + if cap_id: + found.add((cap_id, int(cap_ver))) + except Exception as exc: + print(f" WARNING: failed to parse {profile_path}: {exc}", file=sys.stderr) + error_count += 1 + + print( + f"Scanned {profile_count} profile(s) across {len(drivers_dirs)} driver tree(s)" + + (f", {error_count} parse error(s)" if error_count else "") + + "." + ) + return found + + +def output_path(output_dir: Path, cap_id: str, cap_ver: int) -> Path: + return output_dir / f"{cap_id}_{cap_ver}.json" + + +def fetch_definitions( + pairs: List[Tuple[str, int]], + headers: Dict, + max_retries: int = 3, + retry_delay: float = 2.0, +) -> Tuple[Dict[Tuple[str, int], Dict], List[Tuple[str, int, str]]]: + """ + Fetch capability definitions for the given (id, version) pairs. + + Pairs whose ID is in QUERY_ENDPOINT_IDS are fetched via a single bulk POST + to /capabilities/query. All other pairs are fetched individually via + GET /capabilities//, so that an unsupported capability causes + only an isolated failure rather than affecting the entire batch. + + Any capability that is missing from the bulk query response (e.g. due to an + intermittent API hiccup) is retried individually via GET up to max_retries + times before being recorded as a failure. + + Returns a tuple of: + - dict mapping (id, version) -> definition object for successful fetches + - list of (id, version, reason) tuples for failed fetches + """ + results: Dict[Tuple[str, int], Dict] = {} + failures: List[Tuple[str, int, str]] = [] + if not pairs: + return results, failures + + # Split into bulk-query pairs and individual-GET pairs + query_pairs = [(cid, ver) for cid, ver in pairs if cid in QUERY_ENDPOINT_IDS] + get_pairs = [(cid, ver) for cid, ver in pairs if cid not in QUERY_ENDPOINT_IDS] + + # --- Bulk query for known-good capabilities --- + if query_pairs: + query = [{"capabilityId": cid, "version": ver} for cid, ver in query_pairs] + response = requests.post(QUERY_ENDPOINT, headers=headers, json={"query": query}, timeout=30) + + if response.status_code == 200: + for item in response.json().get("items", []): + results[(item["id"], item["version"])] = item + # Any pair missing from the response gets retried individually - the + # API occasionally omits items from the bulk response transiently. + returned = {(item["id"], item["version"]) for item in response.json().get("items", [])} + missing = [(cid, ver) for cid, ver in query_pairs if (cid, ver) not in returned] + if missing: + print( + f" WARNING: {len(missing)} capability/version pair(s) missing from bulk query " + "response — retrying individually..." + ) + for cap_id, cap_ver in missing: + _fetch_individual(cap_id, cap_ver, headers, results, failures, + max_retries, retry_delay) + else: + print( + f" WARNING: bulk query returned HTTP {response.status_code} — " + "marking all query-endpoint capabilities as failed." + ) + for cap_id, cap_ver in query_pairs: + failures.append((cap_id, cap_ver, f"HTTP {response.status_code}")) + + # --- Individual GET for all other capabilities --- + if get_pairs: + print(f" Fetching {len(get_pairs)} capability/version pair(s) via individual GET...") + for cap_id, cap_ver in get_pairs: + _fetch_individual(cap_id, cap_ver, headers, results, failures, + max_retries, retry_delay) + + return results, failures + + +def _fetch_individual( + cap_id: str, + cap_ver: int, + headers: Dict, + results: Dict, + failures: List, + max_retries: int = 3, + retry_delay: float = 2.0, +) -> None: + """ + Fetch a single capability definition via GET /capabilities//, + retrying up to max_retries times on failure with exponential backoff. + Appends to results on success or failures on final failure. + """ + last_status = None + for attempt in range(1, max_retries + 1): + r = requests.get( + f"{API_BASE}/capabilities/{cap_id}/{cap_ver}", + headers=headers, + timeout=30, + ) + if r.status_code == 200: + results[(cap_id, cap_ver)] = r.json() + return + last_status = r.status_code + if attempt < max_retries: + wait = retry_delay * attempt + print( + f" WARNING: {cap_id} v{cap_ver} — HTTP {last_status} " + f"(attempt {attempt}/{max_retries}, retrying in {wait:.0f}s...)" + ) + time.sleep(wait) + + print(f" WARNING: skipping {cap_id} v{cap_ver} — HTTP {last_status} after {max_retries} attempts.") + failures.append((cap_id, cap_ver, f"HTTP {last_status}")) + + +def write_failure_report( + failures: List[Tuple[str, int, str]], + output_file: Path, + truncate_at: int = 10, +) -> None: + """ + Write a markdown-formatted failure report suitable for use as a PR comment. + + The file is only written when there are failures. If failures is empty, + this function does nothing (leaving any previously existing file untouched, + but no file will be created for a clean run). + """ + if not failures: + return + + lines = [ + "## ⚠️ Capability Definition Download Failures", + "", + "The following capability definitions could not be downloaded from the SmartThings API:", + "", + "| Capability ID | Version | Reason |", + "|--------------|---------|--------|", + ] + + shown = failures[:truncate_at] + for cap_id, cap_ver, reason in shown: + lines.append(f"| `{cap_id}` | {cap_ver} | {reason} |") + + if len(failures) > truncate_at: + lines.append("") + lines.append(f"_...and {len(failures) - truncate_at} more._") + + lines += [ + "", + "These capabilities may not be available in the test environment. " + "Tests using these capabilities may fail or fall back to built-in definitions.", + ] + + output_file.write_text("\n".join(lines) + "\n", encoding="utf-8") + + +def main(argv=None): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "--drivers-dir", + action="append", + dest="drivers_dirs", + metavar="PATH", + required=True, + help="Root of a driver tree to scan (may be specified multiple times).", + ) + parser.add_argument( + "--output-dir", + metavar="PATH", + required=True, + help="Directory to write _.json files.", + ) + parser.add_argument( + "--overwrite", + action="store_true", + default=False, + help=( + "Re-download and overwrite all capability files even if they already " + "exist. Without this flag only missing files are fetched." + ), + ) + parser.add_argument( + "--failed-output-file", + metavar="PATH", + default=None, + help=( + "When set, write a markdown-formatted report of any capabilities that " + "could not be downloaded to this file. The file is only created when " + "there are failures, making it suitable for use as a PR comment via " + "hashFiles() checks in CI workflows." + ), + ) + args = parser.parse_args(argv) + + pat = os.environ.get("CAPABILITY_PAT", "") + if not pat: + print("ERROR: CAPABILITY_PAT environment variable is not set.", file=sys.stderr) + sys.exit(1) + + headers = build_headers(pat) + output_dir = Path(args.output_dir).expanduser().resolve() + output_dir.mkdir(parents=True, exist_ok=True) + + drivers_dirs = [Path(d).expanduser().resolve() for d in args.drivers_dirs] + for d in drivers_dirs: + if not d.is_dir(): + print(f"ERROR: --drivers-dir does not exist: {d}", file=sys.stderr) + sys.exit(1) + + # Step 1: scan profiles + all_pairs = scan_profiles(drivers_dirs) + print(f"Found {len(all_pairs)} unique capability/version pair(s).") + + # Step 2: filter known-bad IDs + excluded = {p for p in all_pairs if p[0] in EXCLUDED_IDS} + for cap_id, cap_ver in sorted(excluded): + print( + f" WARNING: excluding {cap_id} v{cap_ver} " + "(rejected by the query endpoint — likely a duplicate/malformed entry)." + ) + all_pairs -= excluded + + # Step 3: resolve what to fetch + if args.overwrite: + to_fetch = sorted(all_pairs) + already_present = 0 + else: + to_fetch = sorted(p for p in all_pairs if not output_path(output_dir, p[0], p[1]).exists()) + already_present = len(all_pairs) - len(to_fetch) + + if already_present: + print(f"{already_present} capability/version pair(s) already on disk — skipping.") + + if not to_fetch: + print("Nothing to fetch. Output directory is up to date.") + _print_summary(len(drivers_dirs), len(all_pairs) + len(excluded), len(excluded), + already_present, 0, 0, 0, output_dir) + return + + print(f"Fetching {len(to_fetch)} capability definition(s)...") + + # Step 4: fetch + definitions, failures = fetch_definitions(to_fetch, headers) + + # Step 5: write + written = 0 + skipped_api = 0 + for cap_id, cap_ver in to_fetch: + definition = definitions.get((cap_id, cap_ver)) + if definition is None: + skipped_api += 1 + continue + dest = output_path(output_dir, cap_id, cap_ver) + dest.write_text(json.dumps(definition, indent=2), encoding="utf-8") + written += 1 + + _print_summary(len(drivers_dirs), len(all_pairs) + len(excluded), len(excluded), + already_present, len(to_fetch), written, skipped_api, output_dir) + + # Step 6: write failure report if requested + if args.failed_output_file: + failed_output_file = Path(args.failed_output_file).expanduser().resolve() + write_failure_report(failures, failed_output_file) + if failures: + print(f" Failure report written to: {failed_output_file}") + + +def _print_summary( + trees: int, + total_found: int, + excluded: int, + already_present: int, + requested: int, + written: int, + skipped_api: int, + output_dir: Path, +) -> None: + print() + print("=== Summary ===") + print(f" Driver trees scanned : {trees}") + print(f" Unique capabilities : {total_found}") + if excluded: + print(f" Excluded (bad IDs) : {excluded}") + print(f" Already on disk : {already_present}") + print(f" Requested from API : {requested}") + print(f" Written : {written}") + if skipped_api: + print(f" Skipped (API error) : {skipped_api}") + print(f" Output directory : {output_dir}") + + +if __name__ == "__main__": + main() diff --git a/tools/run_driver_tests.py b/tools/run_driver_tests.py index e686a0f4b7..9020bba05b 100755 --- a/tools/run_driver_tests.py +++ b/tools/run_driver_tests.py @@ -38,6 +38,10 @@ def run_tests(verbosity_level, filter, junit, coverage_files, html): total_tests = 0 total_passes = 0 drivers_needing_html = {} + # Propagate ST_CAPABILITY_JSON_DIR so the mock capability channel can load + # capability definitions from pre-fetched JSON files produced by + # tools/fetch_capability_definitions.py. + env = os.environ.copy() 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 @@ -46,9 +50,9 @@ def run_tests(verbosity_level, filter, junit, coverage_files, html): print("#" * len(test_line)) print(test_line) if test_file in coverage_files: - a = subprocess.run("lua -lluacov {}".format(test_file), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + a = subprocess.run("lua -lluacov {}".format(test_file), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env=env) else: - a = subprocess.run("lua {}".format(test_file), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + a = subprocess.run("lua {}".format(test_file), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env=env) lines = a.stdout.decode().split("\n") test_count = 0 passes = 0 diff --git a/tools/run_driver_tests_p.py b/tools/run_driver_tests_p.py index 20f22938e9..d19bf46716 100644 --- a/tools/run_driver_tests_p.py +++ b/tools/run_driver_tests_p.py @@ -34,10 +34,14 @@ def per_driver_task(driver_dir): return failure_output def run_test(test_file): + # Propagate ST_CAPABILITY_JSON_DIR so the mock capability channel can load + # capability definitions from pre-fetched JSON files produced by + # tools/fetch_capability_definitions.py. + env = os.environ.copy() if test_file.parent.parent.parent.name in CHANGED_DRIVERS: - a = subprocess.run("lua -lluacov {}".format(test_file), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + a = subprocess.run("lua -lluacov {}".format(test_file), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env=env) else: - a = subprocess.run("lua {}".format(test_file), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + a = subprocess.run("lua {}".format(test_file), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env=env) error = a.stderr.decode() if error and error != "": print(error) From c9c1a245040de5dd841d8eb1c9e803f06299c9ab Mon Sep 17 00:00:00 2001 From: Zach Varberg Date: Fri, 31 Jul 2026 13:18:12 -0500 Subject: [PATCH 491/538] zigbee-bed: Ensure test loaded capabilities load before accessing --- drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 50c7512838..2b02b5ec73 100644 --- a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua +++ b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua @@ -7,7 +7,6 @@ local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" local t_utils = require "integration_test.utils" local zigbee_test_utils = require "integration_test.zigbee_test_utils" -local custom_capabilities = require "shus-mattress/custom_capabilities" local shus_mattress_profile_def = t_utils.get_profile_definition("shus-smart-mattress.yml") test.add_package_capability("aiMode.yaml") @@ -18,6 +17,8 @@ test.add_package_capability("strongExpMode.yaml") test.add_package_capability("yoga.yaml") test.add_package_capability("mattressHardness.yaml") +local custom_capabilities = require "shus-mattress/custom_capabilities" + local PRIVATE_CLUSTER_ID = 0xFCC2 local MFG_CODE = 0x1235 From a61c2e0aad3c78f765f96c2c3cc3880ff0f0b567 Mon Sep 17 00:00:00 2001 From: chris-baumler Date: Tue, 4 Aug 2026 12:11:29 -0500 Subject: [PATCH 492/538] WWSTCERT-12427 Update smart plug label --- drivers/SmartThings/zwave-switch/fingerprints.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index d0d10f2e5a..2ca0fa3c73 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -565,7 +565,7 @@ zwaveManufacturer: productId: 0xC001 deviceProfileName: smartplug-binary - id: 0312/FF00/FF07 - deviceLabel: Minoston Outlet + deviceLabel: Smart Plug manufacturerId: 0x0312 productType: 0xFF00 productId: 0xFF07 From a0040ab9cd67dce40190a95b989516405dca7117 Mon Sep 17 00:00:00 2001 From: thinkaName <144081204+thinkaName@users.noreply.github.com> Date: Wed, 5 Aug 2026 03:11:56 +0800 Subject: [PATCH 493/538] firstled_light Change to the correct manufacturer (#3095) --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 4 ++-- .../zigbee-switch/src/firstled-light/fingerprints.lua | 2 +- .../zigbee-switch/src/test/test_firstled_light.lua | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 439bb5da28..08eb875591 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2542,9 +2542,9 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: M2S2BAC deviceProfileName: switch-light-restore-wireless - - id: "ManDai Technology/DC2DC12MiV1" + - id: "FIRSTLED/DC2DC12MiV1" deviceLabel: Easy-Fit Driver - manufacturer: ManDai Technology + manufacturer: FIRSTLED model: DC2DC12MiV1 deviceProfileName: light-color-temp-time-restore zigbeeGeneric: diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-light/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-light/fingerprints.lua index ba8c2347b3..a406321a2c 100644 --- a/drivers/SmartThings/zigbee-switch/src/firstled-light/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/firstled-light/fingerprints.lua @@ -2,5 +2,5 @@ -- Licensed under the Apache License, Version 2.0 return { - { mfr = "ManDai Technology", model = "DC2DC12MiV1" } + { mfr = "FIRSTLED", model = "DC2DC12MiV1" } } diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_firstled_light.lua b/drivers/SmartThings/zigbee-switch/src/test/test_firstled_light.lua index 79b77ab12a..05a04a06ad 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_firstled_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_firstled_light.lua @@ -16,7 +16,7 @@ local mock_device = test.mock_device.build_test_zigbee_device( zigbee_endpoints = { [1] = { id = 1, - manufacturer = "ManDai Technology", + manufacturer = "FIRSTLED", model = "DC2DC12MiV1", server_clusters = { 0x0006, 0x0008, 0x0300 } } From 719780ae565e3a135d97ddcfc210762af37b74d6 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:49:46 -0500 Subject: [PATCH 494/538] Matter Switch: Add logging on create/update of EDGE_CHILD devices (#3166) --- .../matter-switch/src/switch_handlers/attribute_handlers.lua | 4 ++++ .../matter-switch/src/switch_utils/device_configuration.lua | 1 + 2 files changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index 753c76992e..ae1f219606 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -333,6 +333,8 @@ function AttributeHandlers.available_endpoints_handler(driver, device, ib, respo device.log.warn("Received an AvailableEndpoints response but no Electrical Sensor endpoints have been identified as supporting the Power Topology cluster with SET feature. Ignoring this response.") return end + + device.log.debug_with({hub_logs=true}, string.format("Handling AvailableEndpoints response for endpoint %d with elements: %s", ib.endpoint_id, st_utils.stringify_table(ib.data.elements or {}))) for i, set_ep_info in pairs(set_topology_eps or {}) do if ib.endpoint_id == set_ep_info.endpoint_id then -- since EP response is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table @@ -366,6 +368,8 @@ function AttributeHandlers.parts_list_handler(driver, device, ib, response) device.log.warn("Received a PartsList response but no Electrical Sensor endpoints have been identified as supporting the Power Topology cluster with TREE feature. Ignoring this response.") return end + + device.log.debug_with({hub_logs=true}, string.format("Handling PartsList response for endpoint %d with elements: %s", ib.endpoint_id, st_utils.stringify_table(ib.data.elements or {}))) for i, tree_ep_info in pairs(tree_topology_eps or {}) do if ib.endpoint_id == tree_ep_info.endpoint_id then -- since EP response is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 130fd1c1e4..8e5f797877 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -39,6 +39,7 @@ function ChildConfiguration.create_or_update_child_devices(driver, device, serve local child_profile, optional_component_capabilities = 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 + device.log.info_with({hub_logs=true}, string.format("Creating child device for endpoint %d with profile %s", ep_id, child_profile)) driver:try_create_device({ type = "EDGE_CHILD", label = label_and_name, From bb87a707e2ccd6d3c04aca2beafcf769323983c1 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 6 Aug 2026 16:22:34 -0500 Subject: [PATCH 495/538] WWSTCERT-12180 Schlage Sense Pro (#3025) * WWSTCERT-12180 Schlage Sense Pro * Fix formatting of Schlage Sense Pro fingerprint entry --- drivers/SmartThings/matter-lock/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 7eb674bce9..16117d9119 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -197,6 +197,12 @@ matterManufacturer: vendorId: 0x152C productId: 0x9501 deviceProfileName: lock + #Schlage + - id: "4662/14336" + deviceLabel: Schlage Sense Pro + vendorId: 0x1236 + productId: 0x3800 + deviceProfileName: lock-modular #Lockly - id: "5212/2" deviceLabel: Lockly Smart Lock From 273d954e4d55763665ce8464fd780194471bee55 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Fri, 7 Aug 2026 09:53:03 -0500 Subject: [PATCH 496/538] WWSTCERT-13076 Lockly Smart Lock (#3161) --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 16117d9119..b81ae4c48f 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -209,6 +209,11 @@ matterManufacturer: vendorId: 0x145C productId: 0x0002 deviceProfileName: lock-modular + - id: "5212/3" + deviceLabel: Lockly Smart Lock + vendorId: 0x145C + productId: 0x0003 + deviceProfileName: lock-modular matterGeneric: - id: "matter/door-lock" deviceLabel: Matter Door Lock From 46305636e93ce3a1f3631faf44186b2abbd6c99b Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 10 Aug 2026 15:01:03 -0500 Subject: [PATCH 497/538] Updated to report crashing files --- tools/run_driver_tests_p.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/run_driver_tests_p.py b/tools/run_driver_tests_p.py index d19bf46716..c8387dcb1c 100644 --- a/tools/run_driver_tests_p.py +++ b/tools/run_driver_tests_p.py @@ -63,6 +63,12 @@ def run_test(test_file): else: successes += 1 test_cases.append(test_case) + if error and error != "" and len(test_cases) == 0: + failure_output += "\t{} ERROR: test file failed to run\n".format(test_suite_name) + error_case = junit_xml.TestCase("(file error) {}".format(test_suite_name)) + error_case.add_error_info("ERROR", error) + test_cases.append(error_case) + failures += 1 test_suite.test_cases = test_cases return (test_suite, successes, failures, failure_output) From ffe3ea9c1cc9aee679568f08872ce639573bf802 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Tue, 11 Aug 2026 11:34:25 -0500 Subject: [PATCH 498/538] Adding 57 and 58 to the Jenkins driver testing suite --- .github/workflows/jenkins-driver-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jenkins-driver-tests.yml b/.github/workflows/jenkins-driver-tests.yml index aff7f6f150..6af62c080e 100644 --- a/.github/workflows/jenkins-driver-tests.yml +++ b/.github/workflows/jenkins-driver-tests.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: version: - [ 59, 60, 61, dev] + [57, 58, 59, 60, 61, dev] runs-on: ubuntu-latest steps: From 248ccf02a591aaea9972bd848744488e27aad210 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 5 Aug 2026 12:24:48 -0500 Subject: [PATCH 499/538] Updated min_api_version for tests on v14(57) and v15(58) - Clamped matter-air_purifier_api9.lua to version 9 - Filtering of test_matter_camera and test_matter_irrigation_system files for pre 59 --- .../src/test/test_cook_top.lua | 8 +- .../src/test/test_dishwasher.lua | 16 +- .../src/test/test_extractor_hood.lua | 22 +- .../src/test/test_laundry_dryer.lua | 18 +- .../src/test/test_laundry_washer.lua | 4 +- .../src/test/test_matter_appliance_rpc_5.lua | 24 +- .../src/test/test_microwave_oven.lua | 10 +- .../matter-appliance/src/test/test_oven.lua | 20 +- .../src/test/test_refrigerator.lua | 10 +- .../src/test/test_matter_button.lua | 20 +- .../test/test_matter_button_parent_child.lua | 16 +- .../src/test/test_matter_multi_button.lua | 38 +-- .../src/test/test_battery_storage.lua | 10 +- .../matter-energy/src/test/test_evse.lua | 24 +- .../src/test/test_evse_energy_meas.lua | 4 +- .../src/test/test_solar_power.lua | 6 +- .../test_thread_border_router_network.lua | 14 +- .../src/test/test_aqara_matter_lock.lua | 16 +- .../src/test/test_bridged_matter_lock.lua | 4 +- .../matter-lock/src/test/test_matter_lock.lua | 24 +- .../src/test/test_matter_lock_battery.lua | 6 +- .../test/test_matter_lock_batteryLevel.lua | 2 +- .../src/test/test_matter_lock_codes.lua | 38 +-- .../src/test/test_matter_lock_cota.lua | 26 +- .../src/test/test_matter_lock_migration.lua | 286 +++++++++--------- .../src/test/test_matter_lock_modular.lua | 26 +- .../src/test/test_matter_lock_unlatch.lua | 18 +- .../src/test/test_new_matter_lock.lua | 104 +++---- .../src/test/test_new_matter_lock_aliro.lua | 86 ++++-- .../src/test/test_new_matter_lock_battery.lua | 28 +- .../src/test/test_matter_media_speaker.lua | 10 +- .../test/test_matter_media_video_player.lua | 18 +- .../matter-rvc/src/test/test_matter_rvc.lua | 40 +-- .../test/test_matter_air_quality_sensor.lua | 22 +- ...test_matter_air_quality_sensor_modular.lua | 25 +- .../src/test/test_matter_aqara_fp400.lua | 8 +- .../test/test_matter_bosch_button_contact.lua | 14 +- .../src/test/test_matter_flow_sensor.lua | 6 +- .../test/test_matter_freeze_leak_sensor.lua | 12 +- .../src/test/test_matter_pressure_sensor.lua | 6 +- .../src/test/test_matter_rain_sensor.lua | 4 +- .../src/test/test_matter_sensor.lua | 18 +- .../src/test/test_matter_sensor_battery.lua | 6 +- .../test/test_matter_sensor_featuremap.lua | 6 +- .../src/test/test_matter_sensor_rpc.lua | 2 +- .../src/test/test_matter_smoke_co_alarm.lua | 22 +- .../test_matter_smoke_co_alarm_battery.lua | 6 +- .../src/test/test_matter_soil_sensor.lua | 35 ++- .../test/test_aqara_climate_sensor_w100.lua | 28 +- .../src/test/test_aqara_cube.lua | 4 +- .../src/test/test_aqara_light_switch_h2.lua | 4 +- .../src/test/test_electrical_sensor_set.lua | 18 +- .../src/test/test_electrical_sensor_tree.lua | 8 +- .../src/test/test_eve_energy.lua | 30 +- .../src/test/test_hager_waasys.lua | 55 +++- .../src/test/test_ikea_scroll.lua | 31 +- .../test/test_light_illuminance_motion.lua | 13 +- .../src/test/test_matter_bridge.lua | 2 +- .../src/test/test_matter_button.lua | 36 +-- .../src/test/test_matter_camera.lua | 107 ++++--- .../test/test_matter_irrigation_system.lua | 8 + .../src/test/test_matter_light_fan.lua | 18 +- .../src/test/test_matter_multi_button.lua | 48 +-- .../test/test_matter_multi_button_motion.lua | 40 +-- .../test_matter_multi_button_switch_mcd.lua | 18 +- ...est_matter_on_off_device_configuration.lua | 14 +- .../test/test_matter_on_off_parent_child.lua | 29 +- .../test_matter_sensor_offset_preferences.lua | 4 +- .../src/test/test_matter_switch.lua | 54 ++-- .../src/test/test_matter_water_valve.lua | 17 +- .../src/test/test_multi_switch_mcd.lua | 6 +- .../src/test/test_stateless_step.lua | 4 +- .../test/test_third_reality_subdrivers.lua | 25 +- .../src/test/test_matter_air_purifier.lua | 26 +- .../test/test_matter_air_purifier_api9.lua | 42 ++- .../test/test_matter_air_purifier_modular.lua | 4 +- .../src/test/test_matter_fan.lua | 4 +- .../src/test/test_matter_heat_pump.lua | 26 +- .../src/test/test_matter_room_ac.lua | 16 +- .../src/test/test_matter_room_ac_modular.lua | 4 +- .../src/test/test_matter_thermo_battery.lua | 6 +- .../test/test_matter_thermo_featuremap.lua | 8 +- ...st_matter_thermo_multiple_device_types.lua | 6 +- .../test_matter_thermo_setpoint_limits.lua | 20 +- ...test_matter_thermo_setpoint_limits_rpc.lua | 6 +- .../src/test/test_matter_thermostat.lua | 48 +-- ...est_matter_thermostat_composed_bridged.lua | 42 +-- .../test/test_matter_thermostat_modular.lua | 22 +- .../src/test/test_matter_thermostat_rpc5.lua | 2 +- .../src/test/test_matter_water_heater.lua | 14 +- .../src/test/test_matter_closure.lua | 127 ++++++-- .../src/test/test_matter_window_covering.lua | 5 +- .../src/test/test_virtual_switch.lua | 14 +- .../test_MultiIR_air_quality_detector.lua | 44 +-- .../src/test/test_shus_mattress.lua | 112 +++---- .../src/test/test_SLED_button.lua | 6 +- .../src/test/test_aduro_button.lua | 6 +- .../src/test/test_aqara_button.lua | 22 +- .../src/test/test_centralite_button.lua | 10 +- .../src/test/test_dimming_remote.lua | 12 +- .../src/test/test_ewelink_button.lua | 10 +- .../src/test/test_ezviz_button.lua | 12 +- .../src/test/test_frient_button.lua | 18 +- .../src/test/test_heiman_button.lua | 16 +- .../src/test/test_ikea_on_off.lua | 10 +- .../src/test/test_ikea_open_close.lua | 10 +- .../src/test/test_ikea_remote_control.lua | 10 +- .../src/test/test_iris_button.lua | 18 +- .../test/test_linxura_aura_smart_button.lua | 8 +- ...est_linxura_smart_controller_4x_button.lua | 8 +- .../src/test/test_push_only_button.lua | 12 +- .../src/test/test_robb_4x_button.lua | 14 +- .../src/test/test_robb_8x_button.lua | 14 +- .../src/test/test_samjin_button.lua | 4 +- .../src/test/test_shinasystem_button.lua | 10 +- .../src/test/test_somfy_situo_1_button.lua | 10 +- .../src/test/test_somfy_situo_4_button.lua | 8 +- .../src/test/test_sonoff_button.lua | 14 +- .../src/test/test_thirdreality_button.lua | 8 +- .../src/test/test_vimar_button.lua | 12 +- .../src/test/test_wallhero_button.lua | 4 +- .../src/test/test_zigbee_button.lua | 24 +- .../src/test/test_zigbee_ecosmart_button.lua | 14 +- .../src/test/test_zunzunbee_8_button.lua | 8 +- ...test_climax_technology_carbon_monoxide.lua | 2 +- ...st_frient_co_smoke_temperature_battery.lua | 15 +- .../src/test/test_zigbee_carbon_monoxide.lua | 12 +- .../src/test/test_aqara_contact_sensor.lua | 18 +- .../src/test/test_aurora_contact_sensor.lua | 4 +- .../src/test/test_centralite_multi_sensor.lua | 16 +- .../test/test_contact_temperature_sensor.lua | 8 +- .../src/test/test_ecolink_contact.lua | 8 +- .../src/test/test_ewelink_heiman_sensor.lua | 4 +- .../src/test/test_frient_contact_sensor.lua | 18 +- .../test/test_frient_contact_sensor_2_pro.lua | 22 +- .../test/test_frient_contact_sensor_pro.lua | 22 +- .../src/test/test_frient_vibration_sensor.lua | 20 +- .../src/test/test_orvibo_contact_sensor.lua | 4 +- .../src/test/test_samjin_multi_sensor.lua | 8 +- .../src/test/test_sengled_contact_sensor.lua | 4 +- .../src/test/test_smartsense_multi.lua | 52 ++-- .../test/test_smartthings_multi_sensor.lua | 26 +- .../src/test/test_third_reality_contact.lua | 4 +- .../test/test_thirdreality_multi_sensor.lua | 6 +- .../src/test/test_zigbee_contact.lua | 18 +- .../src/test/test_zigbee_contact_battery.lua | 6 +- .../src/test/test_zigbee_contact_tyco.lua | 6 +- .../zigbee-fan/src/test/test_fan_light.lua | 42 +-- .../src/test/test_aqara_sensor.lua | 20 +- .../src/test/test_centralite_sensor.lua | 6 +- .../src/test/test_ewelink_sensor.lua | 4 +- .../test/test_frient_air_quality_sensor.lua | 18 +- .../src/test/test_frient_sensor.lua | 14 +- .../src/test/test_heiman_sensor.lua | 4 +- .../src/test/test_humidity_battery_sensor.lua | 8 +- .../src/test/test_humidity_plaid_systems.lua | 18 +- .../src/test/test_humidity_temperature.lua | 10 +- .../test_humidity_temperature_battery.lua | 10 +- .../test/test_humidity_temperature_sensor.lua | 8 +- .../src/test/test_illuminance_sensor.lua | 6 +- .../test/test_illuminance_sensor_aqara.lua | 12 +- .../src/test/test_bad_battery_reporter.lua | 4 +- .../zigbee-lock/src/test/test_c2o_lock.lua | 16 +- ...est_generic_fingerprint_profile_update.lua | 2 +- .../src/test/test_zigbee_lock_legacy.lua | 56 ++-- .../src/test/test_zigbee_lock_v10.lua | 48 +-- .../test_zigbee_yale_fingerprint-lock.lua | 2 +- .../src/test/test_zigbee_yale_legacy.lua | 22 +- .../test_all_capabilities_zigbee_motion.lua | 24 +- .../src/test/test_aqara_high_precision.lua | 18 +- .../test/test_aqara_motion_illuminance.lua | 12 +- .../src/test/test_aurora_motion.lua | 12 +- .../src/test/test_battery_voltage_motion.lua | 2 +- .../src/test/test_centralite_motion.lua | 4 +- .../src/test/test_compacta_motion.lua | 4 +- .../src/test/test_frient_motion_sensor.lua | 12 +- .../test/test_frient_motion_sensor2_pet.lua | 14 +- .../test/test_frient_motion_sensor_pro.lua | 22 +- .../src/test/test_gator_motion.lua | 18 +- .../src/test/test_ikea_motion.lua | 14 +- .../src/test/test_samjin_sensor.lua | 4 +- .../src/test/test_sengled_motion.lua | 4 +- .../test/test_smartsense_motion_sensor.lua | 16 +- .../src/test/test_smartthings_motion.lua | 2 +- .../src/test/test_thirdreality_sensor.lua | 10 +- .../src/test/test_zigbee_motion_iris.lua | 4 +- .../src/test/test_zigbee_motion_nyce.lua | 6 +- .../src/test/test_zigbee_motion_orvibo.lua | 12 +- .../test/test_zigbee_plugin_motion_sensor.lua | 8 +- ...ower_energy_battery_consumption_report.lua | 5 +- ...frient_power_energy_consumption_report.lua | 5 +- ...st_frient_power_energy_current_voltage.lua | 5 +- .../src/test/test_zigbee_power_meter.lua | 14 +- .../src/test/test_zigbee_power_meter_1p.lua | 20 +- .../test/test_zigbee_power_meter_1p_ZM.lua | 5 +- .../src/test/test_zigbee_power_meter_2p.lua | 16 +- .../test/test_zigbee_power_meter_2p_ZM.lua | 5 +- .../src/test/test_zigbee_power_meter_3p.lua | 22 +- .../test/test_zigbee_power_meter_3p_ZM.lua | 5 +- ...e_power_meter_consumption_report_sihas.lua | 12 +- .../src/test/test_zigbee_power_meter_ezex.lua | 8 +- .../test/test_zigbee_power_meter_frient.lua | 2 +- .../test/test_aqara_presence_sensor_fp1.lua | 20 +- .../src/test/test_st_arrival_sensor_v1.lua | 16 +- .../src/test/test_zigbee_presence_sensor.lua | 26 +- .../test_frient_zigbee_range_extender.lua | 18 +- .../src/test/test_zigbee_extend.lua | 2 +- .../src/test/test_zigbee_sensor.lua | 66 ++-- .../src/test/test_frient_siren.lua | 46 +-- .../src/test/test_frient_siren_tamper.lua | 42 +-- .../zigbee-siren/src/test/test_ozom_siren.lua | 6 +- .../src/test/test_aqara_gas_detector.lua | 34 +-- .../src/test/test_aqara_smoke_detector.lua | 22 +- .../src/test/test_frient_heat_detector.lua | 32 +- .../src/test/test_frient_smoke_detector.lua | 36 +-- .../src/test/test_zigbee_smoke_detector.lua | 14 +- .../src/test/test_zigbee_sound_sensor.lua | 16 +- .../test/test_all_capability_zigbee_bulb.lua | 28 +- .../src/test/test_aqara_led_bulb.lua | 8 +- .../src/test/test_aqara_light.lua | 16 +- .../src/test/test_aqara_smart_plug.lua | 28 +- .../src/test/test_aqara_smart_plug_t1.lua | 30 +- .../src/test/test_aqara_switch_module.lua | 18 +- .../test_aqara_switch_module_no_power.lua | 16 +- .../src/test/test_aqara_switch_no_power.lua | 32 +- .../src/test/test_aqara_switch_power.lua | 34 +-- .../src/test/test_aqara_wall_switch.lua | 22 +- .../src/test/test_aurora_relay.lua | 8 +- .../src/test/test_bad_data_type.lua | 2 +- .../src/test/test_bad_device_kind.lua | 4 +- .../zigbee-switch/src/test/test_cree_bulb.lua | 8 +- .../test/test_duragreen_color_temp_bulb.lua | 8 +- .../test/test_enbrighten_metering_dimmer.lua | 14 +- .../src/test/test_frient_IO_module.lua | 10 +- .../src/test/test_frient_switch.lua | 20 +- .../src/test/test_ge_link_bulb.lua | 14 +- .../src/test/test_hanssem_switch.lua | 50 +-- .../src/test/test_inovelli_vzm30_sn.lua | 28 +- .../src/test/test_inovelli_vzm30_sn_child.lua | 12 +- .../test_inovelli_vzm30_sn_preferences.lua | 16 +- .../src/test/test_inovelli_vzm31_sn.lua | 22 +- .../src/test/test_inovelli_vzm31_sn_child.lua | 12 +- .../test_inovelli_vzm31_sn_preferences.lua | 16 +- .../src/test/test_inovelli_vzm32_sn.lua | 30 +- .../src/test/test_inovelli_vzm32_sn_child.lua | 12 +- .../test_inovelli_vzm32_sn_preferences.lua | 12 +- .../src/test/test_jasco_switch.lua | 10 +- .../src/test/test_laisiao_bath_heather.lua | 64 ++-- .../src/test/test_ledvance_metering_plug.lua | 8 +- .../src/test/test_multi_switch.lua | 10 +- .../src/test/test_multi_switch_no_master.lua | 28 +- .../src/test/test_multi_switch_power.lua | 32 +- .../src/test/test_on_off_zigbee_bulb.lua | 10 +- .../src/test/test_osram_iqbr30_light.lua | 8 +- .../src/test/test_osram_light.lua | 6 +- .../zigbee-switch/src/test/test_rgb_bulb.lua | 10 +- .../zigbee-switch/src/test/test_rgbw_bulb.lua | 14 +- .../test/test_robb_smarrt_2-wire_dimmer.lua | 10 +- .../src/test/test_robb_smarrt_knob_dimmer.lua | 8 +- .../src/test/test_sengled_color_temp_bulb.lua | 6 +- ...sengled_dimmer_bulb_with_motion_sensor.lua | 16 +- .../src/test/test_sinope_dimmer.lua | 18 +- .../src/test/test_sinope_switch.lua | 6 +- .../src/test/test_switch_power.lua | 8 +- .../src/test/test_tuya_multi.lua | 2 +- .../src/test/test_tuya_multi_switch.lua | 10 +- .../src/test/test_wallhero_switch.lua | 48 +-- .../src/test/test_white_color_temp_bulb.lua | 6 +- .../src/test/test_yanmi_switch.lua | 24 +- .../src/test/test_zigbee_ezex_switch.lua | 10 +- ...metering_plug_power_consumption_report.lua | 6 +- .../test_zigbee_metering_plug_rexense.lua | 4 +- .../src/test/test_zll_color_temp_bulb.lua | 12 +- .../src/test/test_zll_dimmer.lua | 10 +- .../src/test/test_zll_dimmer_bulb.lua | 14 +- .../src/test/test_zll_rgb_bulb.lua | 32 +- .../src/test/test_zll_rgbw_bulb.lua | 18 +- .../src/test/test_aqara_thermostat.lua | 24 +- .../src/test/test_centralite_thermostat.lua | 10 +- .../src/test/test_danfoss_thermostat.lua | 12 +- .../src/test/test_fidure_thermostat.lua | 4 +- .../src/test/test_leviton_rc.lua | 28 +- .../src/test/test_popp_thermostat.lua | 30 +- .../src/test/test_resideo_dt300st_m000.lua | 100 +++--- .../test/test_sinope_th1300_thermostat.lua | 8 +- .../test/test_sinope_th1400_thermostat.lua | 8 +- .../src/test/test_sinope_thermostat.lua | 10 +- .../test_stelpro_ki_zigbee_thermostat.lua | 36 +-- .../src/test/test_stelpro_thermostat.lua | 38 +-- .../src/test/test_vimar_thermostat.lua | 38 +-- .../src/test/test_zenwithin_thermostat.lua | 40 +-- .../src/test/test_zigbee_thermostat.lua | 52 ++-- .../test_zigbee_thermostat_wallhero_3in1.lua | 86 +++--- .../zigbee-valve/src/test/test_ezex_valve.lua | 26 +- .../src/test/test_sinope_valve.lua | 12 +- .../src/test/test_zigbee_valve.lua | 24 +- .../zigbee-vent/src/test/test_zigbee_vent.lua | 22 +- .../src/test/test_aqara_water_leak_sensor.lua | 8 +- .../test_centralite_water_leak_sensor.lua | 18 +- .../test/test_frient_water_leak_sensor.lua | 18 +- .../src/test/test_leaksmart_water.lua | 14 +- .../test/test_samjin_water_leak_sensor.lua | 14 +- .../test/test_sengled_water_leak_sensor.lua | 4 +- .../src/test/test_sinope_zigbee_water.lua | 18 +- .../test_smartthings_water_leak_sensor.lua | 12 +- .../test_thirdreality_water_leak_sensor.lua | 12 +- .../src/test/test_zigbee_water.lua | 18 +- .../src/test/test_zigbee_water_freeze.lua | 10 +- .../test/test_thirdreality_watering_kit.lua | 32 +- .../test_zigbee_window_shade_battery_ikea.lua | 20 +- ...est_zigbee_window_shade_only_HOPOsmart.lua | 18 +- .../src/test/test_zigbee_window_treatment.lua | 32 +- ..._zigbee_window_treatment_VWSDSTUST120H.lua | 34 +-- .../test_zigbee_window_treatment_aqara.lua | 40 +-- ...ndow_treatment_aqara_curtain_driver_e1.lua | 30 +- ...ow_treatment_aqara_roller_shade_rotate.lua | 32 +- .../test_zigbee_window_treatment_axis.lua | 34 +-- .../test_zigbee_window_treatment_rooms.lua | 26 +- ...ee_window_treatment_screen_innovations.lua | 28 +- .../test_zigbee_window_treatment_sombra.lua | 28 +- .../test_zigbee_window_treatment_somfy.lua | 38 +-- .../src/test/test_aeon_multiwhite_bulb.lua | 36 +-- .../src/test/test_aeotec_led_bulb_6.lua | 6 +- .../src/test/test_fibaro_rgbw_controller.lua | 24 +- .../zwave-bulb/src/test/test_zwave_bulb.lua | 20 +- .../test/test_zwave_aeotec_nanomote_one.lua | 2 +- .../src/test/test_zwave_button.lua | 12 +- .../src/test/test_zwave_fibaro_button.lua | 2 +- .../src/test/test_zwave_multi_button.lua | 44 +-- .../src/test/test_aeon_meter.lua | 8 +- .../src/test/test_aeotec_gen5_meter.lua | 8 +- .../src/test/test_qubino_3_phase_meter.lua | 8 +- .../src/test/test_qubino_smart_meter.lua | 8 +- .../src/test/test_zwave_electric_meter.lua | 6 +- .../src/test/test_zwave_fan_3_speed.lua | 10 +- .../src/test/test_zwave_fan_4_speed.lua | 10 +- .../test_ecolink_garage_door_operator.lua | 22 +- .../src/test/test_mimolite_garage_door.lua | 24 +- .../test/test_zwave_garage_door_opener.lua | 14 +- .../zwave-lock/src/test/test_keywe_lock.lua | 5 +- .../src/test/test_keywe_lock_legacy.lua | 4 +- .../zwave-lock/src/test/test_lock_battery.lua | 12 +- .../test/test_lock_credentials_commands.lua | 65 +++- .../src/test/test_lock_pre_configured.lua | 50 ++- .../src/test/test_lock_users_commands.lua | 65 +++- .../src/test/test_samsung_lock_legacy.lua | 10 +- .../src/test/test_schlage_lock_legacy.lua | 16 +- .../src/test/test_zwave_lock_legacy.lua | 46 +-- .../src/test/test_zwave_responses.lua | 55 +++- .../src/test/test_zwave_mouse_trap.lua | 24 +- .../src/test/test_aeon_multisensor.lua | 4 +- .../src/test/test_aeotec_multisensor_6.lua | 22 +- .../src/test/test_aeotec_multisensor_7.lua | 12 +- .../src/test/test_aeotec_multisensor_gen5.lua | 2 +- .../src/test/test_aeotec_water_sensor.lua | 24 +- .../src/test/test_aeotec_water_sensor_7.lua | 12 +- .../src/test/test_enerwave_motion_sensor.lua | 6 +- .../src/test/test_everpsring_sp817.lua | 4 +- .../src/test/test_everspring_PIR_sensor.lua | 12 +- .../src/test/test_everspring_ST814.lua | 2 +- .../test_everspring_illuminance_sensor.lua | 2 +- .../test_everspring_motion_light_sensor.lua | 2 +- .../test_ezmultipli_multipurpose_sensor.lua | 10 +- .../test/test_fibaro_door_window_sensor.lua | 14 +- .../test/test_fibaro_door_window_sensor_1.lua | 22 +- .../test/test_fibaro_door_window_sensor_2.lua | 20 +- ...ro_door_window_sensor_with_temperature.lua | 20 +- .../src/test/test_fibaro_flood_sensor.lua | 24 +- .../src/test/test_fibaro_flood_sensor_zw5.lua | 2 +- .../src/test/test_fibaro_motion_sensor.lua | 26 +- .../test/test_fibaro_motion_sensor_zw5.lua | 4 +- .../src/test/test_firmware_version.lua | 6 +- .../src/test/test_generic_sensor.lua | 118 ++++---- .../test_glentronics_water_leak_sensor.lua | 14 +- .../src/test/test_homeseer_multi_sensor.lua | 10 +- .../src/test/test_no_wakeup_poll.lua | 2 +- .../src/test/test_sensative_strip.lua | 6 +- .../test_smartthings_water_leak_sensor.lua | 24 +- .../src/test/test_v1_contact_event.lua | 6 +- .../src/test/test_vision_motion_detector.lua | 12 +- .../src/test/test_zooz_4_in_1_sensor.lua | 20 +- .../test/test_zwave_motion_light_sensor.lua | 22 +- .../test_zwave_motion_temp_light_sensor.lua | 18 +- .../src/test/test_zwave_sensor.lua | 54 ++-- .../src/test/test_zwave_water_sensor.lua | 24 +- .../zwave-siren/src/test/test_aeon_siren.lua | 20 +- .../src/test/test_aeotec_doorbell_siren.lua | 160 +++++----- .../src/test/test_ecolink_wireless_siren.lua | 30 +- .../src/test/test_fortrezz_siren.lua | 8 +- .../src/test/test_philio_sound_siren.lua | 36 +-- .../src/test/test_utilitech_siren.lua | 4 +- .../zwave-siren/src/test/test_yale_siren.lua | 18 +- .../src/test/test_zipato_siren.lua | 10 +- .../zwave-siren/src/test/test_zooz_zse50.lua | 48 +-- .../test/test_zwave_multifunctional-siren.lua | 10 +- .../test/test_zwave_notification_siren.lua | 8 +- .../zwave-siren/src/test/test_zwave_siren.lua | 28 +- .../src/test/test_zwave_sound_sensor.lua | 6 +- .../src/test/test_fibaro_co_sensor_zw5.lua | 28 +- .../src/test/test_fibaro_smoke_sensor.lua | 6 +- .../src/test/test_zwave_alarm_v1.lua | 14 +- .../src/test/test_zwave_co_detector.lua | 14 +- .../src/test/test_zwave_smoke_detector.lua | 30 +- .../src/test/test_aeon_smart_strip.lua | 32 +- .../src/test/test_aeotec_dimmer_switch.lua | 22 +- ..._aeotec_dual_nano_switch_configuration.lua | 2 +- .../test/test_aeotec_heavy_duty_switch.lua | 36 +-- ...t_aeotec_metering_switch_configuration.lua | 2 +- .../src/test/test_aeotec_nano_dimmer.lua | 22 +- .../test_aeotec_nano_dimmer_preferences.lua | 2 +- .../src/test/test_aeotec_smart_switch.lua | 4 +- .../test/test_aeotec_smart_switch_7_eu.lua | 12 +- .../test/test_aeotec_smart_switch_7_us.lua | 12 +- .../test/test_aeotec_smart_switch_gen5.lua | 2 +- .../src/test/test_dawon_smart_plug.lua | 4 +- .../src/test/test_dawon_wall_smart_switch.lua | 20 +- .../src/test/test_eaton_5_scene_keypad.lua | 26 +- .../src/test/test_eaton_accessory_dimmer.lua | 16 +- .../src/test/test_eaton_anyplace_switch.lua | 12 +- .../src/test/test_eaton_rf_dimmer.lua | 2 +- .../src/test/test_ecolink_switch.lua | 18 +- .../src/test/test_fibaro_double_switch.lua | 20 +- .../src/test/test_fibaro_single_switch.lua | 42 +-- .../src/test/test_fibaro_wall_plug_eu.lua | 2 +- ...test_fibaro_wall_plug_uk_configuration.lua | 2 +- .../src/test/test_fibaro_wall_plug_us.lua | 16 +- .../test_fibaro_walli_dimmer_preferences.lua | 14 +- .../test/test_fibaro_walli_double_switch.lua | 30 +- ...fibaro_walli_double_switch_preferences.lua | 12 +- .../src/test/test_generic_zwave_device1.lua | 16 +- ...go_control_plug_in_switch_configuraton.lua | 2 +- .../src/test/test_honeywell_dimmer.lua | 2 +- .../test_inovelli_2_channel_smart_plug.lua | 36 +-- .../src/test/test_inovelli_button.lua | 8 +- .../src/test/test_inovelli_dimmer.lua | 14 +- .../src/test/test_inovelli_dimmer_led.lua | 4 +- .../test_inovelli_dimmer_power_energy.lua | 18 +- .../test/test_inovelli_dimmer_preferences.lua | 10 +- .../src/test/test_inovelli_dimmer_scenes.lua | 22 +- .../src/test/test_inovelli_vzw32_sn.lua | 14 +- .../src/test/test_inovelli_vzw32_sn_child.lua | 12 +- .../test_inovelli_vzw32_sn_preferences.lua | 10 +- .../src/test/test_multi_metering_switch.lua | 36 +-- .../src/test/test_multichannel_device.lua | 118 ++++---- .../test_popp_outdoor_plug_configuration.lua | 2 +- .../src/test/test_qubino_din_dimmer.lua | 24 +- .../test_qubino_din_dimmer_preferences.lua | 14 +- .../test_qubino_flush_1_relay_preferences.lua | 10 +- ...test_qubino_flush_1d_relay_preferences.lua | 6 +- .../src/test/test_qubino_flush_2_relay.lua | 34 +-- .../test_qubino_flush_2_relay_preferences.lua | 10 +- .../src/test/test_qubino_flush_dimmer.lua | 24 +- ..._qubino_flush_dimmer_0_10V_preferences.lua | 14 +- .../test_qubino_flush_dimmer_preferences.lua | 18 +- .../test_qubino_mini_dimmer_preferences.lua | 16 +- ...t_qubino_temperature_sensor_with_power.lua | 18 +- ...ubino_temperature_sensor_without_power.lua | 12 +- .../test_shelly_multi_metering_switch.lua | 34 +-- .../zwave-switch/src/test/test_wyfy_touch.lua | 22 +- .../test/test_wyfy_touch_configuration.lua | 2 +- .../src/test/test_zooz_double_plug.lua | 30 +- .../src/test/test_zooz_power_strip.lua | 66 ++-- .../test/test_zooz_zen_30_dimmer_relay.lua | 80 ++--- ...t_zooz_zen_30_dimmer_relay_preferences.lua | 2 +- .../test/test_zwave_dimmer_power_energy.lua | 18 +- .../src/test/test_zwave_dual_switch.lua | 36 +-- .../test/test_zwave_dual_switch_migration.lua | 4 +- .../src/test/test_zwave_switch.lua | 20 +- .../src/test/test_zwave_switch_battery.lua | 6 +- .../test/test_zwave_switch_electric_meter.lua | 8 +- .../test/test_zwave_switch_energy_meter.lua | 10 +- .../src/test/test_zwave_switch_level.lua | 4 +- .../test/test_zwave_switch_power_meter.lua | 8 +- .../test/test_aeotec_radiator_thermostat.lua | 16 +- .../src/test/test_ct100_thermostat.lua | 20 +- .../src/test/test_fibaro_heat_controller.lua | 22 +- .../test/test_popp_radiator_thermostat.lua | 12 +- .../src/test/test_qubino_flush_thermostat.lua | 30 +- .../src/test/test_stelpro_ki_thermostat.lua | 20 +- .../test/test_thermostat_heating_battery.lua | 28 +- .../src/test/test_zwave_thermostat.lua | 44 +-- .../src/test/test_inverse.valve.lua | 12 +- .../zwave-valve/src/test/test_zwave_valve.lua | 16 +- .../test_zwave_virtual_momentary_switch.lua | 14 +- .../src/test/test_fibaro_roller_shutter.lua | 60 ++-- .../src/test/test_qubino_flush_shutter.lua | 50 +-- .../test/test_zwave_aeotec_nano_shutter.lua | 24 +- .../src/test/test_tuya_curtain.lua | 77 ++++- .../tuya-zigbee/src/test/test_tuya_switch.lua | 35 ++- .../src/test/test_tuya_thermostat.lua | 24 +- 490 files changed, 5111 insertions(+), 4585 deletions(-) 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 0321d956ce..4c489ee38d 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua @@ -91,7 +91,7 @@ test.register_coroutine_test( assert(component_to_endpoint_map["cookSurfaceTwo"] == COOK_SURFACE_TWO_ENDPOINT, "Cook Surface Two Endpoint must be 3") end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -157,7 +157,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -192,7 +192,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua b/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua index 6819815506..93b6bbc3f3 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua @@ -103,7 +103,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -171,7 +171,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -239,7 +239,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -307,7 +307,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -386,7 +386,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -450,7 +450,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -490,7 +490,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -549,7 +549,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) 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 d176352f80..f326ba50b8 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_extractor_hood.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_extractor_hood.lua @@ -176,7 +176,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -250,7 +250,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -339,7 +339,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) test.register_message_test( @@ -452,7 +452,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -490,7 +490,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -531,7 +531,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -592,7 +592,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -653,7 +653,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -666,7 +666,7 @@ test.register_coroutine_test( end, { test_init = test_init_onoff, - min_api_version = 17 + min_api_version = 15 } ) @@ -692,7 +692,7 @@ test.register_coroutine_test( end, { test_init = test_init_onoff, - min_api_version = 17 + min_api_version = 15 } ) @@ -716,7 +716,7 @@ test.register_coroutine_test( end, { test_init = test_init_onoff, - min_api_version = 17 + min_api_version = 15 } ) 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 ba1440af85..d942817d57 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua @@ -102,7 +102,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -170,7 +170,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -238,7 +238,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -306,7 +306,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -385,7 +385,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -449,7 +449,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -489,7 +489,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -548,7 +548,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -591,7 +591,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 6aba845a27..288d76778e 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_laundry_washer.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_laundry_washer.lua @@ -135,7 +135,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -178,7 +178,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 7e59d7925f..cc270cdddb 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 @@ -458,7 +458,7 @@ test.register_coroutine_test( end, { test_init = test_init_dishwasher, - min_api_version = 17 + min_api_version = 15 } ) @@ -500,7 +500,7 @@ test.register_coroutine_test( end, { test_init = test_init_dishwasher, - min_api_version = 17 + min_api_version = 15 } ) @@ -542,7 +542,7 @@ test.register_coroutine_test( end, { test_init = test_init_washer, - min_api_version = 17 + min_api_version = 15 } ) @@ -584,7 +584,7 @@ test.register_coroutine_test( end, { test_init = test_init_washer, - min_api_version = 17 + min_api_version = 15 } ) @@ -626,7 +626,7 @@ test.register_coroutine_test( end, { test_init = test_init_dryer, - min_api_version = 17 + min_api_version = 15 } ) @@ -668,7 +668,7 @@ test.register_coroutine_test( end, { test_init = test_init_dryer, - min_api_version = 17 + min_api_version = 15 } ) @@ -710,7 +710,7 @@ test.register_coroutine_test( end, { test_init = test_init_oven, - min_api_version = 17 + min_api_version = 15 } ) @@ -752,7 +752,7 @@ test.register_coroutine_test( end, { test_init = test_init_oven, - min_api_version = 17 + min_api_version = 15 } ) @@ -794,7 +794,7 @@ test.register_coroutine_test( end, { test_init = test_init_refrigerator, - min_api_version = 17 + min_api_version = 15 } ) @@ -836,7 +836,7 @@ test.register_coroutine_test( end, { test_init = test_init_refrigerator, - min_api_version = 17 + min_api_version = 15 } ) @@ -878,7 +878,7 @@ test.register_coroutine_test( end, { test_init = test_init_refrigerator, - min_api_version = 17 + min_api_version = 15 } ) @@ -920,7 +920,7 @@ test.register_coroutine_test( end, { test_init = test_init_refrigerator, - min_api_version = 17 + min_api_version = 15 } ) 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 cf69721714..b6c5f3091d 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua @@ -174,7 +174,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -258,7 +258,7 @@ test.register_message_test( test_init() init_supported_microwave_oven_modes() end, - min_api_version = 17 + min_api_version = 15 } ) @@ -338,7 +338,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -453,7 +453,7 @@ test.register_message_test( }, -- on receiving NO ERROR we don't do anything. }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -479,7 +479,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_oven.lua b/drivers/SmartThings/matter-appliance/src/test/test_oven.lua index 905f34d666..737f67de12 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_oven.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_oven.lua @@ -143,7 +143,7 @@ test.register_coroutine_test( "Cook Surface Two Endpoint must be 6") end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -228,7 +228,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -287,7 +287,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -371,7 +371,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -411,7 +411,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -437,7 +437,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -475,7 +475,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -513,7 +513,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -535,7 +535,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -557,7 +557,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua b/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua index 1210819d51..6fce3a45fb 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua @@ -147,7 +147,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -206,7 +206,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -249,7 +249,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -308,7 +308,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -351,7 +351,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-button/src/test/test_matter_button.lua b/drivers/SmartThings/matter-button/src/test/test_matter_button.lua index dfc32bc7cb..98b46650f0 100644 --- a/drivers/SmartThings/matter-button/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-button/src/test/test_matter_button.lua @@ -71,7 +71,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -109,7 +109,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -196,7 +196,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -220,7 +220,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -244,7 +244,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -268,7 +268,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -308,7 +308,7 @@ test.register_message_test( }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -348,7 +348,7 @@ test.register_message_test( }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -373,7 +373,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) -- run the tests diff --git a/drivers/SmartThings/matter-button/src/test/test_matter_button_parent_child.lua b/drivers/SmartThings/matter-button/src/test/test_matter_button_parent_child.lua index 01d3b103c7..a8e1ecd69f 100644 --- a/drivers/SmartThings/matter-button/src/test/test_matter_button_parent_child.lua +++ b/drivers/SmartThings/matter-button/src/test/test_matter_button_parent_child.lua @@ -136,7 +136,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -213,7 +213,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -237,7 +237,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -261,7 +261,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -295,7 +295,7 @@ test.register_message_test( }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -329,7 +329,7 @@ test.register_message_test( }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -354,7 +354,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) -- run the tests diff --git a/drivers/SmartThings/matter-button/src/test/test_matter_multi_button.lua b/drivers/SmartThings/matter-button/src/test/test_matter_multi_button.lua index 91f3a06494..9158f8c6cd 100644 --- a/drivers/SmartThings/matter-button/src/test/test_matter_multi_button.lua +++ b/drivers/SmartThings/matter-button/src/test/test_matter_multi_button.lua @@ -136,7 +136,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -169,7 +169,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -194,7 +194,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.held({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -243,7 +243,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -283,7 +283,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button4", button_attr.double({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -311,7 +311,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -339,7 +339,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -377,7 +377,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -416,7 +416,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -464,7 +464,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -488,7 +488,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -512,7 +512,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -536,7 +536,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -576,7 +576,7 @@ test.register_message_test( }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -616,7 +616,7 @@ test.register_message_test( }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -641,7 +641,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -686,7 +686,7 @@ test.register_message_test( -- no double event }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -744,7 +744,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) -- run the tests 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 329daa6bd7..4c899a2042 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua @@ -104,7 +104,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) test.register_coroutine_test( @@ -162,7 +162,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -179,7 +179,7 @@ test.register_coroutine_test( capabilities.powerMeter.power({ value = 30.0, unit = "W" }))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -196,7 +196,7 @@ test.register_coroutine_test( capabilities.powerMeter.power({ value = 30.0, unit = "W" }))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -301,7 +301,7 @@ test.register_coroutine_test( test_init = function() test_init() end, - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-energy/src/test/test_evse.lua b/drivers/SmartThings/matter-energy/src/test/test_evse.lua index 5614b0be18..d1b9c92324 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_evse.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_evse.lua @@ -120,7 +120,7 @@ test.register_coroutine_test( "Device Energy Management Endpoint must be 3") end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -150,7 +150,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -201,7 +201,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -225,7 +225,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -248,7 +248,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -271,7 +271,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -294,7 +294,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -317,7 +317,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -340,7 +340,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -364,7 +364,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -438,7 +438,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -541,7 +541,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua b/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua index f3b3b6c700..bf56269261 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua @@ -118,7 +118,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -152,7 +152,7 @@ test.register_coroutine_test( test_init = function() test_init() end, - min_api_version = 17 + min_api_version = 15 } ) 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 8e4e3c4e9a..f69f034a86 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua @@ -124,7 +124,7 @@ test.register_coroutine_test( capabilities.powerMeter.power({ value = 35.0, unit = "W" }))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -172,7 +172,7 @@ test.register_coroutine_test( test_init = function() test_init() end, - min_api_version = 17 + min_api_version = 15 } ) @@ -197,7 +197,7 @@ test.register_coroutine_test( 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, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-hrap/src/test/test_thread_border_router_network.lua b/drivers/SmartThings/matter-hrap/src/test/test_thread_border_router_network.lua index af8315375f..4c894b7bdb 100644 --- a/drivers/SmartThings/matter-hrap/src/test/test_thread_border_router_network.lua +++ b/drivers/SmartThings/matter-hrap/src/test/test_thread_border_router_network.lua @@ -108,7 +108,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -143,7 +143,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -191,7 +191,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -213,7 +213,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -230,7 +230,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -247,7 +247,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -356,7 +356,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua index 57d7e2b71e..7c6bc2e783 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua @@ -95,7 +95,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -119,7 +119,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -141,7 +141,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -163,7 +163,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -185,7 +185,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -211,7 +211,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -310,7 +310,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -327,7 +327,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua index 2f3566eb6c..0ce5d58404 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua @@ -91,7 +91,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -102,7 +102,7 @@ test.register_coroutine_test( mock_device_level:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua index 58185f2ec8..580bd34420 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua @@ -70,7 +70,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -94,7 +94,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -117,7 +117,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -140,7 +140,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -163,7 +163,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -186,7 +186,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -209,7 +209,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -234,7 +234,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -261,7 +261,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -330,7 +330,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -370,7 +370,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -385,7 +385,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua index e43e075baf..9113374514 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua @@ -117,7 +117,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "base-lock" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -145,7 +145,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "base-lock-batteryLevel" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -155,7 +155,7 @@ test.register_coroutine_test( end, { test_init = test_init_no_battery, - min_api_version = 17 + min_api_version = 15 } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua index f1b3c2f182..8995332812 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua @@ -95,7 +95,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua index c2e86aae29..4ccc4d135b 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua @@ -223,7 +223,7 @@ test.register_coroutine_test( expect_reload_all_codes_messages(mock_device) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -239,7 +239,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -263,7 +263,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -287,7 +287,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -311,7 +311,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -333,7 +333,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -358,7 +358,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -373,7 +373,7 @@ test.register_coroutine_test( expect_reload_all_codes_messages(mock_device) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -418,7 +418,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -456,7 +456,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -562,7 +562,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -611,7 +611,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -671,7 +671,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -706,7 +706,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) test.register_coroutine_test( @@ -740,7 +740,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -782,7 +782,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -825,7 +825,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -904,7 +904,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -971,7 +971,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 15 } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua index 39066776f9..9e39655943 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua @@ -143,7 +143,7 @@ test.register_coroutine_test( expect_kick_off_cota_process(mock_device) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -160,7 +160,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -177,7 +177,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -212,7 +212,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -236,7 +236,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -338,7 +338,7 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -436,7 +436,7 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -527,7 +527,7 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -587,7 +587,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -627,7 +627,7 @@ test.register_coroutine_test( test.mock_time.advance_time(2) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -708,7 +708,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -743,7 +743,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -751,7 +751,7 @@ test.register_coroutine_test( "Delay setting COTA cred if another cred is already being set.", function() end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_migration.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_migration.lua index b94b31e105..8e1f731599 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_migration.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_migration.lua @@ -1,143 +1,143 @@ --- Copyright 2026 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 DoorLock = clusters.DoorLock -local lock_utils = require "lock_utils" - -local mock_device = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("base-lock.yml"), - manufacturer_info = { - vendor_id = 0x135D, - product_id = 0x00C1, - }, - endpoints = { - { - endpoint_id = 0, - clusters = { - { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, - }, - device_types = { - { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode - } - }, - { - endpoint_id = 1, - clusters = { - { - cluster_id = DoorLock.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = 0x0101, -- PIN & USR - } - }, - device_types = { - { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock - } - } - } -}) - -local function test_init() - test.disable_startup_messages() - test.mock_device.add_test_device(mock_device) - -- mock_device:set_field(lock_utils.LOCK_CODES_FOR_MIGRATION, { {1, "ST Remote Operation Code"}, {2, "Guest1"}, {3, "Guest2"} }, {persist=true}) -end - -test.set_test_init_function(test_init) - -local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) -subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) -subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device)) -subscribe_request:merge(DoorLock.attributes.NumberOfPINUsersSupported:subscribe(mock_device)) -subscribe_request:merge(DoorLock.attributes.MaxPINCodeLength:subscribe(mock_device)) -subscribe_request:merge(DoorLock.attributes.MinPINCodeLength:subscribe(mock_device)) -subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device)) -subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) -subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) -subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) - -local enabled_optional_component_capability_pairs = {{ - "main", - { - capabilities.lockUsers.ID, - capabilities.lockCredentials.ID, - capabilities.battery.ID, - } -}} - -test.register_coroutine_test( - "Migration completed and User and Credential values restored", - function() - test.wait_for_events() - test.socket.capability:__queue_receive( - { - mock_device.id, - {capability = capabilities.lockCodes.ID, command = "migrate", args = {}} - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true)) - ) - -- at this point, refresh is injected in migration. - - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) - ) - mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = enabled_optional_component_capability_pairs }) - - - test.wait_for_events() - -- assume this was set prior - mock_device:set_field(lock_utils.LOCK_CODES_FOR_MIGRATION, { {1, "ST Remote Operation Code"}, {2, "Guest1"}, {3, "Guest2"} }, {persist=true}) - test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed( - {profile = {id = "00000000-1111-2222-3333-000000000010", components = { main = {capabilities={ - ["lock"]= {id="lock", version=1}, ["lockAlarm"] = {id="lockAlarm", version=1}, ["remoteControlStatus"] = {id="remoteControlStatus", version=1}, - ["lockUsers"] = {id="lockUsers", version=1}, ["lockCredentials"] = {id="lockCredentials", version=1}, ["firmwareUpdate"] = {id="firmwareUpdate", version=1}, - ["refresh"] = {id="refresh", version=1}}}}}} - )) - test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockUsers.users( - { - {userIndex=1, userName="ST Remote Operation Code", userType="guest"}, - {userIndex=2, userName="Guest1", userType="guest"}, - {userIndex=3, userName="Guest2", userType="guest"} - }, - {visibility={displayed=false}}) - ) - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", - capabilities.lockCredentials.credentials( - { - {credentialIndex=1, credentialType="pin", userIndex=1}, - {credentialIndex=2, credentialType="pin", userIndex=2}, - {credentialIndex=3, credentialType="pin", userIndex=3} - }, - {visibility={displayed=false}} - ) - ) - ) - end, - { - min_api_version = 17 - } -) - -test.run_registered_tests() +-- Copyright 2026 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 DoorLock = clusters.DoorLock +local lock_utils = require "lock_utils" + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("base-lock.yml"), + manufacturer_info = { + vendor_id = 0x135D, + product_id = 0x00C1, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.BasicInformation.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x0101, -- PIN & USR + } + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + +local function test_init() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) + -- mock_device:set_field(lock_utils.LOCK_CODES_FOR_MIGRATION, { {1, "ST Remote Operation Code"}, {2, "Guest1"}, {3, "Guest2"} }, {persist=true}) +end + +test.set_test_init_function(test_init) + +local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) +subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.NumberOfPINUsersSupported:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.MaxPINCodeLength:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.MinPINCodeLength:subscribe(mock_device)) +subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device)) +subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device)) +subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device)) +subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device)) + +local enabled_optional_component_capability_pairs = {{ + "main", + { + capabilities.lockUsers.ID, + capabilities.lockCredentials.ID, + capabilities.battery.ID, + } +}} + +test.register_coroutine_test( + "Migration completed and User and Credential values restored", + function() + test.wait_for_events() + test.socket.capability:__queue_receive( + { + mock_device.id, + {capability = capabilities.lockCodes.ID, command = "migrate", args = {}} + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true)) + ) + -- at this point, refresh is injected in migration. + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}})) + ) + mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = enabled_optional_component_capability_pairs }) + + + test.wait_for_events() + -- assume this was set prior + mock_device:set_field(lock_utils.LOCK_CODES_FOR_MIGRATION, { {1, "ST Remote Operation Code"}, {2, "Guest1"}, {3, "Guest2"} }, {persist=true}) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed( + {profile = {id = "00000000-1111-2222-3333-000000000010", components = { main = {capabilities={ + ["lock"]= {id="lock", version=1}, ["lockAlarm"] = {id="lockAlarm", version=1}, ["remoteControlStatus"] = {id="remoteControlStatus", version=1}, + ["lockUsers"] = {id="lockUsers", version=1}, ["lockCredentials"] = {id="lockCredentials", version=1}, ["firmwareUpdate"] = {id="firmwareUpdate", version=1}, + ["refresh"] = {id="refresh", version=1}}}}}} + )) + test.socket["matter"]:__expect_send({mock_device.id, subscribe_request}) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockUsers.users( + { + {userIndex=1, userName="ST Remote Operation Code", userType="guest"}, + {userIndex=2, userName="Guest1", userType="guest"}, + {userIndex=3, userName="Guest2", userType="guest"} + }, + {visibility={displayed=false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.lockCredentials.credentials( + { + {credentialIndex=1, credentialType="pin", userIndex=1}, + {credentialIndex=2, credentialType="pin", userIndex=2}, + {credentialIndex=3, credentialType="pin", userIndex=3} + }, + {visibility={displayed=false}} + ) + ) + ) + end, + { + min_api_version = 15 + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index 745e769f9e..ca1aedfb04 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -361,7 +361,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {"batteryLevel"}}} }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -396,7 +396,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "lock-modular", optional_component_capabilities = {{"main", {"battery"}}} }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -431,7 +431,7 @@ test.register_coroutine_test( end, { test_init = test_init_unlatch, - min_api_version = 17 + min_api_version = 15 } ) @@ -467,7 +467,7 @@ test.register_coroutine_test( end, { test_init = test_init_unlatch, - min_api_version = 17 + min_api_version = 15 } ) @@ -503,7 +503,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin, - min_api_version = 17 + min_api_version = 15 } ) @@ -540,7 +540,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin_schedule_unlatch, - min_api_version = 17 + min_api_version = 15 } ) @@ -574,7 +574,10 @@ test.register_coroutine_test( ) mock_device_modular:expect_metadata_update({ profile = "lock-modular-embedded-unlatch", optional_component_capabilities = {{"main", {"lockUsers", "lockCredentials", "lockSchedules", "battery"}}} }) end, - { test_init = test_init_modular } + { + test_init = test_init_modular, + min_api_version = 15 + } ) test.register_coroutine_test( @@ -582,7 +585,10 @@ test.register_coroutine_test( -- simulate no actual change test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({})) end, - { test_init = test_init_modular } + { + test_init = test_init_modular, + min_api_version = 15 + } ) test.register_coroutine_test( @@ -619,7 +625,7 @@ test.register_coroutine_test( end, { test_init = test_init_modular, - min_api_version = 17 + min_api_version = 15 } ) @@ -692,7 +698,7 @@ test.register_coroutine_test( test.mock_device.add_test_device(mock_nuki_smart_lock_ultra) mock_nuki_smart_lock_ultra:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_PERCENTAGE, {persist = true}) -- assume this has been set previously end, - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua index f040c2b26e..f2224af358 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua @@ -106,7 +106,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -157,7 +157,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -178,7 +178,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -202,7 +202,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -226,7 +226,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -248,7 +248,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -270,7 +270,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -292,7 +292,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -381,7 +381,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 35acbfbff6..0054d552ea 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -96,7 +96,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -133,7 +133,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -184,7 +184,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -204,7 +204,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -224,7 +224,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -244,7 +244,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -264,7 +264,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -284,7 +284,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -304,7 +304,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -375,7 +375,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -456,7 +456,7 @@ function() test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -473,7 +473,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -493,7 +493,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -512,7 +512,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -533,7 +533,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -557,7 +557,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -579,7 +579,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -601,7 +601,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -623,7 +623,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -649,7 +649,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -748,7 +748,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -975,7 +975,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1044,7 +1044,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1071,7 +1071,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1115,7 +1115,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1164,7 +1164,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1191,7 +1191,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1257,7 +1257,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1284,7 +1284,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1350,7 +1350,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1377,7 +1377,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1442,7 +1442,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1471,7 +1471,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1527,7 +1527,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1590,7 +1590,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1646,7 +1646,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1794,7 +1794,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1823,7 +1823,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1877,7 +1877,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1906,7 +1906,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1978,7 +1978,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2007,7 +2007,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2078,7 +2078,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2107,7 +2107,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2157,7 +2157,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2189,7 +2189,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2257,7 +2257,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2286,7 +2286,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2338,7 +2338,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2370,7 +2370,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2501,7 +2501,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2609,7 +2609,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua index 250fcb8af5..573dbf0e62 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_aliro.lua @@ -155,7 +155,10 @@ test.register_coroutine_test( capabilities.lockAliro.readerVerificationKey(EXPECTED_PUB_HEX, {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -176,7 +179,10 @@ test.register_coroutine_test( capabilities.lockAliro.readerGroupIdentifier(EXPECTED_GROUP_ID_HEX, {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -199,7 +205,10 @@ test.register_coroutine_test( {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -222,7 +231,10 @@ test.register_coroutine_test( {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -245,7 +257,10 @@ test.register_coroutine_test( {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -266,7 +281,10 @@ test.register_coroutine_test( capabilities.lockAliro.groupResolvingKey(EXPECTED_GROUP_ID_HEX, {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -289,7 +307,10 @@ test.register_coroutine_test( {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -312,7 +333,10 @@ test.register_coroutine_test( {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -334,7 +358,10 @@ test.register_coroutine_test( capabilities.lockAliro.cardId("3icub18c8pr00", {visibility = {displayed = false}}) ) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -386,7 +413,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -546,7 +576,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -585,7 +618,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -660,7 +693,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -730,7 +763,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -800,7 +833,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -957,7 +990,10 @@ test.register_coroutine_test( ) ) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -993,7 +1029,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -1066,7 +1102,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -1133,7 +1169,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -1200,7 +1236,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -1229,7 +1265,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -1257,7 +1296,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) ) - end + end, + { + min_api_version = 15 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua index 8ec2a2a781..a13f3d455c 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua @@ -427,7 +427,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "lock" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -458,7 +458,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "lock-batteryLevel" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -490,7 +490,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "lock-battery" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -521,7 +521,7 @@ test.register_coroutine_test( end, { test_init = test_init_unlatch, - min_api_version = 17 + min_api_version = 14 } ) @@ -553,7 +553,7 @@ test.register_coroutine_test( end, { test_init = test_init_unlatch, - min_api_version = 17 + min_api_version = 14 } ) @@ -586,7 +586,7 @@ test.register_coroutine_test( end, { test_init = test_init_unlatch, - min_api_version = 17 + min_api_version = 14 } ) @@ -617,7 +617,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin, - min_api_version = 17 + min_api_version = 14 } ) @@ -649,7 +649,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin, - min_api_version = 17 + min_api_version = 14 } ) @@ -682,7 +682,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin, - min_api_version = 17 + min_api_version = 14 } ) @@ -713,7 +713,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin_schedule_unlatch, - min_api_version = 17 + min_api_version = 14 } ) @@ -745,7 +745,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin_schedule_unlatch, - min_api_version = 17 + min_api_version = 14 } ) @@ -778,7 +778,7 @@ test.register_coroutine_test( end, { test_init = test_init_user_pin_schedule_unlatch, - min_api_version = 17 + min_api_version = 14 } ) @@ -799,7 +799,7 @@ test.register_coroutine_test( end, { test_init = test_init_battery, - min_api_version = 17 + min_api_version = 14 } ) @@ -842,7 +842,7 @@ test.register_coroutine_test( end, { test_init = test_init_battery_level, - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/matter-media/src/test/test_matter_media_speaker.lua b/drivers/SmartThings/matter-media/src/test/test_matter_media_speaker.lua index b795f6438a..85d8d8868b 100644 --- a/drivers/SmartThings/matter-media/src/test/test_matter_media_speaker.lua +++ b/drivers/SmartThings/matter-media/src/test/test_matter_media_speaker.lua @@ -126,7 +126,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -167,7 +167,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -213,7 +213,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -335,7 +335,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -366,7 +366,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) test.run_registered_tests() diff --git a/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua b/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua index dc3a4e4c08..c94b63275a 100644 --- a/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua +++ b/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua @@ -240,7 +240,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -278,7 +278,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -316,7 +316,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -354,7 +354,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -395,7 +395,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -436,7 +436,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -605,7 +605,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -638,7 +638,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -671,7 +671,7 @@ test.register_coroutine_test( mock_device_variable_speed:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua b/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua index 626f7fea60..e1c95249a8 100644 --- a/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua +++ b/drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua @@ -227,7 +227,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -243,7 +243,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -274,7 +274,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -302,7 +302,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -330,7 +330,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -357,7 +357,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -378,7 +378,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -419,7 +419,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -457,7 +457,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -521,7 +521,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -575,7 +575,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -632,7 +632,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -689,7 +689,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -743,7 +743,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -797,7 +797,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -1032,7 +1032,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -1068,7 +1068,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -1094,7 +1094,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -1120,7 +1120,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -1205,7 +1205,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) 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 8d3bd462a5..781854ee8b 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 @@ -499,7 +499,7 @@ test.register_coroutine_test( test_aqs_device_type_do_configure(mock_device, "aqs-temp-humidity-all-level-all-meas") end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -510,7 +510,7 @@ test.register_coroutine_test( end, { test_init = test_init_common, - min_api_version = 17 + min_api_version = 15 } ) @@ -521,7 +521,7 @@ test.register_coroutine_test( end, { test_init = test_init_level, - min_api_version = 17 + min_api_version = 15 } ) @@ -533,7 +533,7 @@ test.register_coroutine_test( end, { test_init = test_init_co_co2, - min_api_version = 17 + min_api_version = 15 } ) @@ -544,7 +544,7 @@ test.register_coroutine_test( end, { test_init = test_init_tvoc, - min_api_version = 17 + min_api_version = 15 } ) @@ -568,7 +568,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -590,7 +590,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -625,7 +625,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -695,7 +695,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -720,7 +720,7 @@ test.register_coroutine_test( end, { test_init = test_init_common, - min_api_version = 17 + min_api_version = 14 } ) @@ -828,7 +828,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 27921e0dec..4a18304d56 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 @@ -353,6 +353,7 @@ test.register_coroutine_test( end, { test_init = test_init_modular_fingerprint, + min_api_version = 15 } ) @@ -409,7 +410,7 @@ test.register_coroutine_test( end, { test_init = test_init_all, - min_api_version = 17 + min_api_version = 15 } ) @@ -439,7 +440,7 @@ test.register_coroutine_test( end, { test_init = test_init_common, - min_api_version = 17 + min_api_version = 15 } ) @@ -466,7 +467,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device_modular_fingerprint:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "unhealthy", "moderate", "slightlyUnhealthy"}, {visibility={displayed=false}}))) test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, subscribe_request_tvoc}) end, - { test_init = test_init_modular_fingerprint } + { + test_init = test_init_modular_fingerprint, + min_api_version = 15 + } ) test.register_coroutine_test( @@ -475,7 +479,10 @@ test.register_coroutine_test( -- simulate no actual change test.socket.device_lifecycle:__queue_receive(mock_device_modular_fingerprint:generate_info_changed({})) end, - { test_init = test_init_modular_fingerprint } + { + test_init = test_init_modular_fingerprint, + min_api_version = 15 + } ) test.register_coroutine_test( @@ -501,7 +508,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device_modular_fingerprint:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "unhealthy", "moderate", "slightlyUnhealthy"}, {visibility={displayed=false}}))) test.socket.matter:__expect_send({mock_device_modular_fingerprint.id, subscribe_request_tvoc}) end, - { test_init = test_init_modular_fingerprint } + { + test_init = test_init_modular_fingerprint, + min_api_version = 15 + } ) test.register_coroutine_test( @@ -510,7 +520,10 @@ test.register_coroutine_test( -- simulate no actual change test.socket.device_lifecycle:__queue_receive(mock_device_modular_fingerprint:generate_info_changed({})) end, - { test_init = test_init_modular_fingerprint } + { + test_init = test_init_modular_fingerprint, + min_api_version = 15 + } ) -- run tests diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua index 7f384c744e..ba4f2fb296 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua @@ -61,7 +61,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -77,7 +77,7 @@ test.register_coroutine_test( assert(mock_device.profile.id == current_profile, "Profile should not change on driverSwitched") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -112,7 +112,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -134,7 +134,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 644c4088e1..d592a9089e 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 @@ -82,7 +82,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -168,7 +168,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -192,7 +192,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -232,7 +232,7 @@ test.register_message_test( }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -257,7 +257,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -292,7 +292,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 dd4eb87252..952989f6a1 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 @@ -71,7 +71,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -101,7 +101,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -133,7 +133,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) 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 02e5815c06..5cd5e85950 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 @@ -99,7 +99,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -135,7 +135,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -170,7 +170,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -191,7 +191,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -211,7 +211,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -244,7 +244,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 13bd93ed70..8920bfcf7f 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 @@ -94,7 +94,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) test.run_registered_tests() 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 2b942c6f0c..6e8c11099d 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 @@ -96,7 +96,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -131,7 +131,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 d6024f61be..8e9b8580d9 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor.lua @@ -163,7 +163,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -185,7 +185,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -207,7 +207,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -242,7 +242,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -264,7 +264,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -299,7 +299,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -336,7 +336,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -366,7 +366,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -381,7 +381,7 @@ test.register_coroutine_test( end, { test_init = test_init_presence_sensor, - min_api_version = 17 + min_api_version = 15 } ) 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 0709be6a31..7393937682 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 @@ -88,7 +88,7 @@ test.register_coroutine_test( mock_device_humidity_battery:expect_metadata_update({ profile = "humidity-battery" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -116,7 +116,7 @@ test.register_coroutine_test( mock_device_humidity_battery:expect_metadata_update({ profile = "humidity-batteryLevel" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -142,7 +142,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 0a02bbb27f..df9ab678cb 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 @@ -171,7 +171,7 @@ test.register_coroutine_test( end, { test_init = test_init_humidity_battery, - min_api_version = 17 + min_api_version = 15 } ) @@ -181,7 +181,7 @@ test.register_coroutine_test( end, { test_init = test_init_humidity_no_battery, - min_api_version = 17 + min_api_version = 15 } ) @@ -191,7 +191,7 @@ test.register_coroutine_test( end, { test_init = test_init_temp_humidity, - min_api_version = 17 + min_api_version = 15 } ) 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 6ca73351c9..1093c1be76 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 @@ -71,7 +71,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 aeef7b15f9..2170115366 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 @@ -124,7 +124,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -172,7 +172,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -220,7 +220,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -281,7 +281,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -342,7 +342,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -390,7 +390,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -435,7 +435,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -470,7 +470,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -492,7 +492,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -527,7 +527,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -578,7 +578,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 3c8102064e..1b96842797 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 @@ -92,7 +92,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "smoke-co-temp-humidity-comeas-battery" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -107,7 +107,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -127,7 +127,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_soil_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_soil_sensor.lua index a5a8042fc0..dbdc25527e 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_soil_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_soil_sensor.lua @@ -80,7 +80,10 @@ test.register_coroutine_test( 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 -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "Relative humidity reports should generate correct messages", @@ -105,7 +108,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 41 })) ) end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "Temperature reports should generate correct messages", @@ -120,7 +126,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) ) end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "Min and max temperature attributes set capability constraint", @@ -144,7 +153,10 @@ test.register_coroutine_test( ) ) end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "Soil moisture is reported raw when no limits are set", @@ -159,7 +171,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 55 })) ) end -) +, + { + min_api_version = 15 + }) local function build_soil_moisture_limits(min_value, max_value) if version.api < 21 then @@ -194,7 +209,10 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 25 })) ) end -) +, + { + min_api_version = 17 + }) test.register_coroutine_test( "Soil moisture scaling rounds correctly", @@ -224,6 +242,9 @@ test.register_coroutine_test( mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 90 })) ) end -) +, + { + min_api_version = 17 + }) test.run_registered_tests() 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 50713626f2..0e92647a4a 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 @@ -160,7 +160,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -185,7 +185,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -213,7 +213,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -232,7 +232,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -257,7 +257,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -282,7 +282,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -323,7 +323,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button2", capabilities.button.button.double({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -352,7 +352,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -381,7 +381,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -400,7 +400,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -419,7 +419,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -444,7 +444,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -467,7 +467,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -490,7 +490,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 dbda0c35fc..ad197cf435 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_aqara_cube.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_aqara_cube.lua @@ -235,7 +235,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -270,7 +270,7 @@ test.register_coroutine_test( end, { test_init = test_init_exhausted, - min_api_version = 17 + min_api_version = 14 } ) 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 468232cbc3..08402cf273 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 @@ -263,7 +263,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -343,7 +343,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 index 18c44f7b9c..32989187fa 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -227,7 +227,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -295,7 +295,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -320,7 +320,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -382,7 +382,7 @@ test.register_coroutine_test( end, { test_init = test_init_periodic, - min_api_version = 17 + min_api_version = 14 } ) @@ -407,7 +407,7 @@ test.register_coroutine_test( end, { test_init = test_init, - min_api_version = 17 + min_api_version = 14 } ) @@ -422,7 +422,7 @@ test.register_coroutine_test( end, { test_init = test_init_periodic, - min_api_version = 17 + min_api_version = 14 } ) @@ -553,7 +553,7 @@ test.register_coroutine_test( end, { test_init = test_init, - min_api_version = 17 + min_api_version = 14 } ) @@ -624,7 +624,7 @@ test.register_coroutine_test( end, { test_init = test_init_periodic, - min_api_version = 17 + min_api_version = 14 } ) @@ -707,7 +707,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua index 2abfe6e9c5..e285c593e2 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua @@ -163,7 +163,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -231,7 +231,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -257,7 +257,7 @@ test.register_coroutine_test( end, { test_init = test_init, - min_api_version = 17 + min_api_version = 14 } ) @@ -340,7 +340,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) 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 e90f5a70da..f51143b813 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -197,7 +197,7 @@ test.register_coroutine_test( assert(sub_driver ~= nil, "sub_driver should be returned") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -209,7 +209,7 @@ test.register_coroutine_test( assert(result == false, "can_handle should return false for device without Eve Private Cluster") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -242,7 +242,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -275,7 +275,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -295,7 +295,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -310,7 +310,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -343,7 +343,7 @@ test.register_coroutine_test( test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") end, - min_api_version = 17 + min_api_version = 14 } ) @@ -362,7 +362,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -389,7 +389,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -416,7 +416,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -455,7 +455,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -483,7 +483,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -510,7 +510,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -546,7 +546,7 @@ test.register_coroutine_test( test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") end, - min_api_version = 17 + min_api_version = 14 } ) @@ -643,7 +643,7 @@ test.register_coroutine_test( test.disable_startup_messages() test.mock_device.add_test_device(mock_eve_device_using_electrical_sensor) end, - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua index 96a684529d..a85137d0e3 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua @@ -592,7 +592,10 @@ test.register_coroutine_test("Test: 4-Button Device Detection - Profile Changes })) }) -end) +end, + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Button Event Handling - Pushed, Double Press, and Held Events on 4-Button Device", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -796,7 +799,10 @@ test.register_coroutine_test("Test: Button Event Handling - Pushed, Double Press ) }) test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.held({ state_change = true }))) -end) +end, + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Device Type Handler - Handles Button (Type 15) and OnOff (Type 256) Device Types with Child Creation", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -889,7 +895,10 @@ test.register_coroutine_test("Test: Device Type Handler - Handles Button (Type 1 parent.id, clusters.OnOff.attributes.OnOff:subscribe(parent, nil) }) -end) +end, + { + min_api_version = 15 + }) test.register_coroutine_test("Test: 2G Relay - Profile Changes Between light-binary and 2-button Based On Endpoint Availability", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1131,7 +1140,10 @@ test.register_coroutine_test("Test: 2G Relay - Profile Changes Between light-bin parent_device_id = parent.id, parent_assigned_child_key = "4" }) -end) +end, + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Dimmer Device - Child Creation for Dimmable Endpoint with Button Support", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1227,7 +1239,10 @@ test.register_coroutine_test("Test: Dimmer Device - Child Creation for Dimmable clusters.LevelControl.server.commands.MoveToLevelWithOnOff(child_dimmer, 3, 51, nil, 0, 0) }) -end) +end, + { + min_api_version = 15 + }) test.register_coroutine_test("Test: 1G Dimmer - Initialization and Profile Update to light-level", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1236,7 +1251,10 @@ test.register_coroutine_test("Test: 1G Dimmer - Initialization and Profile Updat add_matter_device(dimmer, parent, "2-button") test.wait_for_events() configure_parent(parent) -end) +end, + { + min_api_version = 15 + }) test.register_coroutine_test("Test: 1G Dimmer - Host Commands and Level Control Capabilities", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1308,7 +1326,10 @@ test.register_coroutine_test("Test: 1G Dimmer - Host Commands and Level Control parent_1g.id, clusters.LevelControl.server.commands.MoveToLevelWithOnOff(dimmer, 4, 51, nil, 0, 0) }) -end) +end, + { + min_api_version = 15 + }) test.register_coroutine_test("Test: PIR Device - Initialization with Motion and Illuminance Capabilities", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1318,7 +1339,10 @@ test.register_coroutine_test("Test: PIR Device - Initialization with Motion and test.wait_for_events() configure_parent(parent_pir) -end) +end, + { + min_api_version = 15 + }) test.register_coroutine_test("Test: PIR Device - Complete Functionality with Motion, Illuminance, and Dimmer Support", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1472,7 +1496,10 @@ test.register_coroutine_test("Test: PIR Device - Complete Functionality with Mot }) test.socket.capability:__expect_send(child_dimmer:generate_test_message("main", capabilities.switchLevel.level(50))) parent_pir.expect_native_attr_handler_registration(parent_pir, "switchLevel", "level") -end) +end, + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile with Window Covering Child Device", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1610,7 +1637,10 @@ test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0))) test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closed())) -end) +end, + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Window Covering - Preference Changes for Reverse Polarity and Preset Position", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1775,6 +1805,9 @@ test.register_coroutine_test("Test: Window Covering - Preference Changes for Rev parent.id, cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) }) -end) +end, + { + min_api_version = 15 + }) 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 index be28bd040a..6b0bfe32a1 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua @@ -233,7 +233,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -347,7 +347,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -461,7 +461,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -512,7 +512,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -563,7 +563,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -614,7 +614,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -665,7 +665,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -699,7 +699,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -733,7 +733,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -747,7 +747,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_ikea_scroll.id, read_request}) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- Coroutine tests for sensitivity preference logic. -- KNOB_SENSITIVITY_FACTORS = {0.5, 1.0, 2.0}, indexed by the preference value. @@ -797,7 +800,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -845,7 +848,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -893,7 +896,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -941,7 +944,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) 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 b22498eaf7..efc80d9e99 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 @@ -78,7 +78,10 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, 1, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "init should cause device to subscribe to all appropriate clusters", function() @@ -109,7 +112,7 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device.id, subscribe_request}) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -131,7 +134,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -166,7 +169,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -245,7 +248,7 @@ test.register_coroutine_test( end, { test_init = test_init_light_level_motion, - min_api_version = 17 + min_api_version = 15 } ) 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 6cb679796d..eda33fbf5b 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua @@ -101,7 +101,7 @@ test.register_coroutine_test( end, { test_init = test_init_mock_bridge, - min_api_version = 17 + min_api_version = 15 } ) 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 061f2e2f47..d49d9f2452 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua @@ -87,7 +87,7 @@ test.register_coroutine_test( end, { test_init = test_init_for_lifecycle_tests, - min_api_version = 17 + min_api_version = 15 } ) @@ -109,7 +109,7 @@ test.register_coroutine_test( end, { test_init = test_init_for_lifecycle_tests, - min_api_version = 17 + min_api_version = 15 } ) @@ -132,7 +132,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -170,7 +170,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -209,7 +209,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -257,7 +257,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -281,7 +281,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -305,7 +305,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -329,7 +329,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -368,7 +368,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -407,7 +407,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -482,7 +482,7 @@ test.register_coroutine_test( end, { test_init = test_init_battery, - min_api_version = 17 + min_api_version = 15 } ) @@ -509,7 +509,7 @@ test.register_coroutine_test( end, { test_init = test_init_profile_change_with_battery, - min_api_version = 17 + min_api_version = 15 } ) @@ -521,7 +521,7 @@ test.register_coroutine_test( end, { test_init = test_init_profile_change_with_battery, - min_api_version = 17 + min_api_version = 15 } ) @@ -536,7 +536,7 @@ test.register_coroutine_test( end, { test_init = test_init_profile_change_with_battery, - min_api_version = 17 + min_api_version = 15 } ) @@ -551,7 +551,7 @@ test.register_coroutine_test( end, { test_init = test_init_profile_change_with_battery, - min_api_version = 17 + min_api_version = 15 } ) @@ -571,7 +571,7 @@ test.register_coroutine_test( end, { test_init = test_init_profile_change_with_battery, - min_api_version = 17 + min_api_version = 15 } ) @@ -594,7 +594,7 @@ test.register_coroutine_test( end, { test_init = test_init_battery, - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index c238bfd2ca..3926b63fe9 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -1,12 +1,19 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local version = require "version" +local test = require "integration_test" +if version.api < 17 then + -- Run an empty test suite to avoid an error being thrown + test.run_registered_tests() + os.exit(0) +end + local capabilities = require "st.capabilities" local cluster_base = require "st.matter.cluster_base" local clusters = require "st.matter.clusters" local camera_fields = require "sub_drivers.camera.camera_utils.fields" local t_utils = require "integration_test.utils" -local test = require "integration_test" local uint32 = require "st.matter.data_types.Uint32" test.disable_startup_messages() @@ -666,7 +673,7 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -732,7 +739,7 @@ test.register_coroutine_test( assert(not configure_buttons_called, "configure_buttons should not be called when capability state is unchanged") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -759,7 +766,7 @@ test.register_coroutine_test( assert(reconcile_called, "reconcile_profile_and_capabilities should be called") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -807,7 +814,7 @@ test.register_coroutine_test( assert(init_event_count == 0, "cameraPrivacyMode should not be reinitialized for equal values with metatable differences") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -857,7 +864,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -900,7 +907,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -929,7 +936,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -973,7 +980,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1004,7 +1011,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1054,7 +1061,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1079,7 +1086,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1132,7 +1139,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1256,7 +1263,7 @@ test.register_coroutine_test( emit_supported_resolutions() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1274,7 +1281,7 @@ test.register_coroutine_test( emit_supported_resolutions() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1292,7 +1299,7 @@ test.register_coroutine_test( emit_supported_resolutions() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1347,7 +1354,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1371,7 +1378,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1389,7 +1396,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1407,7 +1414,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1455,7 +1462,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1493,7 +1500,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1519,7 +1526,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1548,7 +1555,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.sounds.selectedSound(2))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1591,7 +1598,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1632,7 +1639,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1658,7 +1665,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1698,7 +1705,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1734,7 +1741,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1759,7 +1766,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1826,7 +1833,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1934,7 +1941,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1960,7 +1967,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1985,7 +1992,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2017,7 +2024,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2098,7 +2105,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2130,7 +2137,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2155,7 +2162,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2229,7 +2236,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2302,7 +2309,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2378,7 +2385,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2470,7 +2477,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2576,7 +2583,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2652,7 +2659,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -3263,7 +3270,7 @@ test.register_coroutine_test( camera_cfg.reconcile_profile_and_capabilities = original_reconcile end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -3304,7 +3311,7 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -3316,7 +3323,7 @@ test.register_coroutine_test( end, { test_init = test_init_no_per_zone_sensitivity, - min_api_version = 17 + min_api_version = 14 } ) @@ -3328,7 +3335,7 @@ test.register_coroutine_test( end, { test_init = test_init_no_user_defined_zone, - min_api_version = 17 + min_api_version = 14 } ) @@ -3347,7 +3354,7 @@ test.register_coroutine_test( end, { test_init = test_init_no_user_defined_zone, - min_api_version = 17 + min_api_version = 14 } ) @@ -3364,7 +3371,7 @@ test.register_coroutine_test( end, { test_init = test_init_no_user_defined_zone, - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_irrigation_system.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_irrigation_system.lua index 3acf4129f9..31795f91ea 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_irrigation_system.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_irrigation_system.lua @@ -1,6 +1,14 @@ -- Copyright © 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local version = require "version" +local test = require "integration_test" +if version.api < 17 then + test.run_registered_tests() + os.exit(0) +end + + local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local t_utils = require "integration_test.utils" 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 32023a36cb..d3be60f352 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 @@ -157,7 +157,10 @@ test.register_coroutine_test( ) test.socket.matter:__expect_send({mock_device_capabilities_disabled.id, subscribe_request}) end, - { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } + { + test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end, + min_api_version = 15 + } ) test.register_coroutine_test( @@ -165,7 +168,10 @@ test.register_coroutine_test( -- simulate no actual change test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed({})) end, - { test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end } + { + test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end, + min_api_version = 15 + } ) local FanMode = clusters.FanControl.attributes.FanMode @@ -213,7 +219,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -249,7 +255,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -274,7 +280,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -299,7 +305,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 d7d5efde97..0f4c1e28b1 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 @@ -278,7 +278,7 @@ test.register_coroutine_test( end, { test_init = test_init_battery, - min_api_version = 17 + min_api_version = 15 } ) @@ -302,7 +302,7 @@ test.register_coroutine_test( end, { test_init = test_init_battery, - min_api_version = 17 + min_api_version = 15 } ) @@ -325,7 +325,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -358,7 +358,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -383,7 +383,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.held({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -408,7 +408,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -432,7 +432,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -472,7 +472,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button5", button_attr.double({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -500,7 +500,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -528,7 +528,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -566,7 +566,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -605,7 +605,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -653,7 +653,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -677,7 +677,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -701,7 +701,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -725,7 +725,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -764,7 +764,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -803,7 +803,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -827,7 +827,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -871,7 +871,7 @@ test.register_message_test( -- no double event }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -929,7 +929,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -951,7 +951,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -972,7 +972,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "5-button-batteryLevel" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -993,7 +993,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "5-button-battery" }) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 index 44ee11861e..ec2e7ae289 100644 --- 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 @@ -184,7 +184,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -217,7 +217,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -242,7 +242,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.held({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -267,7 +267,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -291,7 +291,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -331,7 +331,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button6", button_attr.double({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -359,7 +359,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -378,7 +378,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -406,7 +406,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -444,7 +444,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -483,7 +483,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -531,7 +531,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -555,7 +555,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -579,7 +579,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -603,7 +603,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -643,7 +643,7 @@ test.register_message_test( }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -683,7 +683,7 @@ test.register_message_test( }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -707,7 +707,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -751,7 +751,7 @@ test.register_message_test( -- no double event }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -809,7 +809,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) -- run the 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 70f9666542..b3390edc29 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 @@ -271,7 +271,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -294,7 +294,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -327,7 +327,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -352,7 +352,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -411,7 +411,7 @@ test.register_coroutine_test( end, { test_init = test_init_mcd_unsupported_switch_device_type, - min_api_version = 17 + min_api_version = 15 } ) @@ -432,7 +432,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -450,7 +450,7 @@ test.register_coroutine_test( expect_configure_buttons() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -464,7 +464,7 @@ test.register_coroutine_test( expect_configure_buttons() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -484,7 +484,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_child.id, "doConfigure" }) end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_device_configuration.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_device_configuration.lua index d27ee2c383..fc729ed3fa 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_device_configuration.lua @@ -50,7 +50,7 @@ test.register_coroutine_test( end, { test_init = function() test.mock_device.add_test_device(mock_device_onoff_switch_as_server) end, - min_api_version = 17 + min_api_version = 15 } ) @@ -83,7 +83,7 @@ test.register_coroutine_test( end, { test_init = function() test.mock_device.add_test_device(mock_device_onoff_switch_as_client) end, - min_api_version = 17 + min_api_version = 15 } ) @@ -122,7 +122,7 @@ test.register_coroutine_test( end, { test_init = function() test.mock_device.add_test_device(mock_device_dimmer_switch_as_server) end, - min_api_version = 17 + min_api_version = 15 } ) @@ -159,7 +159,7 @@ test.register_coroutine_test( end, { test_init = function() test.mock_device.add_test_device(mock_device_plug_with_switch_profile_vendor_override) end, - min_api_version = 17 + min_api_version = 15 } ) @@ -196,7 +196,7 @@ test.register_coroutine_test( end, { test_init = function() test.mock_device.add_test_device(mock_device_color_dimmer) end, - min_api_version = 17 + min_api_version = 15 } ) @@ -241,7 +241,7 @@ test.register_coroutine_test( end, { test_init = function() test.mock_device.add_test_device(mock_device_mounted_on_off_control) end, - min_api_version = 17 + min_api_version = 15 } ) @@ -296,7 +296,7 @@ test.register_coroutine_test( end, { test_init = function() test.mock_device.add_test_device(mock_device_mounted_dimmable_load_control) end, - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua index 608112d61c..02a01dc44e 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_on_off_parent_child.lua @@ -177,7 +177,7 @@ test.register_coroutine_test( end, { test_init = test_init_for_lifecycle_tests, - min_api_version = 17 + min_api_version = 15 } ) @@ -193,7 +193,7 @@ test.register_coroutine_test( end, { test_init = test_init_for_lifecycle_tests, - min_api_version = 17 + min_api_version = 15 } ) @@ -207,7 +207,7 @@ test.register_coroutine_test( end, { test_init = test_init_for_generate_info_changed_tests, - min_api_version = 17 + min_api_version = 15 } ) @@ -238,7 +238,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -289,7 +289,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -320,6 +320,9 @@ test.register_message_test( clusters.ColorControl.server.commands.MoveToColor:build_test_command_response(mock_device, extended_color_ep_id) } }, + }, + { + min_api_version = 15 } ) @@ -354,7 +357,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -460,7 +463,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -514,7 +517,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -613,7 +616,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -692,7 +695,7 @@ test.register_coroutine_test( test_init = function() test.mock_device.add_test_device(mock_plug) end, - min_api_version = 17 + min_api_version = 15 } ) @@ -710,7 +713,7 @@ test.register_coroutine_test( test_init = function() test.mock_device.add_test_device(mock_plug) end, - min_api_version = 17 + min_api_version = 15 } ) @@ -771,7 +774,7 @@ test.register_coroutine_test( test_init = function() test.mock_device.add_test_device(mock_plug_profile_override) end, - min_api_version = 17 + min_api_version = 15 } ) @@ -871,7 +874,7 @@ test.register_coroutine_test( end, { test_init = function() test.mock_device.add_test_device(mock_switch) end, - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua index eabc9246f7..e6d51d1f36 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_sensor_offset_preferences.lua @@ -61,7 +61,7 @@ test.register_coroutine_test("Read appropriate attribute values after tempOffset }))) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -84,7 +84,7 @@ test.register_coroutine_test("Read appropriate attribute values after humidityOf }))) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 e68f1d0101..d48a89b9cc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -93,7 +93,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -110,7 +110,7 @@ test.register_coroutine_test( test.socket.matter:__expect_send({mock_device.id, subscribe_request}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -143,7 +143,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -176,7 +176,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -204,7 +204,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -229,7 +229,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -291,7 +291,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -316,7 +316,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -346,7 +346,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -376,7 +376,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -448,7 +448,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -526,7 +526,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -551,7 +551,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -576,7 +576,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -606,7 +606,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -631,7 +631,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -676,7 +676,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -694,7 +694,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -759,7 +759,7 @@ test.register_coroutine_test( end, { test_init = test_init_x_y_color_mode, - min_api_version = 17 + min_api_version = 14 } ) @@ -791,7 +791,7 @@ test.register_coroutine_test( end, { test_init = test_init_x_y_color_mode, - min_api_version = 17 + min_api_version = 14 } ) @@ -823,7 +823,7 @@ test.register_coroutine_test( end, { test_init = test_init_x_y_color_mode, - min_api_version = 17 + min_api_version = 14 } ) @@ -881,7 +881,7 @@ test.register_coroutine_test( end, { test_init = test_init_x_y_color_mode, - min_api_version = 17 + min_api_version = 14 } ) @@ -979,7 +979,7 @@ test.register_message_test( }, { test_init = test_init_with_hue_sat_color_mode_set, - min_api_version = 17 + min_api_version = 14 } ) @@ -1065,7 +1065,7 @@ test.register_message_test( }, { test_init = test_init_with_hue_sat_color_mode_set, - min_api_version = 17 + min_api_version = 14 } ) @@ -1143,7 +1143,7 @@ test.register_coroutine_test( end, { test_init = test_init_no_hue_sat, - min_api_version = 17 + min_api_version = 15 } ) @@ -1195,7 +1195,7 @@ test.register_coroutine_test( end, { test_init = test_init_color_temp, - min_api_version = 17 + min_api_version = 15 } ) @@ -1222,7 +1222,7 @@ test.register_coroutine_test( end, { test_init = test_init_color_temp, - min_api_version = 17 + min_api_version = 15 } ) 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 788a00d76e..221405a62a 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 @@ -69,7 +69,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) mock_device:expect_metadata_update({ profile = "water-valve-level" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 15 + } ) test.register_message_test( @@ -93,7 +96,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -118,7 +121,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -159,7 +162,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -179,8 +182,6 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.valve.valve.closed()) }, - }, - { { channel = "matter", direction = "receive", @@ -209,7 +210,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -231,7 +232,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) 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 5b1b569b25..0b68428669 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 @@ -200,7 +200,7 @@ test.register_message_test( }, { test_init = test_init_mock_3switch, - min_api_version = 17 + min_api_version = 14 } ) @@ -227,7 +227,7 @@ test.register_message_test( }, { test_init = test_init_mock_2switch, - min_api_version = 17 + min_api_version = 14 } ) @@ -254,7 +254,7 @@ test.register_message_test( }, { test_init = test_init_mock_3switch_non_sequential, - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua index a92c547ac7..eec293013b 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_stateless_step.lua @@ -128,7 +128,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -210,7 +210,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_third_reality_subdrivers.lua b/drivers/SmartThings/matter-switch/src/test/test_third_reality_subdrivers.lua index a32ce8f8ff..8ed1a78031 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_third_reality_subdrivers.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_third_reality_subdrivers.lua @@ -95,6 +95,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.doorControl.door.open()) } + }, + { + min_api_version = 15 } ) @@ -114,6 +117,9 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.doorControl.door.closed()) } + }, + { + min_api_version = 15 } ) @@ -143,6 +149,9 @@ test.register_message_test( clusters.OnOff.server.commands.On(mock_device, 1) } } + }, + { + min_api_version = 15 } ) @@ -170,6 +179,9 @@ test.register_message_test( clusters.OnOff.server.commands.Off(mock_device, 1) } } + }, + { + min_api_version = 15 } ) @@ -192,6 +204,9 @@ test.register_message_test( -- BatPercentRemaining is in units of 0.5%, so 200 = 100% message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) } + }, + { + min_api_version = 15 } ) @@ -212,6 +227,9 @@ test.register_message_test( -- 150 * 0.5 = 75% message = mock_device:generate_test_message("main", capabilities.battery.battery(75)) } + }, + { + min_api_version = 15 } ) @@ -224,7 +242,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({profile = "garage-door-battery"}) mock_device:expect_metadata_update({provisioning_state = "PROVISIONED"}) end, - {min_api_version = 17} + {min_api_version = 15} ) test.register_coroutine_test( @@ -234,7 +252,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({profile = "garage-door-battery"}) mock_device:expect_metadata_update({provisioning_state = "PROVISIONED"}) end, - {min_api_version = 17} + {min_api_version = 15} ) @@ -298,6 +316,7 @@ test.register_coroutine_test( end, { test_init = function() test.mock_device.add_test_device(mock_device_misprofiled) end, + min_api_version = 15 } ) @@ -525,7 +544,7 @@ test.register_coroutine_test( end, { test_init = test_init_mk1, - min_api_version = 17 + min_api_version = 15 } ) 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 0a97b12181..630896d25c 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 @@ -432,7 +432,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_aqs, - min_api_version = 17 + min_api_version = 15 } ) @@ -448,7 +448,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs, - min_api_version = 17 + min_api_version = 15 } ) @@ -473,7 +473,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 17 + min_api_version = 15 } ) @@ -530,7 +530,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -574,7 +574,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -612,7 +612,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -660,7 +660,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -721,7 +721,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -777,7 +777,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -799,7 +799,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -870,7 +870,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -888,7 +888,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 17 + min_api_version = 15 } ) @@ -914,7 +914,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 17 + min_api_version = 15 } ) 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 8bb8a6c920..4fed28f10f 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 @@ -6,9 +6,6 @@ local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local SinglePrecisionFloat = require "st.matter.data_types.SinglePrecisionFloat" local t_utils = require "integration_test.utils" -local version = require "version" - -version.api = 9 -- include driver-side cluster definitions to test embedded clusters on lower api versions clusters.HepaFilterMonitoring = require "embedded_clusters.HepaFilterMonitoring" @@ -432,7 +429,8 @@ test.register_coroutine_test( end, { test_init = test_init_ap_aqs, - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) @@ -448,7 +446,8 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs, - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) @@ -473,7 +472,8 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) @@ -530,7 +530,8 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) @@ -574,7 +575,8 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) @@ -612,7 +614,8 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) @@ -660,7 +663,8 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) @@ -721,7 +725,8 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) @@ -778,7 +783,8 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) @@ -800,7 +806,8 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) @@ -872,7 +879,8 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) @@ -890,7 +898,8 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) @@ -916,7 +925,8 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 17 + min_api_version = 9, + max_api_version = 10 } ) 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 6aefcb5045..fcc8740458 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 @@ -317,7 +317,7 @@ test.register_coroutine_test( end, { test_init = test_init_basic, - min_api_version = 17 + min_api_version = 15 } ) @@ -394,7 +394,7 @@ test.register_coroutine_test( end, { test_init = test_init_ap_thermo_aqs_preconfigured, - min_api_version = 17 + min_api_version = 15 } ) 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 91d3303f78..8d8964acf0 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua @@ -113,7 +113,7 @@ test.register_coroutine_test( end, { test_init = test_init, - min_api_version = 17 + min_api_version = 15 } ) @@ -128,7 +128,7 @@ test.register_coroutine_test( end, { test_init = test_init_generic, - min_api_version = 17 + min_api_version = 15 } ) 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 66c6e593ed..730a5db48a 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 @@ -148,7 +148,7 @@ test.register_coroutine_test( assert(component_to_endpoint_map["thermostatTwo"] == THERMOSTAT_TWO_EP, string.format("Thermostat Two Endpoint must be %d", THERMOSTAT_TWO_EP)) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -193,7 +193,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -238,7 +238,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -279,7 +279,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -320,7 +320,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -387,7 +387,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -475,7 +475,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -546,7 +546,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -613,7 +613,7 @@ test.register_message_test( }, { test_init = test_init_auto, - min_api_version = 17 + min_api_version = 15 } ) @@ -637,7 +637,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -662,7 +662,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -710,7 +710,7 @@ test.register_coroutine_test( test_init = function() test_init() end, - min_api_version = 17 + min_api_version = 15 } ) @@ -763,7 +763,7 @@ test.register_coroutine_test( test_init = function() test_init() end, - min_api_version = 17 + min_api_version = 15 } ) 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 2f7085bff2..012fc0ab66 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 @@ -296,7 +296,7 @@ test.register_coroutine_test( end, { test_init = test_init_configure, - min_api_version = 17 + min_api_version = 15 } ) @@ -317,7 +317,7 @@ test.register_coroutine_test( end, { test_init = test_init_nostate, - min_api_version = 17 + min_api_version = 15 } ) @@ -355,7 +355,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -411,7 +411,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -459,7 +459,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -495,7 +495,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -544,7 +544,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -584,7 +584,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 aac1ecd4bc..fe15fdb026 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 @@ -314,7 +314,7 @@ test.register_coroutine_test( end, { test_init = test_init_basic, - min_api_version = 17 + min_api_version = 15 } ) @@ -342,7 +342,7 @@ test.register_coroutine_test( end, { test_init = test_init_no_state, - min_api_version = 17 + min_api_version = 15 } ) test.run_registered_tests() 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 5352a65a9d..0206d949f5 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 @@ -107,7 +107,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "thermostat-cooling-only-nostate" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -129,7 +129,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "thermostat-cooling-only-nostate-batteryLevel" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -144,7 +144,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 377725fba0..efdeeec6fe 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 @@ -228,7 +228,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "thermostat-humidity-fan-heating-only" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -252,7 +252,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -277,7 +277,7 @@ test.register_coroutine_test( mock_device_simple:expect_metadata_update({ profile = "thermostat-cooling-only-nostate" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -294,7 +294,7 @@ test.register_coroutine_test( mock_device_no_battery:expect_metadata_update({ profile = "thermostat-cooling-only-nostate-nobattery" }) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 4e7704feb1..ac72e56018 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 @@ -240,7 +240,7 @@ test.register_coroutine_test( get_subscribe_request(mock_device, new_cluster_subscribe_list)) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -252,7 +252,7 @@ test.register_coroutine_test( end, { test_init = test_init_disorder_endpoints, - min_api_version = 17 + min_api_version = 15 } ) @@ -286,7 +286,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 1d772fb2f1..ae5dcc10af 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 @@ -150,7 +150,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -179,7 +179,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -208,7 +208,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -237,7 +237,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -259,7 +259,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -281,7 +281,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -297,7 +297,7 @@ test.register_coroutine_test( assert(min_setpoint_deadband_checked == true, "min_setpoint_deadband_checked is True") end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -327,7 +327,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -357,7 +357,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -387,7 +387,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 3dd153d270..13702d015d 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 @@ -101,7 +101,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -126,7 +126,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -151,7 +151,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 d1373c036d..c7ab7d646f 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat.lua @@ -169,7 +169,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -191,7 +191,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -213,7 +213,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -240,7 +240,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -267,7 +267,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -289,7 +289,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -311,7 +311,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -333,7 +333,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -355,7 +355,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -392,7 +392,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -441,7 +441,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -490,7 +490,7 @@ test.register_message_test( }, { test_init = test_init_auto, - min_api_version = 17 + min_api_version = 15 } ) @@ -530,7 +530,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -571,7 +571,7 @@ test.register_message_test( }, { test_init = test_init_auto, - min_api_version = 17 + min_api_version = 15 } ) @@ -623,7 +623,7 @@ test.register_message_test( }, { test_init = test_init_auto, - min_api_version = 17 + min_api_version = 15 } ) @@ -680,7 +680,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -716,7 +716,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -741,7 +741,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -766,7 +766,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -791,7 +791,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -832,7 +832,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -857,7 +857,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -878,7 +878,7 @@ test.register_coroutine_test("Battery percent reports should generate correct me test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -933,7 +933,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 af44bf73f6..07e9e94604 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 @@ -98,7 +98,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -121,7 +121,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -144,7 +144,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -173,7 +173,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -202,7 +202,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -225,7 +225,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -248,7 +248,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -271,7 +271,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -294,7 +294,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -329,7 +329,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -382,7 +382,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -424,7 +424,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -474,7 +474,7 @@ test.register_message_test( }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -512,7 +512,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -537,7 +537,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -562,7 +562,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -587,7 +587,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -628,7 +628,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -653,7 +653,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -674,7 +674,7 @@ test.register_coroutine_test("Battery percent reports should generate correct me test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -729,7 +729,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 a678e85499..b1aca3242e 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 @@ -179,7 +179,7 @@ test.register_coroutine_test( end, { test_init = test_init, - min_api_version = 17 + min_api_version = 15 } ) @@ -245,7 +245,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({ profile = updated_device_profile })) test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) end, - { test_init = test_init_modular_fingerprint } + { + test_init = test_init_modular_fingerprint, + min_api_version = 15 + } ) test.register_coroutine_test( @@ -253,7 +256,10 @@ test.register_coroutine_test( -- simulate no actual change test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({})) end, - { test_init = function() test.mock_device.add_test_device(mock_device_modular) end } + { + test_init = function() test.mock_device.add_test_device(mock_device_modular) end, + min_api_version = 15 + } ) local function initialize_subscribe_request(mock_device, subscribed_attributes) @@ -318,7 +324,10 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({ profile = updated_device_profile })) test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) end, - { test_init = test_init_modular_fingerprint } + { + test_init = test_init_modular_fingerprint, + min_api_version = 15 + } ) test.register_coroutine_test( @@ -326,7 +335,10 @@ test.register_coroutine_test( -- simulate no actual change test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({})) end, - { test_init = function() test.mock_device.add_test_device(mock_device_modular) end } + { + test_init = function() test.mock_device.add_test_device(mock_device_modular) end, + min_api_version = 15 + } ) -- run tests 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 f7078703ac..6c05f671e5 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 @@ -137,7 +137,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 b4336beeb4..7c279a68c0 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 @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -142,7 +142,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -204,7 +204,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -236,7 +236,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -261,7 +261,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -331,7 +331,7 @@ test.register_coroutine_test( test_init = function() test_init() end, - min_api_version = 17 + min_api_version = 15 } ) @@ -433,7 +433,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua b/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua index e39cb428c8..1c61a6c96a 100644 --- a/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua +++ b/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua @@ -300,7 +300,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -323,7 +326,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -343,7 +349,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -363,7 +372,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -383,7 +395,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -427,7 +442,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -442,7 +460,10 @@ test.register_coroutine_test( mock_device, 10, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED ) }) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -457,7 +478,10 @@ test.register_coroutine_test( mock_device, 10, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN ) }) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -470,7 +494,10 @@ test.register_coroutine_test( mock_device.id, clusters.ClosureControl.server.commands.Stop(mock_device, 10) }) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -484,7 +511,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5))) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -499,7 +529,10 @@ test.register_coroutine_test( mock_device.id, clusters.ClosureDimension.server.commands.SetTarget(mock_device, 11, 75 * 100) }) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -514,7 +547,10 @@ test.register_coroutine_test( mock_device.id, clusters.ClosureDimension.server.commands.SetTarget(mock_device, 12, 40 * 100) }) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -534,7 +570,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("windowShade1", capabilities.windowShadeLevel.shadeLevel(60)) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -554,7 +593,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("windowShade2", capabilities.windowShadeLevel.shadeLevel(25)) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -574,7 +616,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("windowShade1", capabilities.windowShadeLevel.shadeLevel(0)) ) - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -594,7 +639,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("windowShade1", capabilities.windowShadeLevel.shadeLevel(100)) ) - end + end, + { + min_api_version = 15 + } ) -- --------------------------------------------------------------------------- @@ -622,7 +670,10 @@ test.register_coroutine_test( mock_door_device:generate_test_message("main", capabilities.doorControl.door.closing()) ) end, - {test_init = test_init_door} + { + test_init = test_init_door, + min_api_version = 17 + } ) test.register_coroutine_test( @@ -646,7 +697,10 @@ test.register_coroutine_test( mock_door_device:generate_test_message("main", capabilities.doorControl.door.opening()) ) end, - {test_init = test_init_door} + { + test_init = test_init_door, + min_api_version = 17 + } ) test.register_coroutine_test( @@ -667,9 +721,13 @@ test.register_coroutine_test( mock_door_device:generate_test_message("main", capabilities.doorControl.door.closed()) ) end, - {test_init = test_init_door} + { + test_init = test_init_door, + min_api_version = 17 + } ) + test.register_coroutine_test( "doorControl open following OverallCurrentState FULLY_OPENED", function() update_profile_door() @@ -688,7 +746,10 @@ test.register_coroutine_test( mock_door_device:generate_test_message("main", capabilities.doorControl.door.open()) ) end, - {test_init = test_init_door} + { + test_init = test_init_door, + min_api_version = 17 + } ) test.register_coroutine_test( @@ -710,7 +771,10 @@ test.register_coroutine_test( mock_door_device:generate_test_message("main", capabilities.doorControl.door.open()) ) end, - {test_init = test_init_door} + { + test_init = test_init_door, + min_api_version = 17 + } ) test.register_coroutine_test( @@ -726,7 +790,10 @@ test.register_coroutine_test( ) }) end, - {test_init = test_init_door} + { + test_init = test_init_door, + min_api_version = 15 + } ) test.register_coroutine_test( @@ -742,9 +809,11 @@ test.register_coroutine_test( ) }) end, - {test_init = test_init_door} + { + test_init = test_init_door, + min_api_version = 15 + } ) - test.register_coroutine_test( "ClosureDimension CurrentState on endpoint 11 emits level on door1 for door device", function() update_profile_door() @@ -763,7 +832,10 @@ test.register_coroutine_test( mock_door_device:generate_test_message("door1", capabilities.level.level(75)) ) end, - {test_init = test_init_door} + { + test_init = test_init_door, + min_api_version = 17 + } ) test.register_coroutine_test( @@ -784,7 +856,10 @@ test.register_coroutine_test( mock_door_device:generate_test_message("door2", capabilities.level.level(30)) ) end, - {test_init = test_init_door} + { + test_init = test_init_door, + min_api_version = 17 + } ) test.run_registered_tests() 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 8376e3a6d4..7199e3704d 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 @@ -520,7 +520,10 @@ test.register_coroutine_test( "main", capabilities.windowShade.windowShade.partially_open() ) ) - end + end, + { + min_api_version = 17 + } ) test.register_coroutine_test( diff --git a/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua b/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua index 5c284b1e6b..1feb54fb0d 100644 --- a/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua +++ b/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua @@ -46,7 +46,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -65,7 +65,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -84,7 +84,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -104,7 +104,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.switch.switch.on())) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua b/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua index 606b716dff..b9ae4cab1a 100644 --- a/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua +++ b/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua @@ -73,7 +73,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, read_AQI_messge}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -137,7 +137,7 @@ test.register_coroutine_test( capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "good"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -157,7 +157,7 @@ test.register_coroutine_test( capabilities.fineDustHealthConcern.fineDustHealthConcern.good())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -177,7 +177,7 @@ test.register_coroutine_test( capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern.good())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -197,7 +197,7 @@ test.register_coroutine_test( capabilities.dustHealthConcern.dustHealthConcern.good())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -215,7 +215,7 @@ test.register_coroutine_test( capabilities.formaldehydeMeasurement.formaldehydeLevel({value = 1000.0, unit = "mg/m^3"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -235,7 +235,7 @@ test.register_coroutine_test( capabilities.tvocHealthConcern.tvocHealthConcern({value = "unhealthy"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -253,7 +253,7 @@ test.register_coroutine_test( capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "good"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -271,7 +271,7 @@ test.register_coroutine_test( capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "moderate"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -289,7 +289,7 @@ test.register_coroutine_test( capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "slightlyUnhealthy"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -307,7 +307,7 @@ test.register_coroutine_test( capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "unhealthy"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -325,7 +325,7 @@ test.register_coroutine_test( capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "veryUnhealthy"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -343,7 +343,7 @@ test.register_coroutine_test( capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "hazardous"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -363,7 +363,7 @@ test.register_coroutine_test( capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "moderate"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -383,7 +383,7 @@ test.register_coroutine_test( capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "unhealthy"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -403,7 +403,7 @@ test.register_coroutine_test( capabilities.fineDustHealthConcern.fineDustHealthConcern({value = "moderate"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -423,7 +423,7 @@ test.register_coroutine_test( capabilities.fineDustHealthConcern.fineDustHealthConcern({value = "unhealthy"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -443,7 +443,7 @@ test.register_coroutine_test( capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern({value = "unhealthy"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -463,7 +463,7 @@ test.register_coroutine_test( capabilities.dustHealthConcern.dustHealthConcern({value = "unhealthy"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -483,7 +483,7 @@ test.register_coroutine_test( capabilities.tvocHealthConcern.tvocHealthConcern({value = "good"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 2b02b5ec73..08eb79e2a9 100644 --- a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua +++ b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua @@ -86,7 +86,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, read_0x0011_messge}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -131,7 +131,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, read_0x0011_messge}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -149,7 +149,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftback.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -167,7 +167,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftback.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -185,7 +185,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftwaist.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -203,7 +203,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftwaist.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -221,7 +221,7 @@ test.register_coroutine_test( custom_capabilities.left_control.lefthip.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -239,7 +239,7 @@ test.register_coroutine_test( custom_capabilities.left_control.lefthip.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -257,7 +257,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightback.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -275,7 +275,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightback.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -293,7 +293,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightwaist.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -311,7 +311,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightwaist.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -329,7 +329,7 @@ test.register_coroutine_test( custom_capabilities.right_control.righthip.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -347,7 +347,7 @@ test.register_coroutine_test( custom_capabilities.right_control.righthip.idle({ visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -365,7 +365,7 @@ test.register_coroutine_test( custom_capabilities.mattressHardness.leftBackHardness(1))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -383,7 +383,7 @@ test.register_coroutine_test( custom_capabilities.mattressHardness.leftWaistHardness(1))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -401,7 +401,7 @@ test.register_coroutine_test( custom_capabilities.mattressHardness.leftHipHardness(1))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -419,7 +419,7 @@ test.register_coroutine_test( custom_capabilities.mattressHardness.rightBackHardness(1))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -437,7 +437,7 @@ test.register_coroutine_test( custom_capabilities.mattressHardness.rightWaistHardness(1))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -455,7 +455,7 @@ test.register_coroutine_test( custom_capabilities.mattressHardness.rightHipHardness(1))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -473,7 +473,7 @@ test.register_coroutine_test( custom_capabilities.yoga.state.both())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -491,7 +491,7 @@ test.register_coroutine_test( custom_capabilities.yoga.state.right())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -509,7 +509,7 @@ test.register_coroutine_test( custom_capabilities.yoga.state.left())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -527,7 +527,7 @@ test.register_coroutine_test( custom_capabilities.yoga.state.stop())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -545,7 +545,7 @@ test.register_coroutine_test( custom_capabilities.ai_mode.left.off())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -563,7 +563,7 @@ test.register_coroutine_test( custom_capabilities.ai_mode.left.on())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -581,7 +581,7 @@ test.register_coroutine_test( custom_capabilities.ai_mode.right.on())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -599,7 +599,7 @@ test.register_coroutine_test( custom_capabilities.ai_mode.right.off())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -617,7 +617,7 @@ test.register_coroutine_test( custom_capabilities.auto_inflation.inflationState.off())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -635,7 +635,7 @@ test.register_coroutine_test( custom_capabilities.auto_inflation.inflationState.on())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -653,7 +653,7 @@ test.register_coroutine_test( custom_capabilities.strong_exp_mode.expState.off())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -671,7 +671,7 @@ test.register_coroutine_test( custom_capabilities.strong_exp_mode.expState.on())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -689,7 +689,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -706,7 +706,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -723,7 +723,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -740,7 +740,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -757,7 +757,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -774,7 +774,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -791,7 +791,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -808,7 +808,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -827,7 +827,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftback.soft())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -846,7 +846,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftwaist.soft())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -865,7 +865,7 @@ test.register_coroutine_test( custom_capabilities.left_control.lefthip.soft())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -884,7 +884,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftback.hard())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -903,7 +903,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftwaist.hard())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -922,7 +922,7 @@ test.register_coroutine_test( custom_capabilities.left_control.lefthip.hard())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -941,7 +941,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightback.soft())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -960,7 +960,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightwaist.soft())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -979,7 +979,7 @@ test.register_coroutine_test( custom_capabilities.right_control.righthip.soft())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -998,7 +998,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightback.hard())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1017,7 +1017,7 @@ test.register_coroutine_test( custom_capabilities.right_control.rightwaist.hard())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1036,7 +1036,7 @@ test.register_coroutine_test( custom_capabilities.right_control.righthip.hard())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1053,7 +1053,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1070,7 +1070,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1087,7 +1087,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1112,7 +1112,7 @@ test.register_coroutine_test( custom_capabilities.left_control.leftback("idle", { visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua index 342a62a5c7..288d531148 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_SLED_button.lua @@ -50,7 +50,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -74,7 +74,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -89,7 +89,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua index 808eb0a764..d44d73da7a 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_aduro_button.lua @@ -93,7 +93,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -131,7 +131,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -179,7 +179,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) 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 49883cd275..da3fe73c48 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua @@ -99,7 +99,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -131,7 +131,7 @@ test.register_coroutine_test( mock_device_e1:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -166,7 +166,7 @@ test.register_coroutine_test( mock_device_h1_double_rocker:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -187,7 +187,7 @@ test.register_coroutine_test( capabilities.button.button.pushed({ state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -208,7 +208,7 @@ test.register_coroutine_test( capabilities.button.button.double({ state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -229,7 +229,7 @@ test.register_coroutine_test( capabilities.button.button.held({ state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -248,7 +248,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) test.register_message_test( @@ -266,7 +266,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) test.register_message_test( @@ -284,7 +284,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -328,7 +328,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -350,7 +350,7 @@ test.register_coroutine_test( capabilities.batteryLevel.quantity(1))) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua index be95f76cce..c08a42e4c0 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_centralite_button.lua @@ -95,7 +95,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -152,7 +152,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -191,7 +191,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -205,7 +205,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -253,7 +253,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua b/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua index db9bad6ea9..17170106a7 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_dimming_remote.lua @@ -55,7 +55,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -86,7 +86,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -117,7 +117,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -275,7 +275,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua index 773021ac20..61e30bfaee 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ewelink_button.lua @@ -64,7 +64,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -79,7 +79,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -111,7 +111,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -151,7 +151,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua index 07652b0e0e..b012f30a8d 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ezviz_button.lua @@ -55,7 +55,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -137,7 +137,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device_ezviz_button.id, ZoneStatusAttribute:read(mock_device_ezviz_button) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -199,7 +199,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua index 40d74e503c..6dc9b979c1 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_frient_button.lua @@ -88,7 +88,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_message_test("Refresh should read all necessary attributes", { }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -149,7 +149,7 @@ test.register_coroutine_test("panicAlarm should be triggered and cleared", funct end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -184,7 +184,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -226,7 +226,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -253,7 +253,7 @@ function() test.socket.zigbee:__expect_send({mock_device.id, buttonDelay_msg}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -286,7 +286,7 @@ test.register_coroutine_test(" Configuration and Switching to button-profile-pan --test.socket.capability:__expect_send({mock_device.id, capabilities.panicAlarm.panicAlarm.clear({state_change = true})}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -302,7 +302,7 @@ test.register_coroutine_test("Switching from button-profile-panic-frient to butt test.socket.zigbee:__expect_send({mock_device_panic.id, cluster_base.write_manufacturer_specific_attribute(mock_device_panic,BasicInput.ID,0x8000,DEVELCO_MANUFACTURER_CODE,data_types.Uint16,0xFFFF)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -324,7 +324,7 @@ test.register_coroutine_test("New preferences after switching the profile should test.socket.zigbee:__expect_send({mock_device_panic.id, cluster_base.write_manufacturer_specific_attribute(mock_device_panic, IASZone.ID,0x8005,DEVELCO_MANUFACTURER_CODE,data_types.Enum8, 1)}) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua index 7e9448ed7e..7142b8ad8c 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_heiman_button.lua @@ -92,7 +92,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -127,7 +127,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -174,7 +174,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -198,7 +198,7 @@ test.register_coroutine_test( mock_device_hs6ssb:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -279,7 +279,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -313,7 +313,7 @@ test.register_coroutine_test( mock_device_hs6ssb:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -361,7 +361,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -409,7 +409,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua b/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua index 4a772c5ab0..d3d1d75c67 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ikea_on_off.lua @@ -75,7 +75,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -119,7 +119,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -143,7 +143,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -205,7 +205,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -304,7 +304,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua b/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua index d350567294..56a8d3c382 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ikea_open_close.lua @@ -55,7 +55,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -100,7 +100,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -124,7 +124,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -149,7 +149,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, Groups.commands.AddGroup(mock_device, 0x0000) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -247,7 +247,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua b/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua index 1fc0e9d8e6..92360b9b5d 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_ikea_remote_control.lua @@ -109,7 +109,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -154,7 +154,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -179,7 +179,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -286,7 +286,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -305,7 +305,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua index 81e68a52da..14284ca482 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_iris_button.lua @@ -47,7 +47,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -63,7 +63,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -105,7 +105,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -177,7 +177,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -198,7 +198,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -240,7 +240,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -261,7 +261,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua index a70d3b52a2..b2eecc400c 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_linxura_aura_smart_button.lua @@ -57,7 +57,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -115,7 +115,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua index 35ad7aae08..406bcd018f 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_linxura_smart_controller_4x_button.lua @@ -57,7 +57,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -115,7 +115,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua index 2f6deceb70..527c15ade7 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_push_only_button.lua @@ -48,7 +48,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) test.register_message_test( @@ -61,7 +61,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -80,7 +80,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -164,7 +164,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua index bbe5cbaf86..0f014d8b41 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_robb_4x_button.lua @@ -81,7 +81,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -124,7 +124,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -212,7 +212,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -326,7 +326,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -368,7 +368,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -386,7 +386,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua index 8c291f8483..c073b7e051 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_robb_8x_button.lua @@ -126,7 +126,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -203,7 +203,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -284,7 +284,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -329,7 +329,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -444,7 +444,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -486,7 +486,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -504,7 +504,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua index 72bcd2009c..be5af30cbc 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua @@ -90,7 +90,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -140,7 +140,7 @@ test.register_coroutine_test( -- }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua index d5eed5d55f..2a3f825ac9 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_shinasystem_button.lua @@ -85,7 +85,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -142,7 +142,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -199,7 +199,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -239,7 +239,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -287,7 +287,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua index 2793a6de1b..f5f91aec73 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_1_button.lua @@ -65,7 +65,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -140,7 +140,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -202,7 +202,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -250,7 +250,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua index 9e209541ff..bd4d767f2f 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_somfy_situo_4_button.lua @@ -94,7 +94,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -163,7 +163,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -187,7 +187,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -235,7 +235,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_sonoff_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_sonoff_button.lua index 22f76bc383..4e02098b0b 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_sonoff_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_sonoff_button.lua @@ -161,7 +161,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -180,7 +180,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -199,7 +199,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -218,7 +218,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -237,7 +237,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -256,7 +256,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -271,7 +271,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua index f4ee2e0aed..7c9472df39 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_thirdreality_button.lua @@ -48,7 +48,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -65,7 +65,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -82,7 +82,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.double({ state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.held({ state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua index c76d080a01..f1f4413429 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua @@ -77,7 +77,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -139,7 +139,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -165,7 +165,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -213,7 +213,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua index 93c6d26139..d4fcdd87f1 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_wallhero_button.lua @@ -306,7 +306,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -347,7 +347,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua index db4f6b9a43..a854809234 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_button.lua @@ -39,7 +39,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -58,7 +58,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -77,7 +77,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -91,7 +91,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -110,7 +110,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -129,7 +129,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -167,7 +167,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -187,7 +187,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -213,7 +213,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -296,7 +296,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -373,7 +373,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua index 1b3078f637..cc75674c85 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_zigbee_ecosmart_button.lua @@ -50,7 +50,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -98,7 +98,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -141,7 +141,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -167,7 +167,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -212,7 +212,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -261,7 +261,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua index 13479d4e8b..5e288c6344 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_zunzunbee_8_button.lua @@ -81,7 +81,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -142,7 +142,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -199,7 +199,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) 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 b0df2ffaa4..fb92f80e90 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 @@ -42,7 +42,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_frient_co_smoke_temperature_battery.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_frient_co_smoke_temperature_battery.lua index 16625fdf92..36bad5633a 100644 --- a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_frient_co_smoke_temperature_battery.lua +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_frient_co_smoke_temperature_battery.lua @@ -447,7 +447,10 @@ test.register_coroutine_test( ) }) end -) +, + { + min_api_version = 17 + }) test.register_coroutine_test( "Alarm off command should send StartWarning stop", @@ -474,7 +477,10 @@ test.register_coroutine_test( ) }) end -) +, + { + min_api_version = 17 + }) test.register_coroutine_test( "Default response to StartWarning should emit alarm events", @@ -500,7 +506,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 17 + }) test.run_registered_tests() 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 1b67dc8ee6..ab593688c3 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 @@ -44,7 +44,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -71,7 +71,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ test.register_message_test( }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -126,7 +126,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -216,7 +216,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua index 8f6c5ecfdd..014e2b8ed1 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua @@ -66,7 +66,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -90,7 +90,7 @@ test.register_coroutine_test( capabilities.batteryLevel.battery("normal"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -108,7 +108,7 @@ test.register_coroutine_test( capabilities.batteryLevel.battery("normal"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -126,7 +126,7 @@ test.register_coroutine_test( capabilities.batteryLevel.battery("critical"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_coroutine_test( capabilities.batteryLevel.battery("normal"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -162,7 +162,7 @@ test.register_coroutine_test( capabilities.batteryLevel.battery("warning"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -180,7 +180,7 @@ test.register_coroutine_test( capabilities.batteryLevel.battery("critical"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -198,7 +198,7 @@ test.register_coroutine_test( capabilities.contactSensor.contact.closed())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -216,7 +216,7 @@ test.register_coroutine_test( capabilities.contactSensor.contact.open())) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua index a2bcfdf612..395b85063f 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_aurora_contact_sensor.lua @@ -44,7 +44,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua index b13d48fc2e..09f0108325 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua @@ -108,7 +108,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({300, 200, 100})) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -134,7 +134,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive()) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -197,7 +197,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -221,7 +221,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -282,7 +282,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -307,7 +307,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -333,7 +333,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua index 62e0eee71b..b0ce27f524 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_contact_temperature_sensor.lua @@ -46,7 +46,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -108,7 +108,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -127,7 +127,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -146,7 +146,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua index e092666737..1341f0a518 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_ecolink_contact.lua @@ -47,7 +47,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -117,7 +117,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -155,7 +155,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua index f3b94929b6..05d030b0d3 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_ewelink_heiman_sensor.lua @@ -45,7 +45,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua index 8be3dd430a..e766f0a4d5 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua @@ -48,7 +48,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -135,7 +135,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -154,7 +154,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -207,7 +207,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -226,7 +226,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -245,7 +245,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -264,7 +264,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -283,7 +283,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua index f6a2e0ebd9..3911b323c1 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_2_pro.lua @@ -56,7 +56,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -178,7 +178,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -205,7 +205,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -224,7 +224,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -243,7 +243,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -322,7 +322,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -341,7 +341,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -360,7 +360,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -379,7 +379,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -398,7 +398,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -426,7 +426,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua index 66881f9f0b..e21f06f588 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor_pro.lua @@ -56,7 +56,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -178,7 +178,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -205,7 +205,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -224,7 +224,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -243,7 +243,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -322,7 +322,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -346,7 +346,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -370,7 +370,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -394,7 +394,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -418,7 +418,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -446,7 +446,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua index 7829e32736..d386758433 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_vibration_sensor.lua @@ -300,7 +300,7 @@ test.register_coroutine_test( mock_device_contact:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -328,7 +328,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -347,7 +347,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -366,7 +366,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -407,7 +407,7 @@ function() }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -431,7 +431,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -455,7 +455,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -477,7 +477,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -505,7 +505,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -533,7 +533,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua index 6ebb00d45e..9e4bd2aa0a 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_orvibo_contact_sensor.lua @@ -44,7 +44,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua index d4082723c7..7d854c2f6e 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua @@ -95,7 +95,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -119,7 +119,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -180,7 +180,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -222,7 +222,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua index fc48ab8eaa..f3e92f2c9a 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_sengled_contact_sensor.lua @@ -44,7 +44,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua index 4713e6eb41..701aba257b 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua @@ -116,7 +116,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -134,7 +134,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -152,7 +152,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -188,7 +188,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -206,7 +206,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -224,7 +224,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -242,7 +242,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -260,7 +260,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -278,7 +278,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(97))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -296,7 +296,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -314,7 +314,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(60))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -332,7 +332,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(0))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -350,7 +350,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(0))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -368,7 +368,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(0))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -383,7 +383,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({1050, 3, 9})) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -398,7 +398,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({-1050, -3, -9})) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -413,7 +413,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({10, 1020, 7})) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -428,7 +428,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({-10, -1020, -7})) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -443,7 +443,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({116, 4, 1003})) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -458,7 +458,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({-116, -4, -1003})) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -491,7 +491,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -510,7 +510,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -529,7 +529,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -548,7 +548,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -567,7 +567,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua index da87c0aee2..770b8e6718 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua @@ -89,7 +89,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -108,7 +108,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -134,7 +134,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive()) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -151,7 +151,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active()) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({300, 100, -200})) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -206,7 +206,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -236,7 +236,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -255,7 +255,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -274,7 +274,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -301,7 +301,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -325,7 +325,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -390,7 +390,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -434,7 +434,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua index c03ab49dde..0273886a4c 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_third_reality_contact.lua @@ -44,7 +44,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_thirdreality_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_thirdreality_multi_sensor.lua index 92f7274365..924205c3ff 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_thirdreality_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_thirdreality_multi_sensor.lua @@ -54,7 +54,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.inactive()) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -71,7 +71,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.accelerationSensor.acceleration.active()) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -90,7 +90,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.threeAxis.threeAxis({200, 100, 300})) ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua index f27743d4ee..1064145d99 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua @@ -46,7 +46,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -73,7 +73,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -131,7 +131,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -185,7 +185,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -205,7 +205,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -250,7 +250,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -344,7 +344,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua index d908aedba6..c7bce911b2 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_battery.lua @@ -88,7 +88,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -108,7 +108,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -128,7 +128,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua index 6b482996de..625b8f0d6b 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact_tyco.lua @@ -56,7 +56,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -84,7 +84,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -105,7 +105,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua index b305bb8a9d..fbee4cf910 100644 --- a/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua +++ b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua @@ -50,7 +50,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -71,7 +71,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -92,7 +92,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -138,7 +138,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -163,7 +163,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -183,7 +183,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -203,7 +203,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -223,7 +223,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -243,7 +243,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -263,7 +263,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -287,7 +287,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -311,7 +311,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -335,7 +335,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -365,7 +365,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -395,7 +395,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -425,7 +425,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -455,7 +455,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -485,7 +485,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -509,7 +509,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -533,7 +533,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 762c1f4431..87f8c0e85c 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 @@ -96,7 +96,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -119,7 +119,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -142,7 +142,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -165,7 +165,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -196,7 +196,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -241,7 +241,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -263,7 +263,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -285,7 +285,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -307,7 +307,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) 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 b35c590b91..6fdf421c04 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 @@ -95,7 +95,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -186,7 +186,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -231,7 +231,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 aefbd3c350..c8d5578e11 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 @@ -66,7 +66,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -114,7 +114,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 index 06e836a007..eb022d7ebf 100644 --- 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 @@ -85,7 +85,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -104,7 +104,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -221,7 +221,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -243,7 +243,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -262,7 +262,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -298,7 +298,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -328,7 +328,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -345,7 +345,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) 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 81bc408f32..dfca8c6be1 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 @@ -67,7 +67,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -86,7 +86,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -105,7 +105,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -167,7 +167,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -189,7 +189,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -216,7 +216,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -268,7 +268,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) 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 c75326503d..41a9d5ba22 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 @@ -66,7 +66,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 3d9e24e2eb..d23abc3777 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 @@ -48,7 +48,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -70,7 +70,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -92,7 +92,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -119,7 +119,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) 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 c8aa3a6191..69a154e13e 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 @@ -51,7 +51,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -73,7 +73,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -151,7 +151,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -210,7 +210,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -250,7 +250,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -287,7 +287,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 e821dd3d56..dc3e6052c5 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 @@ -48,7 +48,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -78,7 +78,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -100,7 +100,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -137,7 +137,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -171,7 +171,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 79 }))) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 b03f27d59d..6ffd1b5f28 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 @@ -60,7 +60,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -90,7 +90,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -112,7 +112,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -157,7 +157,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -191,7 +191,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 79 }))) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 f01b9534bb..06a18c6063 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 @@ -55,7 +55,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -85,7 +85,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -107,7 +107,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 fb97045c03..f8c959141d 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 @@ -46,7 +46,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -68,7 +68,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -106,7 +106,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua index 32ee9eae11..8c519b17ca 100644 --- a/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua +++ b/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua @@ -59,7 +59,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -100,7 +100,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -183,7 +183,7 @@ test.register_coroutine_test( MFG_CODE, data_types.Uint16, frequency) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -197,7 +197,7 @@ test.register_coroutine_test( detectionFrequency.detectionFrequency(FREQUENCY_DEFAULT_VALUE, { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_bad_battery_reporter.lua b/drivers/SmartThings/zigbee-lock/src/test/test_bad_battery_reporter.lua index 61fb28ce8d..6dfd87d155 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_bad_battery_reporter.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_bad_battery_reporter.lua @@ -46,7 +46,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -84,7 +84,7 @@ test.register_message_test( }, { test_init = test_init_yrd, - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua index 4bde45ec1a..bf358af78c 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_c2o_lock.lua @@ -41,7 +41,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -75,7 +75,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -88,7 +88,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -101,7 +101,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -114,7 +114,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -129,7 +129,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -154,7 +154,7 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device, 8)}) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_generic_fingerprint_profile_update.lua b/drivers/SmartThings/zigbee-lock/src/test/test_generic_fingerprint_profile_update.lua index e6d6a22b78..bd15eb3dd8 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_generic_fingerprint_profile_update.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_generic_fingerprint_profile_update.lua @@ -46,7 +46,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_legacy.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_legacy.lua index c3b316f486..accfb345ac 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_legacy.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_legacy.lua @@ -81,7 +81,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, Alarm.attributes.AlarmCount:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -163,7 +163,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -196,7 +196,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -215,7 +215,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -234,7 +234,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -253,7 +253,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -273,7 +273,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -284,7 +284,7 @@ test.register_coroutine_test( expect_reload_all_codes_messages() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -303,7 +303,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -345,7 +345,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -395,7 +395,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({["1"] = "test"}), { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -453,7 +453,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -471,7 +471,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -545,7 +545,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -580,7 +580,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -615,7 +615,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -654,7 +654,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -689,7 +689,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({}), { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -746,7 +746,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -777,7 +777,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -843,7 +843,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -862,7 +862,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -881,7 +881,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -909,7 +909,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -939,7 +939,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_v10.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_v10.lua index 9ce880efe6..4d4a370168 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_v10.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock_v10.lua @@ -81,7 +81,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, Alarm.attributes.AlarmCount:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -163,7 +163,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -196,7 +196,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -215,7 +215,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -234,7 +234,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -253,7 +253,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -273,7 +273,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -284,7 +284,7 @@ test.register_coroutine_test( expect_reload_all_codes_messages() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -303,7 +303,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -345,7 +345,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -395,7 +395,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({["1"] = "test"}), { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -453,7 +453,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -471,7 +471,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -545,7 +545,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -580,7 +580,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -615,7 +615,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -654,7 +654,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -689,7 +689,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({}), { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -746,7 +746,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -777,7 +777,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -843,7 +843,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_fingerprint-lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_fingerprint-lock.lua index b0f4d1ffcf..560e0b3768 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_fingerprint-lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_fingerprint-lock.lua @@ -48,7 +48,7 @@ test.register_message_test( }, { test_init = test_init, - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_legacy.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_legacy.lua index 95d496e786..82e487439b 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_legacy.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale_legacy.lua @@ -47,7 +47,7 @@ test.register_coroutine_test( expect_reload_all_codes_messages() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -89,7 +89,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -139,7 +139,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({["1"] = "test"}), { visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -189,7 +189,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({["0"] = "test"}), { visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -218,7 +218,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -276,7 +276,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -321,7 +321,7 @@ test.register_coroutine_test( capabilities.lockCodes.codeChanged("1 failed", { state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -339,7 +339,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({["1"] = "foo"}), { visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -382,7 +382,7 @@ test.register_coroutine_test( capabilities.lockCodes.codeChanged("1 is not set", { state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -412,7 +412,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({["1"] = "initialName"}), { visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -442,7 +442,7 @@ test.register_coroutine_test( capabilities.lockCodes.lockCodes(json.encode({}), { visibility = { displayed = false }}))) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 72fbeb168b..07cb3a9d99 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 @@ -48,7 +48,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -75,7 +75,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -129,7 +129,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -156,7 +156,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -182,7 +182,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -201,7 +201,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -220,7 +220,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -329,7 +329,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -416,7 +416,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -437,7 +437,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -457,7 +457,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) 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 5e961e37ec..3b87239092 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 @@ -73,7 +73,7 @@ test.register_coroutine_test( capabilities.battery.battery(100))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_coroutine_test( capabilities.motionSensor.motion.inactive())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -188,7 +188,7 @@ test.register_coroutine_test( detectionFrequency.detectionFrequency(value, {visibility = {displayed = false}}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -205,7 +205,7 @@ test.register_coroutine_test( sensitivityAdjustment.sensitivityAdjustment.High())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -222,7 +222,7 @@ test.register_coroutine_test( sensitivityAdjustment.sensitivityAdjustment.Medium())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -239,7 +239,7 @@ test.register_coroutine_test( sensitivityAdjustment.sensitivityAdjustment.Low())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -277,7 +277,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 a75033bef8..979c64d45b 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 @@ -88,7 +88,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -111,7 +111,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -139,7 +139,7 @@ test.register_coroutine_test( capabilities.motionSensor.motion.inactive())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -158,7 +158,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -202,7 +202,7 @@ test.register_coroutine_test( capabilities.motionSensor.motion.inactive())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( detectionFrequency.detectionFrequency(PREF_FREQUENCY_VALUE_DEFAULT, {visibility = {displayed = false}}))) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 4ae03f0354..f209f4f18f 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 @@ -56,7 +56,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -83,7 +83,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -110,7 +110,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -137,7 +137,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -176,7 +176,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -220,7 +220,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 1bb1e359bb..99c2da077d 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 @@ -52,7 +52,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) 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 fd1222b0cc..94c6779b44 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 @@ -71,7 +71,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -97,7 +97,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) 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 ec85954568..03afa1c6e9 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 @@ -77,7 +77,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -101,7 +101,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 a7b3828958..42a30de572 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 @@ -71,7 +71,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -84,7 +84,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, OccupancySensing.attributes.Occupancy:read(mock_device):to_endpoint(OCCUPANCY_ENDPOINT)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -214,7 +214,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -233,7 +233,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -252,7 +252,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -284,7 +284,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 4dc67338d5..b84bfc6b6e 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 @@ -79,7 +79,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -98,7 +98,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -147,7 +147,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -201,7 +201,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, IlluminanceMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(ILLUMINANCE_ENDPOINT)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -359,7 +359,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -402,7 +402,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 fe633e089a..f3e39499e0 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 @@ -82,7 +82,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -101,7 +101,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -121,7 +121,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -141,7 +141,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -168,7 +168,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -190,7 +190,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -206,7 +206,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, IlluminanceMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(ILLUMINANCE_ENDPOINT)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -367,7 +367,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -410,7 +410,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -430,7 +430,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -450,7 +450,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 ad129ddc2b..343d4921c1 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 @@ -46,7 +46,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -65,7 +65,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.presenceSensor.presence.not_present())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -81,7 +81,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.open())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -97,7 +97,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -129,7 +129,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(0))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(10))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -163,7 +163,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -182,7 +182,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 f922439c22..bd1f959bc7 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 @@ -60,7 +60,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -149,7 +149,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_add_hub_to_group(0xB9F2) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -211,7 +211,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -237,7 +237,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -265,7 +265,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 95c24f5f8e..2e098b3e69 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 @@ -95,7 +95,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) 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 4ca87d6358..9f4c74dc30 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 @@ -46,7 +46,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:configure_reporting(mock_device, 0xFFFF, 0x0000, 0) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -98,7 +98,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 b8b8fc4c62..c6e4d022e6 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 @@ -69,7 +69,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -86,7 +86,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -103,7 +103,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -152,7 +152,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({ value = -50, unit = "dBm" }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -182,7 +182,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.signalStrength.rssi({value = -100, unit = 'dBm'}))) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 83c8f3c36d..0f6c4039e2 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 @@ -55,7 +55,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) 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 cae6559187..45328961e9 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 @@ -70,7 +70,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -122,7 +122,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 7808c5184a..5794245167 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 @@ -56,7 +56,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -143,7 +143,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 9dca781210..01e7785287 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 @@ -46,7 +46,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -65,7 +65,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -94,7 +94,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) 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 c9172d8b8f..82f1b9509b 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 @@ -46,7 +46,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -65,7 +65,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -84,7 +84,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -103,7 +103,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -130,7 +130,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -157,7 +157,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) 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 3bc0f85f0c..e93a03b861 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 @@ -49,7 +49,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -68,7 +68,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -115,7 +115,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -139,7 +139,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_battery_consumption_report.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_battery_consumption_report.lua index 6a312c1906..d752ccd553 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_battery_consumption_report.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_battery_consumption_report.lua @@ -155,7 +155,10 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end -) +, + { + min_api_version = 15 + }) test.register_message_test( "Refresh should read all necessary attributes", diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_consumption_report.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_consumption_report.lua index 2679329fd3..ae8ad437d9 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_consumption_report.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_consumption_report.lua @@ -140,7 +140,10 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end -) +, + { + min_api_version = 15 + }) test.register_message_test( "Refresh should read all necessary attributes", diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_current_voltage.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_current_voltage.lua index 75f9da6961..a6edbf71bd 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_current_voltage.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_frient_power_energy_current_voltage.lua @@ -356,6 +356,9 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end -) +, + { + min_api_version = 15 + }) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter.lua index 571764d8f2..19ad41c700 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter.lua @@ -91,7 +91,7 @@ test.register_coroutine_test( test_init = function() -- no op to override auto device add on startup end, - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_coroutine_test( test_init = function() -- no op to override auto device add on startup end, - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_coroutine_test( test_init = function() -- no op to override auto device add on startup end, - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_coroutine_test( test_init = function() -- no op to override auto device add on startup end, - min_api_version = 17 + min_api_version = 14 } ) @@ -195,7 +195,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -291,7 +291,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 index f82864cc06..354cffe1e2 100644 --- 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 @@ -76,7 +76,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -156,7 +156,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -240,7 +240,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -259,7 +259,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -305,7 +305,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -324,7 +324,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -347,7 +347,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p_ZM.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p_ZM.lua index ef82322125..530281a13b 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p_ZM.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p_ZM.lua @@ -206,6 +206,9 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end -) +, + { + min_api_version = 15 + }) test.run_registered_tests() 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 index 37edda6bd9..5f9d8a3348 100644 --- 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 @@ -73,7 +73,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -93,7 +93,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -133,7 +133,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -153,7 +153,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -193,7 +193,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -301,7 +301,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_2p_ZM.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_2p_ZM.lua index 5b959e7c61..ffb31f4b64 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_2p_ZM.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_2p_ZM.lua @@ -279,6 +279,9 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end -) +, + { + min_api_version = 15 + }) test.run_registered_tests() 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 index ed606d096e..1b227dec62 100644 --- 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 @@ -72,7 +72,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -92,7 +92,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -112,7 +112,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -132,7 +132,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -152,7 +152,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -192,7 +192,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -212,7 +212,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -232,7 +232,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -252,7 +252,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -384,7 +384,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_3p_ZM.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_3p_ZM.lua index c4d24f35ba..8aba874223 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_3p_ZM.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_3p_ZM.lua @@ -353,6 +353,9 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end -) +, + { + min_api_version = 15 + }) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua index ece6587f2f..b484ac43ee 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua @@ -100,7 +100,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -209,7 +209,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -236,7 +236,7 @@ test.register_coroutine_test( test_init = function() -- no op to override auto device add on startup end, - min_api_version = 17 + min_api_version = 14 } ) test.register_coroutine_test( @@ -254,7 +254,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -277,7 +277,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_ezex.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_ezex.lua index afb5adda69..39a8bc3d43 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_ezex.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_ezex.lua @@ -93,7 +93,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -132,7 +132,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -166,7 +166,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -193,7 +193,7 @@ test.register_coroutine_test( test_init = function() -- no op to override auto device add on startup end, - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua index f6cf172d7f..2a4b2db86a 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua @@ -193,7 +193,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/test/test_aqara_presence_sensor_fp1.lua b/drivers/SmartThings/zigbee-presence-sensor/src/test/test_aqara_presence_sensor_fp1.lua index 557519996e..b04f283c95 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/test/test_aqara_presence_sensor_fp1.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/test/test_aqara_presence_sensor_fp1.lua @@ -63,7 +63,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -91,7 +91,7 @@ test.register_coroutine_test( data_types.Uint8, updates.preferences["stse.sensitivity"]) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -119,7 +119,7 @@ test.register_coroutine_test( data_types.Uint8, 0x01) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -147,7 +147,7 @@ test.register_coroutine_test( data_types.Uint8, updates.preferences["stse.approachDistance"]) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -165,7 +165,7 @@ test.register_coroutine_test( PresenceSensor.presence("present"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -183,7 +183,7 @@ test.register_coroutine_test( PresenceSensor.presence("not present"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -206,7 +206,7 @@ test.register_coroutine_test( MovementSensor.movement("noMovement"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -229,7 +229,7 @@ test.register_coroutine_test( MovementSensor.movement("noMovement"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -252,7 +252,7 @@ test.register_coroutine_test( MovementSensor.movement("noMovement"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -275,7 +275,7 @@ test.register_coroutine_test( MovementSensor.movement("noMovement"))) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 9124534739..411dddda5a 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 @@ -98,7 +98,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -129,7 +129,7 @@ test.register_coroutine_test( add_device_after_switch_over() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -160,7 +160,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -176,7 +176,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -192,7 +192,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -207,7 +207,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("not present")) ) end, { - test_init = function() end, min_api_version = 17 + test_init = function() end, min_api_version = 14 } ) 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 53843e0014..a688b8026b 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 @@ -79,7 +79,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -98,7 +98,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -152,7 +152,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -188,7 +188,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -199,7 +199,7 @@ test.register_coroutine_test( add_device_after_switch_over() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -214,7 +214,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("not present")) ) end, { - test_init = function() end, min_api_version = 17 + test_init = function() end, min_api_version = 14 } ) @@ -245,7 +245,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("not present")) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -280,7 +280,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -313,7 +313,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -334,7 +334,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -349,7 +349,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -397,7 +397,7 @@ test.register_coroutine_test( mock_device_str_interval:generate_test_message("main", capabilities.presenceSensor.presence("not present")) ) end, - { test_init = function() end, min_api_version = 17 + { test_init = function() end, min_api_version = 14 } ) @@ -413,7 +413,7 @@ test.register_coroutine_test( mock_device_nil_interval:generate_test_message("main", capabilities.presenceSensor.presence("not present")) ) end, - { test_init = function() end, min_api_version = 17 + { test_init = function() end, min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua b/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua index 30a6270b8a..5df7aea4a8 100644 --- a/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua +++ b/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua @@ -52,7 +52,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -115,7 +115,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -134,7 +134,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -153,7 +153,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -191,7 +191,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -210,7 +210,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -229,7 +229,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-range-extender/src/test/test_zigbee_extend.lua b/drivers/SmartThings/zigbee-range-extender/src/test/test_zigbee_extend.lua index 59156dd798..4fddd85ec1 100644 --- a/drivers/SmartThings/zigbee-range-extender/src/test/test_zigbee_extend.lua +++ b/drivers/SmartThings/zigbee-range-extender/src/test/test_zigbee_extend.lua @@ -30,7 +30,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-sensor/src/test/test_zigbee_sensor.lua b/drivers/SmartThings/zigbee-sensor/src/test/test_zigbee_sensor.lua index 32d4762d4b..40ea0c0b4e 100644 --- a/drivers/SmartThings/zigbee-sensor/src/test/test_zigbee_sensor.lua +++ b/drivers/SmartThings/zigbee-sensor/src/test/test_zigbee_sensor.lua @@ -121,7 +121,7 @@ test.register_coroutine_test( mock_device_generic_sensor:expect_metadata_update({profile = ZIGBEE_GENERIC_CONTACT_SENSOR_PROFILE}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -133,7 +133,7 @@ test.register_coroutine_test( mock_device_generic_sensor:expect_metadata_update({profile = ZIGBEE_GENERIC_MOTION_SENSOR_PROFILE}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_coroutine_test( mock_device_motion_illuminance:expect_metadata_update({profile = ZIGBEE_GENERIC_MOTION_ILLUMINANCE_PROFILE}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -155,7 +155,7 @@ test.register_coroutine_test( mock_device_generic_sensor:expect_metadata_update({profile = ZIGBEE_GENERIC_WATERLEAK_SENSOR_PROFILE}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -174,7 +174,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -193,7 +193,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -212,7 +212,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -231,7 +231,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -250,7 +250,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -269,7 +269,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -290,7 +290,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -311,7 +311,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -330,7 +330,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -349,7 +349,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -368,7 +368,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -387,7 +387,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -406,7 +406,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -425,7 +425,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -447,7 +447,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -466,7 +466,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -485,7 +485,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -504,7 +504,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -523,7 +523,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -542,7 +542,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -561,7 +561,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -674,7 +674,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -694,7 +694,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device_motion_sensor.id, ZoneStatusAttribute:read(mock_device_motion_sensor) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -714,7 +714,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device_motion_illuminance.id, ZoneStatusAttribute:read(mock_device_motion_illuminance) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -734,7 +734,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device_waterleak_sensor.id, ZoneStatusAttribute:read(mock_device_waterleak_sensor) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -808,7 +808,7 @@ test.register_coroutine_test( mock_device_contact_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -874,7 +874,7 @@ test.register_coroutine_test( mock_device_motion_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -940,7 +940,7 @@ test.register_coroutine_test( mock_device_motion_illuminance:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1007,7 +1007,7 @@ test.register_coroutine_test( mock_device_waterleak_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua index 4d0486c6fd..3ecee4b16d 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua @@ -259,7 +259,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -379,7 +379,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -410,7 +410,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -440,7 +440,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -456,7 +456,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -516,7 +516,7 @@ test.register_coroutine_test( get_siren_OFF_commands() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -602,7 +602,7 @@ test.register_coroutine_test( get_siren_OFF_commands() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -681,7 +681,7 @@ test.register_coroutine_test( get_siren_OFF_commands(expectedWarningDuration) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -708,7 +708,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -734,7 +734,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -779,7 +779,7 @@ test.register_coroutine_test( get_squawk_command_new_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -824,7 +824,7 @@ test.register_coroutine_test( get_squawk_command_older_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -881,7 +881,7 @@ test.register_coroutine_test( get_squawk_command_new_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -915,7 +915,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -962,7 +962,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -981,7 +981,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1000,7 +1000,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1019,7 +1019,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1038,7 +1038,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1057,7 +1057,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1103,7 +1103,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1121,7 +1121,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1182,7 +1182,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua index 70854ca7e7..cfa4e8b8a5 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua @@ -277,7 +277,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -404,7 +404,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -435,7 +435,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -465,7 +465,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -481,7 +481,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -542,7 +542,7 @@ test.register_coroutine_test( get_siren_OFF_commands() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -628,7 +628,7 @@ test.register_coroutine_test( get_siren_OFF_commands() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -707,7 +707,7 @@ test.register_coroutine_test( get_siren_OFF_commands(expectedWarningDuration) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -733,7 +733,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -759,7 +759,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -804,7 +804,7 @@ test.register_coroutine_test( get_squawk_command_new_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -849,7 +849,7 @@ test.register_coroutine_test( get_squawk_command_older_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -906,7 +906,7 @@ test.register_coroutine_test( get_squawk_command_new_fw(SquawkMode.SOUND_FOR_SYSTEM_IS_DISARMED, IaswdLevel.MEDIUM_LEVEL) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -940,7 +940,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -989,7 +989,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1014,7 +1014,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1039,7 +1039,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1058,7 +1058,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1077,7 +1077,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1096,7 +1096,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1134,7 +1134,7 @@ test.register_coroutine_test( { test_init = function() test.mock_device.add_test_device(mock_device_112) end, - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_ozom_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_ozom_siren.lua index f7b13d4895..70eccc578f 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_ozom_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_ozom_siren.lua @@ -62,7 +62,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua index c15b0e4d62..47e10b7923 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua @@ -65,7 +65,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", lifeTimeReport.lifeTimeState.normal())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -86,7 +86,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -105,7 +105,7 @@ test.register_coroutine_test( capabilities.gasDetector.gas.detected())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ test.register_coroutine_test( capabilities.gasDetector.gas.clear())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -141,7 +141,7 @@ test.register_coroutine_test( capabilities.audioMute.mute.muted())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_coroutine_test( capabilities.audioMute.mute.unmuted())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_coroutine_test( PRIVATE_MUTE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -186,7 +186,7 @@ test.register_coroutine_test( capabilities.audioMute.mute.muted())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -206,7 +206,7 @@ test.register_coroutine_test( selfCheck.selfCheckState.selfCheckCompleted())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -223,7 +223,7 @@ test.register_coroutine_test( PRIVATE_SELF_CHECK_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -243,7 +243,7 @@ test.register_coroutine_test( lifeTimeReport.lifeTimeState.endOfLife())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -261,7 +261,7 @@ test.register_coroutine_test( lifeTimeReport.lifeTimeState.normal())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -279,7 +279,7 @@ test.register_coroutine_test( sensitivityAdjustment.sensitivityAdjustment.Low())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -297,7 +297,7 @@ test.register_coroutine_test( selfCheck.selfCheckState.idle())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -315,7 +315,7 @@ test.register_coroutine_test( sensitivityAdjustment.sensitivityAdjustment.High())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -349,7 +349,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -384,7 +384,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua index eaee68d41e..31751312c7 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua @@ -51,7 +51,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -82,7 +82,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -100,7 +100,7 @@ test.register_coroutine_test( capabilities.smokeDetector.smoke.detected())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_coroutine_test( capabilities.audioMute.mute.muted())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -132,7 +132,7 @@ test.register_coroutine_test( PRIVATE_MUTE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -150,7 +150,7 @@ test.register_coroutine_test( selfCheck.selfCheckState.selfCheckCompleted())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -165,7 +165,7 @@ test.register_coroutine_test( PRIVATE_SELF_CHECK_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -183,7 +183,7 @@ test.register_coroutine_test( capabilities.smokeDetector.smoke.clear())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -201,7 +201,7 @@ test.register_coroutine_test( capabilities.audioMute.mute.unmuted())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( selfCheck.selfCheckState.idle())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -238,7 +238,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 index 7f1a7e5f3a..6512e3504f 100644 --- 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 @@ -76,7 +76,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -198,7 +198,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -217,7 +217,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -236,7 +236,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -255,7 +255,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -276,7 +276,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -303,7 +303,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -345,7 +345,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -382,7 +382,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -418,7 +418,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -455,7 +455,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -491,7 +491,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -528,7 +528,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -605,7 +605,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -624,7 +624,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -643,7 +643,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) 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 7596148a66..f9a60d24c4 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 @@ -83,7 +83,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -205,7 +205,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -224,7 +224,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -244,7 +244,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -264,7 +264,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -285,7 +285,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -312,7 +312,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -391,7 +391,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -428,7 +428,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -464,7 +464,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -501,7 +501,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -537,7 +537,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -574,7 +574,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -651,7 +651,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -693,7 +693,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -712,7 +712,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -731,7 +731,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -747,7 +747,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_zigbee_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_zigbee_smoke_detector.lua index f0f9b8259e..1afb849207 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_zigbee_smoke_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_zigbee_smoke_detector.lua @@ -45,7 +45,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -72,7 +72,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -126,7 +126,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -245,7 +245,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -284,7 +284,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua b/drivers/SmartThings/zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua index 11a251b1d7..90e1369935 100644 --- a/drivers/SmartThings/zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua +++ b/drivers/SmartThings/zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua @@ -123,7 +123,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -191,7 +191,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -210,7 +210,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -229,7 +229,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -248,7 +248,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -267,7 +267,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -294,7 +294,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -318,7 +318,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 8826ca535d..1332185693 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 @@ -103,7 +103,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -131,7 +131,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -188,7 +188,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -216,7 +216,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -236,7 +236,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -593,7 +593,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17, + min_api_version = 15, max_api_version = 19 } ) @@ -790,7 +790,7 @@ test.register_coroutine_test( test_init = function() -- no op to override auto device add on startup end, - min_api_version = 17 + min_api_version = 14 } ) @@ -818,7 +818,7 @@ test.register_coroutine_test( test_init = function() -- no op to override auto device add on startup end, - min_api_version = 17 + min_api_version = 14 } ) @@ -845,7 +845,7 @@ test.register_coroutine_test( test_init = function() -- no op to override auto device add on startup end, - min_api_version = 17 + min_api_version = 14 } ) @@ -872,7 +872,7 @@ test.register_coroutine_test( test_init = function() -- no op to override auto device add on startup end, - min_api_version = 17 + min_api_version = 14 } ) @@ -909,7 +909,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -928,7 +928,7 @@ test.register_coroutine_test( mock_device:expect_native_attr_handler_registration("colorTemperature", "colorTemperature") end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -962,7 +962,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 14.0, unit = "kWh" }))) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 cc3e86d218..f1dc9e1ea4 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 @@ -54,7 +54,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ minimum = 2700, maximum = 6000 }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -109,7 +109,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -204,7 +204,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 a80bf1dcfc..16ceec4e24 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua @@ -56,7 +56,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ minimum = 2700, maximum = 6000 }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -183,7 +183,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -211,7 +211,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -226,7 +226,7 @@ test.register_coroutine_test( RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -241,7 +241,7 @@ test.register_coroutine_test( TURN_OFF_INDICATOR_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -254,7 +254,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnTransitionTime:write(mock_device, 10) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -267,7 +267,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OffTransitionTime:write(mock_device, 10) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -302,7 +302,7 @@ test.register_coroutine_test( test_init = function() test.mock_device.add_test_device(mock_device_cwacn1) end, - min_api_version = 17 + min_api_version = 14 } ) 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 6e8ddba455..cffacc4451 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 @@ -78,7 +78,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -139,7 +139,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -156,7 +156,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -187,7 +187,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -201,7 +201,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -223,7 +223,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -258,7 +258,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -273,7 +273,7 @@ test.register_coroutine_test( RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -288,7 +288,7 @@ test.register_coroutine_test( MFG_CODE, data_types.SinglePrecisionFloat, SinglePrecisionFloat(0, 6, 0.5625)) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -317,7 +317,7 @@ test.register_coroutine_test( SimpleMetering.attributes.CurrentSummationDelivered:read(mock_standard) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -343,7 +343,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -368,7 +368,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 9476f41345..21c697ed1c 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 @@ -79,7 +79,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -121,7 +121,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -140,7 +140,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -157,7 +157,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -174,7 +174,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -188,7 +188,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -202,7 +202,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -224,7 +224,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -259,7 +259,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -274,7 +274,7 @@ test.register_coroutine_test( RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -289,7 +289,7 @@ test.register_coroutine_test( MFG_CODE, data_types.SinglePrecisionFloat, SinglePrecisionFloat(0, 6, 0.5625)) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -304,7 +304,7 @@ test.register_coroutine_test( RESTORE_TURN_OFF_INDICATOR_LIGHT_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -333,7 +333,7 @@ test.register_coroutine_test( SimpleMetering.attributes.CurrentSummationDelivered:read(mock_standard) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -359,7 +359,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -384,7 +384,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 4779073041..93e21c4844 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 @@ -56,7 +56,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -73,7 +73,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -90,7 +90,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -104,7 +104,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -140,7 +140,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -167,7 +167,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -182,7 +182,7 @@ test.register_coroutine_test( RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -197,7 +197,7 @@ test.register_coroutine_test( ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 8d3135b463..14a816183d 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 @@ -48,7 +48,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -62,7 +62,7 @@ test.register_coroutine_test( OnOff.attributes.OnOff:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -75,7 +75,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -88,7 +88,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -131,7 +131,7 @@ test.register_coroutine_test( RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -146,7 +146,7 @@ test.register_coroutine_test( ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 4e7ffaa38a..4f12a94a55 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 @@ -99,7 +99,7 @@ test.register_coroutine_test( { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ test.register_coroutine_test( { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -150,7 +150,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_base_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -164,7 +164,7 @@ test.register_coroutine_test( OnOff.attributes.OnOff:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -177,7 +177,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -190,7 +190,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.switch.switch.on())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -203,7 +203,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -216,7 +216,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.switch.switch.off())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -230,7 +230,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -244,7 +244,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device):to_endpoint(0x02) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -258,7 +258,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -272,7 +272,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device):to_endpoint(0x02) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -289,7 +289,7 @@ test.register_coroutine_test( capabilities.button.button.pushed({ state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -306,7 +306,7 @@ test.register_coroutine_test( capabilities.button.button.pushed({ state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -323,7 +323,7 @@ test.register_coroutine_test( RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -338,7 +338,7 @@ test.register_coroutine_test( CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0) }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 b6eb4a320e..ffe37bb293 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 @@ -86,7 +86,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -121,7 +121,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -138,7 +138,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -155,7 +155,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -189,7 +189,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -203,7 +203,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -217,7 +217,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device):to_endpoint(0x02) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -231,7 +231,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -245,7 +245,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device):to_endpoint(0x02) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -262,7 +262,7 @@ test.register_coroutine_test( capabilities.button.button.pushed({ state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -279,7 +279,7 @@ test.register_coroutine_test( capabilities.button.button.pushed({ state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -301,7 +301,7 @@ test.register_coroutine_test( AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -328,7 +328,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -343,7 +343,7 @@ test.register_coroutine_test( RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -358,7 +358,7 @@ test.register_coroutine_test( CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0) }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 a0a7e9b924..7efbeefe78 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 @@ -60,7 +60,7 @@ test.register_coroutine_test( mock_device:expect_native_attr_handler_registration("switch", "switch") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -73,7 +73,7 @@ test.register_coroutine_test( mock_device:expect_native_attr_handler_registration("switch", "switch") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -86,7 +86,7 @@ test.register_coroutine_test( mock_device:expect_native_attr_handler_registration("switch", "switch") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ test.register_coroutine_test( mock_device:expect_native_attr_handler_registration("switch", "switch") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -127,7 +127,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device):to_endpoint(0x02) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -141,7 +141,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -155,7 +155,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device):to_endpoint(0x02) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ test.register_coroutine_test( capabilities.button.button.pushed({ state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -189,7 +189,7 @@ test.register_coroutine_test( capabilities.button.button.pushed({ state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -204,7 +204,7 @@ test.register_coroutine_test( CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0) }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 3adf489318..4b16a32bb5 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aurora_relay.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aurora_relay.lua @@ -42,7 +42,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, SimpleMetering.attributes.InstantaneousDemand:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -69,7 +69,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 36ac4d5fa0..296ab9a904 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 @@ -43,7 +43,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 189f64715a..569c5c6b98 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 @@ -33,7 +33,7 @@ test.register_coroutine_test("zwave_device_handled", function() test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -45,7 +45,7 @@ test.register_message_test( message = { mock_device.id, { capability = "switch", component = "main", command = "on", args = { } } } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 66af249b0c..b8cfb47e41 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_cree_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_cree_bulb.lua @@ -66,7 +66,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -77,7 +77,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main",capabilities.switchLevel.level(100))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -90,7 +90,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -104,7 +104,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 d1bebb68d5..5651b0086b 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 @@ -143,7 +143,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -238,7 +238,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -269,7 +269,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -291,7 +291,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 f28bb56e37..86d654a053 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 @@ -130,7 +130,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -157,7 +157,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -184,7 +184,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -214,7 +214,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -244,7 +244,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -263,7 +263,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -282,7 +282,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 index b410872075..f1e568bc7f 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_frient_IO_module.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_frient_IO_module.lua @@ -522,7 +522,7 @@ test.register_coroutine_test( assert(parent_native, "expected parent device to register native switch handler for input 3") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -556,7 +556,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -616,7 +616,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -640,7 +640,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -691,7 +691,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) 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 2a86e1ed19..90cd1524bd 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_frient_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_frient_switch.lua @@ -81,7 +81,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_message_test("Current divisor, multiplier, summation should be han }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -153,7 +153,7 @@ test.register_coroutine_test("Refresh command should read all necessary attribut ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -180,7 +180,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -207,7 +207,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -239,7 +239,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -264,7 +264,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -277,7 +277,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -341,7 +341,7 @@ test.register_coroutine_test("doConfigure should send bind request, read attribu mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -357,7 +357,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.powerSource.powerSource.unknown())) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 d35bbf4f35..94104f74b7 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 @@ -47,7 +47,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -64,7 +64,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 50) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -79,7 +79,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -112,7 +112,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -149,7 +149,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 0) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -189,7 +189,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 50) }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 7982403b8c..13184c37d3 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_hanssem_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_hanssem_switch.lua @@ -128,7 +128,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -161,7 +161,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -194,7 +194,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -227,7 +227,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -260,7 +260,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -293,7 +293,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -326,7 +326,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -359,7 +359,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -392,7 +392,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -425,7 +425,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -458,7 +458,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -491,7 +491,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -523,7 +523,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -550,7 +550,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -577,7 +577,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -604,7 +604,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -631,7 +631,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -658,7 +658,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -685,7 +685,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -712,7 +712,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -739,7 +739,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -766,7 +766,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -793,7 +793,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -820,7 +820,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -870,7 +870,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 index b2b7019dd0..3dcee92797 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn.lua @@ -105,7 +105,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -168,7 +168,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -192,7 +192,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -289,7 +289,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -321,7 +321,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -359,7 +359,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -382,7 +382,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -405,7 +405,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -427,7 +427,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -448,7 +448,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -497,7 +497,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -556,7 +556,7 @@ test.register_coroutine_test( mock_inovelli_vzm30_sn:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 index 3c839f571f..8be1b0ee0a 100644 --- 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 @@ -91,7 +91,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -146,7 +146,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -180,7 +180,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -239,7 +239,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -302,7 +302,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -365,7 +365,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 index 6c6d633d70..fa6bf5202f 100644 --- 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 @@ -63,7 +63,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -88,7 +88,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -112,7 +112,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -160,7 +160,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -184,7 +184,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -208,7 +208,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -227,7 +227,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm30_sn:generate_info_changed({preferences = {notificationChild = true}})) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 index 6c0da0b158..1549b80a41 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn.lua @@ -73,7 +73,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -97,7 +97,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -121,7 +121,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -218,7 +218,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -250,7 +250,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -288,7 +288,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -310,7 +310,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -332,7 +332,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -381,7 +381,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -433,7 +433,7 @@ test.register_coroutine_test( mock_inovelli_vzm31_sn:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 index fd7f718ff9..a3b1717c04 100644 --- 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 @@ -80,7 +80,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -135,7 +135,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -228,7 +228,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -291,7 +291,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -354,7 +354,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 index 3823d4e010..56f526c2cd 100644 --- 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 @@ -52,7 +52,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -77,7 +77,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -101,7 +101,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -149,7 +149,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -197,7 +197,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -216,7 +216,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm31_sn:generate_info_changed({preferences = {notificationChild = true}})) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 index ee21044bea..80f7a765a7 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn.lua @@ -84,7 +84,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -142,7 +142,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -166,7 +166,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -193,7 +193,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -263,7 +263,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -295,7 +295,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -333,7 +333,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -356,7 +356,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -379,7 +379,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -402,7 +402,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -425,7 +425,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -445,7 +445,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -494,7 +494,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -549,7 +549,7 @@ test.register_coroutine_test( mock_inovelli_vzm32_sn:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 index 7173463e16..544ee73aea 100644 --- 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 @@ -80,7 +80,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -135,7 +135,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -228,7 +228,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -291,7 +291,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -354,7 +354,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 index 9939926ff3..a630e54efe 100644 --- 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 @@ -52,7 +52,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -77,7 +77,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -101,7 +101,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm32_sn:generate_info_changed({preferences = {notificationChild = true}})) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -174,7 +174,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 6d1b78e806..14df41e010 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua @@ -56,7 +56,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -83,7 +83,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -126,7 +126,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -155,7 +155,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 5c7df6e762..5a2e92e8a3 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua @@ -50,7 +50,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -70,7 +70,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -90,7 +90,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -110,7 +110,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -130,7 +130,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -150,7 +150,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -190,7 +190,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -212,7 +212,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -232,7 +232,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -252,7 +252,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -272,7 +272,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -292,7 +292,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -312,7 +312,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -332,7 +332,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -352,7 +352,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -365,7 +365,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device):to_endpoint(0x02) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -378,7 +378,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device):to_endpoint(0x03) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -391,7 +391,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device):to_endpoint(0x04) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -404,7 +404,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device):to_endpoint(0x05) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -417,7 +417,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device):to_endpoint(0x06) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -430,7 +430,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device):to_endpoint(0x07) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -443,7 +443,7 @@ test.register_coroutine_test( OnOff.server.commands.On(mock_device):to_endpoint(0x08) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -456,7 +456,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device):to_endpoint(0x01) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -469,7 +469,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device):to_endpoint(0x02) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -482,7 +482,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device):to_endpoint(0x03) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -495,7 +495,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device):to_endpoint(0x04) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -508,7 +508,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device):to_endpoint(0x05) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -521,7 +521,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device):to_endpoint(0x06) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -534,7 +534,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device):to_endpoint(0x07) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -547,7 +547,7 @@ test.register_coroutine_test( OnOff.server.commands.Off(mock_device):to_endpoint(0x08) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -567,7 +567,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua b/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua index ebdcae468e..0968fa7c5e 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_ledvance_metering_plug.lua @@ -55,7 +55,7 @@ test.register_coroutine_test( assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 100) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -70,7 +70,7 @@ test.register_coroutine_test( assert(mock_device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 1000) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -85,7 +85,7 @@ test.register_coroutine_test( assert(mock_device_eu_em_t:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 100) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -100,7 +100,7 @@ test.register_coroutine_test( assert(mock_device_eu_em_t:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) == 1000) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 b6b5adade5..9e1ccb8d5e 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch.lua @@ -39,7 +39,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -59,7 +59,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) test.register_message_test( @@ -78,7 +78,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -98,7 +98,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 2ec4c6eb86..1d5d8b712a 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 @@ -105,7 +105,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -138,7 +138,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -171,7 +171,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -204,7 +204,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -237,7 +237,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -270,7 +270,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -297,7 +297,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -324,7 +324,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -351,7 +351,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -378,7 +378,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -405,7 +405,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -432,7 +432,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -511,7 +511,7 @@ test.register_coroutine_test( mock_base_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -553,7 +553,7 @@ test.register_coroutine_test( test_init = function() test.mock_device.add_test_device(mock_non_mns_device) end, - min_api_version = 17 + min_api_version = 14 } ) 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 788c5477c5..5df2466cb2 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 @@ -117,7 +117,7 @@ test.register_coroutine_test( test_init = function() -- no op to avoid auto device add and immediate init event on driver startup end, - min_api_version = 17 + min_api_version = 14 } ) @@ -141,7 +141,7 @@ test.register_coroutine_test( test_init = function() -- no op to avoid auto device add and immediate init event on driver startup end, - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -203,7 +203,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -231,7 +231,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -259,7 +259,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -287,7 +287,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -315,7 +315,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -345,7 +345,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -375,7 +375,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -402,7 +402,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -429,7 +429,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -456,7 +456,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -483,7 +483,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -509,7 +509,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -528,7 +528,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 7a1af4a260..062bd8fc6c 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 @@ -47,7 +47,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -75,7 +75,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -103,7 +103,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -132,7 +132,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -175,7 +175,7 @@ test.register_coroutine_test( mock_simple_device:expect_metadata_update({provisioning_state = "PROVISIONED"}) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 8727de3c35..4d846e8380 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 @@ -65,7 +65,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main",capabilities.switchLevel.level(100))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -107,7 +107,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -138,7 +138,7 @@ test.register_message_test( }, { inner_block_ordering = "RELAXED", - min_api_version = 17 + min_api_version = 14 } ) 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 29a53accff..a09436c9ce 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_osram_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_osram_light.lua @@ -65,7 +65,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main",capabilities.switchLevel.level(100))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -107,7 +107,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 06c20b34e5..bac6c6715c 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_rgb_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_rgb_bulb.lua @@ -67,7 +67,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -114,7 +114,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -141,7 +141,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -168,7 +168,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -200,7 +200,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 4a83d8b920..604780cd9c 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_rgbw_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_rgbw_bulb.lua @@ -165,7 +165,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -221,7 +221,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -248,7 +248,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -275,7 +275,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -304,7 +304,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -330,7 +330,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -346,7 +346,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 cfbfa42b50..462a607694 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 @@ -56,7 +56,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -83,7 +83,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -111,7 +111,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -131,7 +131,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -152,7 +152,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 1bfa694b5b..cf64a89595 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 @@ -56,7 +56,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -83,7 +83,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -111,7 +111,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -132,7 +132,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 80241b4ca9..7d633129ec 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 @@ -143,7 +143,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -238,7 +238,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -269,7 +269,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 aa4e56653c..6ba6eef2b2 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 @@ -69,7 +69,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -83,7 +83,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -97,7 +97,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -119,7 +119,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -141,7 +141,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -163,7 +163,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, IASZone.attributes.ZoneStatus:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -190,7 +190,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -217,7 +217,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) 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 4de1a70303..a16d47a4b0 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_sinope_dimmer.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_sinope_dimmer.lua @@ -73,7 +73,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -109,7 +109,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -184,7 +184,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -214,7 +214,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -240,7 +240,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -280,7 +280,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -316,7 +316,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -360,7 +360,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) 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 9d9b33df99..28bd04a664 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_sinope_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_sinope_switch.lua @@ -57,7 +57,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -84,7 +84,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -111,7 +111,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) 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 4ff3fb2afa..2a26c95e3c 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_switch_power.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_switch_power.lua @@ -125,7 +125,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -180,7 +180,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -242,7 +242,7 @@ test.register_coroutine_test( mock_aurora_relay_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -312,7 +312,7 @@ test.register_coroutine_test( mock_vimar_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 ac3a78a982..7754d6281e 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua @@ -78,7 +78,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 b6b5adade5..9e1ccb8d5e 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 @@ -39,7 +39,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -59,7 +59,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) test.register_message_test( @@ -78,7 +78,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -98,7 +98,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 8bbe6bad68..f28334b661 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua @@ -182,7 +182,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -215,7 +215,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -248,7 +248,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -281,7 +281,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -314,7 +314,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -347,7 +347,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -380,7 +380,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -413,7 +413,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -428,7 +428,7 @@ test.register_coroutine_test( 0x6000, 0x1235, data_types.Uint8, 0x01) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -443,7 +443,7 @@ test.register_coroutine_test( 0x6000, 0x1235, data_types.Uint8, 0x00) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -475,7 +475,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -502,7 +502,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -529,7 +529,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -556,7 +556,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -584,7 +584,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -611,7 +611,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -638,7 +638,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -665,7 +665,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -682,7 +682,7 @@ test.register_coroutine_test( ))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -698,7 +698,7 @@ test.register_coroutine_test( ))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -714,7 +714,7 @@ test.register_coroutine_test( ))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -730,7 +730,7 @@ test.register_coroutine_test( ))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -794,7 +794,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -808,7 +808,7 @@ test.register_coroutine_test( capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 8a0db9d65c..1524a8aa85 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 @@ -144,7 +144,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -239,7 +239,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -270,7 +270,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua index 5f29f49c2b..889f4f15d6 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua @@ -101,7 +101,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -134,7 +134,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -167,7 +167,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -201,7 +201,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -234,7 +234,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -267,7 +267,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -300,7 +300,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -327,7 +327,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -354,7 +354,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -384,7 +384,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -411,7 +411,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -438,7 +438,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 d2b73dd4b1..cfcab8e20e 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 @@ -54,7 +54,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -81,7 +81,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -100,7 +100,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -127,7 +127,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -146,7 +146,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 e265fa13dc..1b4d46446b 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 @@ -66,7 +66,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -85,7 +85,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -153,7 +153,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) 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 a616744baf..ff732a9589 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 @@ -49,7 +49,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -73,7 +73,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 f6842b077f..20f6d458a2 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 @@ -66,7 +66,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -115,7 +115,7 @@ test.register_coroutine_test( test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "polling") end, - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -156,7 +156,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -197,7 +197,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -238,7 +238,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -281,7 +281,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) 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 2e688c0a86..7738dc659b 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer.lua @@ -67,7 +67,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -78,7 +78,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main",capabilities.switchLevel.level(100))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -91,7 +91,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -157,7 +157,7 @@ test.register_coroutine_test( test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(5*60, "interval", "polling") end, - min_api_version = 17 + min_api_version = 14 } ) @@ -171,7 +171,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 ec10a66a62..195e0eb469 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 @@ -67,7 +67,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -80,7 +80,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -93,7 +93,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -115,7 +115,7 @@ test.register_coroutine_test( test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "polling") end, - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -157,7 +157,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -178,7 +178,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 bbbda5660d..2eeffc86aa 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 @@ -85,7 +85,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -100,7 +100,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -115,7 +115,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -139,7 +139,7 @@ test.register_coroutine_test( test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "polling") end, - min_api_version = 17 + min_api_version = 14 } ) @@ -162,7 +162,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -185,7 +185,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -208,7 +208,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -286,7 +286,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -365,7 +365,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -406,7 +406,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -447,7 +447,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -474,7 +474,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentY:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -489,7 +489,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.saturation(65))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -504,7 +504,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.saturation(65))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -519,7 +519,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.saturation(65))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -534,7 +534,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorControl.saturation(65))) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 4e2ad43b06..0a5d739a48 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 @@ -195,7 +195,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -248,7 +248,7 @@ test.register_coroutine_test( test.mock_device.add_test_device(mock_device) test.timer.__create_and_queue_test_time_advance_timer(5*60, "interval", "polling") end, - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -301,7 +301,7 @@ test.register_coroutine_test( end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -354,7 +354,7 @@ test.register_coroutine_test( end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -407,7 +407,7 @@ test.register_coroutine_test( end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -462,7 +462,7 @@ test.register_coroutine_test( end, { - min_api_version = 17, + min_api_version = 14, max_api_version = 19 } ) @@ -491,7 +491,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentHue:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -519,7 +519,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -549,7 +549,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.CurrentSaturation:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua index 8b84e987a0..d6ae23b544 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua @@ -77,7 +77,7 @@ test.register_coroutine_test( PRIVATE_THERM0STAT_VALVE_DETECTION_SWITCH_ID, MFG_CODE, data_types.Uint8, 0x01) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -92,7 +92,7 @@ test.register_coroutine_test( PRIVATE_ANTIFREEZE_MODE_TEMPERATURE_SETTING_ID, MFG_CODE, data_types.Uint32, 500) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_coroutine_test( capabilities.hardwareFault.hardwareFault.clear())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -158,7 +158,7 @@ test.register_coroutine_test( valveCalibration.calibrationState.calibrationFailure())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -190,7 +190,7 @@ test.register_coroutine_test( invisibleCapabilities.invisibleCapabilities({"thermostatHeatingSetpoint"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -258,7 +258,7 @@ test.register_coroutine_test( invisibleCapabilities.invisibleCapabilities({"thermostatHeatingSetpoint"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -286,7 +286,7 @@ test.register_coroutine_test( capabilities.lock.lock.locked())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -305,7 +305,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -366,7 +366,7 @@ test.register_coroutine_test( PRIVATE_VALVE_SWITCH_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -380,7 +380,7 @@ test.register_coroutine_test( PRIVATE_VALVE_CALIBRATION_ID, MFG_CODE, data_types.Uint8, 0x01) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -395,7 +395,7 @@ test.register_coroutine_test( PRIVATE_CHILD_LOCK_ID, MFG_CODE, data_types.Uint8, 0x01) }) end, { - min_api_version = 17 + min_api_version = 14 } ) --]] @@ -470,7 +470,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_centralite_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_centralite_thermostat.lua index b1942290db..fdf30687aa 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_centralite_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_centralite_thermostat.lua @@ -48,7 +48,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -68,7 +68,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -88,7 +88,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -119,7 +119,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -150,7 +150,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_danfoss_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_danfoss_thermostat.lua index 43fe7c3c82..33bda1be3e 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_danfoss_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_danfoss_thermostat.lua @@ -44,7 +44,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -55,7 +55,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device_danfoss:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.0, unit = "C"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -66,7 +66,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device_danfoss:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 21.0, unit = "C"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device_danfoss.id, Thermostat.attributes.OccupiedCoolingSetpoint:build_test_attr_report(mock_device_danfoss, 2100) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -121,7 +121,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_fidure_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_fidure_thermostat.lua index 469f36dec9..25ffd9a224 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_fidure_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_fidure_thermostat.lua @@ -63,7 +63,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -78,7 +78,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_leviton_rc.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_leviton_rc.lua index 8fc6b2944f..7101150397 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_leviton_rc.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_leviton_rc.lua @@ -53,7 +53,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -70,7 +70,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.LocalTemperature:read(mock_device):to_endpoint(ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -81,7 +81,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 21.0, unit = "C"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -92,7 +92,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.off())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -115,7 +115,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatFanMode.thermostatFanMode.auto())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -130,7 +130,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({value = 21.0, unit = "C"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 21.0, unit = "C"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:build_test_attr_report(mock_device, 2100) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_coroutine_test( test.socket.zigbee:__queue_receive({ mock_device.id, Thermostat.attributes.OccupiedCoolingSetpoint:build_test_attr_report(mock_device, 2100) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -185,7 +185,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 2100):to_endpoint(ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -197,7 +197,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, 2100):to_endpoint(ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -208,7 +208,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.SystemMode:write(mock_device, 3):to_endpoint(ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, FanControl.attributes.FanMode:write(mock_device, 5):to_endpoint(ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -253,7 +253,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, Thermostat.attributes.LocalTemperature:read(mock_device):to_endpoint(ENDPOINT) }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_popp_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_popp_thermostat.lua index d1ae50109d..b351d725bd 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_popp_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_popp_thermostat.lua @@ -69,7 +69,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -100,7 +100,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -122,7 +122,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -168,7 +168,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -190,7 +190,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -254,7 +254,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -291,7 +291,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -319,7 +319,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -356,7 +356,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -393,7 +393,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -410,7 +410,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.eco())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -462,7 +462,7 @@ test.register_coroutine_test( )}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -479,7 +479,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -496,7 +496,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua index 71264c3a99..5a0f8591ed 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua @@ -102,7 +102,7 @@ test.register_coroutine_test("Configure should configure all necessary attribute }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ test.register_coroutine_test("Refresh should read all necessary attributes", fun end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -139,7 +139,7 @@ test.register_coroutine_test("Temperature reporting should create the appropriat }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -156,7 +156,7 @@ test.register_coroutine_test("Thermostat mode reporting should create the approp .thermostatMode.heat())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ test.register_coroutine_test("ControlSequenceOfOperation reporting should create }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -187,7 +187,7 @@ test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create th }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -202,7 +202,7 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 2100)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -218,7 +218,7 @@ test.register_coroutine_test("Setting the thermostat mode to away should generat Thermostat.attributes.SystemMode.OFF)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -234,7 +234,7 @@ test.register_coroutine_test("Setting the thermostat mode to heat should generat Thermostat.attributes.SystemMode.HEAT)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -257,7 +257,7 @@ test.register_coroutine_test("Refresh should read all necessary attributes with end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -271,7 +271,7 @@ test.register_coroutine_test("Temperature reporting should create the appropriat }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -288,7 +288,7 @@ test.register_coroutine_test("Thermostat mode reporting should create the approp .thermostatMode.heat())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -304,7 +304,7 @@ test.register_coroutine_test("ControlSequenceOfOperation reporting should create }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -319,7 +319,7 @@ test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create th }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -334,7 +334,7 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_first_child, 2100)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -350,7 +350,7 @@ test.register_coroutine_test("Setting the thermostat mode to away should generat Thermostat.attributes.SystemMode.OFF)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -366,7 +366,7 @@ test.register_coroutine_test("Setting the thermostat mode to heat should generat Thermostat.attributes.SystemMode.HEAT)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -389,7 +389,7 @@ test.register_coroutine_test("Refresh should read all necessary attributes with end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -403,7 +403,7 @@ test.register_coroutine_test("Temperature reporting should create the appropriat }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -420,7 +420,7 @@ test.register_coroutine_test("Thermostat mode reporting should create the approp .thermostatMode.heat())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -436,7 +436,7 @@ test.register_coroutine_test("ControlSequenceOfOperation reporting should create }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -451,7 +451,7 @@ test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create th }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -466,7 +466,7 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_second_child, 2100)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -482,7 +482,7 @@ test.register_coroutine_test("Setting the thermostat mode to away should generat Thermostat.attributes.SystemMode.OFF)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -498,7 +498,7 @@ test.register_coroutine_test("Setting the thermostat mode to heat should generat Thermostat.attributes.SystemMode.HEAT)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -521,7 +521,7 @@ test.register_coroutine_test("Refresh should read all necessary attributes with end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -535,7 +535,7 @@ test.register_coroutine_test("Temperature reporting should create the appropriat }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -552,7 +552,7 @@ test.register_coroutine_test("Thermostat mode reporting should create the approp .thermostatMode.heat())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -568,7 +568,7 @@ test.register_coroutine_test("ControlSequenceOfOperation reporting should create }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -583,7 +583,7 @@ test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create th }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -598,7 +598,7 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_third_child, 2100)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -614,7 +614,7 @@ test.register_coroutine_test("Setting the thermostat mode to away should generat Thermostat.attributes.SystemMode.OFF)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -630,7 +630,7 @@ test.register_coroutine_test("Setting the thermostat mode to heat should generat Thermostat.attributes.SystemMode.HEAT)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -653,7 +653,7 @@ test.register_coroutine_test("Refresh should read all necessary attributes with end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -667,7 +667,7 @@ test.register_coroutine_test("Temperature reporting should create the appropriat }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -684,7 +684,7 @@ test.register_coroutine_test("Thermostat mode reporting should create the approp .thermostatMode.heat())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -700,7 +700,7 @@ test.register_coroutine_test("ControlSequenceOfOperation reporting should create }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -715,7 +715,7 @@ test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create th }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -730,7 +730,7 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_forth_child, 2100)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -746,7 +746,7 @@ test.register_coroutine_test("Setting the thermostat mode to away should generat Thermostat.attributes.SystemMode.OFF)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -762,7 +762,7 @@ test.register_coroutine_test("Setting the thermostat mode to heat should generat Thermostat.attributes.SystemMode.HEAT)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -785,7 +785,7 @@ test.register_coroutine_test("Refresh should read all necessary attributes with end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -799,7 +799,7 @@ test.register_coroutine_test("Temperature reporting should create the appropriat }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -816,7 +816,7 @@ test.register_coroutine_test("Thermostat mode reporting should create the approp .thermostatMode.heat())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -832,7 +832,7 @@ test.register_coroutine_test("ControlSequenceOfOperation reporting should create }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -847,7 +847,7 @@ test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create th }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -862,7 +862,7 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_fifth_child, 2100)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -878,7 +878,7 @@ test.register_coroutine_test("Setting the thermostat mode to away should generat Thermostat.attributes.SystemMode.OFF)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -894,7 +894,7 @@ test.register_coroutine_test("Setting the thermostat mode to heat should generat Thermostat.attributes.SystemMode.HEAT)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -913,7 +913,7 @@ test.register_coroutine_test("ThermostatRunningState reporting shoulb create the capabilities.thermostatOperatingState.thermostatOperatingState({value="fan only"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1300_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1300_thermostat.lua index 271c070df3..8fc50e9706 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1300_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1300_thermostat.lua @@ -47,7 +47,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -66,7 +66,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -198,7 +198,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1400_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1400_thermostat.lua index 55ba5be56c..5d7a679e92 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1400_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_th1400_thermostat.lua @@ -47,7 +47,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -66,7 +66,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -208,7 +208,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua index 2506ca0f2f..a85e50971f 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua @@ -47,7 +47,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -66,7 +66,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -182,7 +182,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -212,7 +212,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua index 470bb1378d..da890c2b11 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua @@ -59,7 +59,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -78,7 +78,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -126,7 +126,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -150,7 +150,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -188,7 +188,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -210,7 +210,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -242,7 +242,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", ThermostatOperatingState.thermostatOperatingState("idle"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -265,7 +265,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", ThermostatMode.thermostatMode.heat())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -298,7 +298,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -331,7 +331,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -423,7 +423,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -508,7 +508,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -531,7 +531,7 @@ test.register_coroutine_test("Setting the heating setpoint should generate the a }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -563,7 +563,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) test.register_coroutine_test( @@ -588,7 +588,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -615,7 +615,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_thermostat.lua index 208949090c..bc0b3933c0 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_thermostat.lua @@ -51,7 +51,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -75,7 +75,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -133,7 +133,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -167,7 +167,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -190,7 +190,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -213,7 +213,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -237,7 +237,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -256,7 +256,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -275,7 +275,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -294,7 +294,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -313,7 +313,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -355,7 +355,7 @@ test.register_coroutine_test( test.socket.zigbee:__set_channel_ordering("relaxed") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -400,7 +400,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -440,7 +440,7 @@ test.register_coroutine_test( test.socket.zigbee:__set_channel_ordering("relaxed") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -485,7 +485,7 @@ test.register_coroutine_test( mock_device_maestro:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -500,7 +500,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({preferences = { lock = 1 } })) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -513,7 +513,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -551,7 +551,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_vimar_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_vimar_thermostat.lua index afbe34524b..9ba03896e8 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_vimar_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_vimar_thermostat.lua @@ -87,7 +87,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -149,7 +149,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -188,7 +188,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -227,7 +227,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -269,7 +269,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -311,7 +311,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -375,7 +375,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -415,7 +415,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -454,7 +454,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -496,7 +496,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -538,7 +538,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -602,7 +602,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -637,7 +637,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -671,7 +671,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -730,7 +730,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -790,7 +790,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -820,7 +820,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -850,7 +850,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua index c16757366d..ddd240a0e0 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua @@ -49,7 +49,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -69,7 +69,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) test.register_message_test( @@ -88,7 +88,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -108,7 +108,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -149,7 +149,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -211,7 +211,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -291,7 +291,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -426,7 +426,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -461,7 +461,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -476,7 +476,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -519,7 +519,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -534,7 +534,7 @@ test.register_coroutine_test( assert(mock_device:get_field("minHeatSetpoint") == 500, "minHeatSetpoint field not stored correctly") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -549,7 +549,7 @@ test.register_coroutine_test( assert(mock_device:get_field("maxHeatSetpoint") == 3500, "maxHeatSetpoint field not stored correctly") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -564,7 +564,7 @@ test.register_coroutine_test( assert(mock_device:get_field("minCoolSetpoint") == 1600, "minCoolSetpoint field not stored correctly") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -579,7 +579,7 @@ test.register_coroutine_test( assert(mock_device:get_field("maxCoolSetpoint") == 3200, "maxCoolSetpoint field not stored correctly") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -617,7 +617,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -655,7 +655,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -691,7 +691,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -727,7 +727,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -884,7 +884,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat.lua index e954e05a8a..40f8a58e2d 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat.lua @@ -38,7 +38,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -58,7 +58,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -85,7 +85,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -105,7 +105,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -124,7 +124,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -168,7 +168,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -188,7 +188,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -250,7 +250,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -275,7 +275,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -300,7 +300,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -320,7 +320,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -361,7 +361,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -381,7 +381,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -412,7 +412,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -443,7 +443,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -474,7 +474,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -505,7 +505,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -535,7 +535,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -566,7 +566,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -597,7 +597,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -643,7 +643,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -785,7 +785,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -911,7 +911,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -931,7 +931,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat_wallhero_3in1.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat_wallhero_3in1.lua index a93a3a36ee..b258d14727 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat_wallhero_3in1.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_zigbee_thermostat_wallhero_3in1.lua @@ -212,7 +212,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -233,7 +233,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -253,7 +253,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -273,7 +273,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -293,7 +293,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -313,7 +313,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -333,7 +333,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -353,7 +353,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -373,7 +373,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -393,7 +393,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -413,7 +413,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -433,7 +433,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -453,7 +453,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -473,7 +473,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -493,7 +493,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -513,7 +513,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -533,7 +533,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -553,7 +553,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -573,7 +573,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -593,7 +593,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -613,7 +613,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -633,7 +633,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -652,7 +652,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -671,7 +671,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -690,7 +690,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -709,7 +709,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -728,7 +728,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -747,7 +747,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -766,7 +766,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -785,7 +785,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -804,7 +804,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -823,7 +823,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -842,7 +842,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -861,7 +861,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -880,7 +880,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -899,7 +899,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -918,7 +918,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -937,7 +937,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -956,7 +956,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -975,7 +975,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -994,7 +994,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1013,7 +1013,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1233,7 +1233,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua index d9db636eab..013e79a828 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua @@ -47,7 +47,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -68,7 +68,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -87,7 +87,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -106,7 +106,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -126,7 +126,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -146,7 +146,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -166,7 +166,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -186,7 +186,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -205,7 +205,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -224,7 +224,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -272,7 +272,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -314,7 +314,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -353,7 +353,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua index 9ba28f751e..c081d0ab40 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua @@ -49,7 +49,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -97,7 +97,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -131,7 +131,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(20)) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -142,7 +142,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(0)) ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -153,7 +153,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(100)) ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_zigbee_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_zigbee_valve.lua index 72038fdb37..36eb70de1d 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_zigbee_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_zigbee_valve.lua @@ -47,7 +47,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -68,7 +68,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -87,7 +87,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -107,7 +107,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -127,7 +127,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -147,7 +147,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -167,7 +167,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -186,7 +186,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -205,7 +205,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -254,7 +254,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -296,7 +296,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -335,7 +335,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-vent/src/test/test_zigbee_vent.lua b/drivers/SmartThings/zigbee-vent/src/test/test_zigbee_vent.lua index 26420c997b..d4781ddfd3 100644 --- a/drivers/SmartThings/zigbee-vent/src/test/test_zigbee_vent.lua +++ b/drivers/SmartThings/zigbee-vent/src/test/test_zigbee_vent.lua @@ -61,7 +61,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -81,7 +81,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -101,7 +101,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -119,7 +119,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, Level.attributes.CurrentLevel:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -138,7 +138,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -162,7 +162,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -241,7 +241,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -299,7 +299,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -320,7 +320,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, Level.commands.MoveToLevelWithOnOff(mock_device, math.floor(83 / 100 * 254), 0xFFFF)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -333,7 +333,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(50))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -345,7 +345,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.atmosphericPressureMeasurement.atmosphericPressure({value = 1, unit = "kPa"}))) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 51be73b5b3..06f558bdcd 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 @@ -53,7 +53,7 @@ test.register_coroutine_test( data_types.Uint8, 1) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -80,7 +80,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -107,7 +107,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -126,7 +126,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 d92293d30e..a5acbfb81e 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 @@ -48,7 +48,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -110,7 +110,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -129,7 +129,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -167,7 +167,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -186,7 +186,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -213,7 +213,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -240,7 +240,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -272,7 +272,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 ba6361bc9d..6c0881c715 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 @@ -48,7 +48,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -135,7 +135,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -154,7 +154,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -192,7 +192,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -246,7 +246,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -278,7 +278,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 140608bfcc..47c17f4c2f 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 @@ -42,7 +42,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.waterSensor.water.wet())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -54,7 +54,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.waterSensor.water.dry())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -66,7 +66,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.waterSensor.water.dry())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -78,7 +78,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.waterSensor.water.dry())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -90,7 +90,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.waterSensor.water.dry())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -103,7 +103,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 7b28197ad6..bc941ebfdb 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 @@ -48,7 +48,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -110,7 +110,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -129,7 +129,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -175,7 +175,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -202,7 +202,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -234,7 +234,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 1e7c70eba1..5c2764ebe7 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 @@ -45,7 +45,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 2a6fd6be79..c7b8f35247 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 @@ -48,7 +48,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -67,7 +67,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -86,7 +86,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -105,7 +105,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -124,7 +124,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -151,7 +151,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -170,7 +170,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -313,7 +313,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -376,7 +376,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 e550d7d246..bde19b9206 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 @@ -48,7 +48,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -110,7 +110,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -143,7 +143,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -197,7 +197,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -229,7 +229,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 e3946056f4..8056969142 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 @@ -43,7 +43,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, Basic.attributes.ApplicationVersion:read(mock_device)}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -56,7 +56,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -81,7 +81,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -106,7 +106,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -131,7 +131,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -156,7 +156,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 bf6bb7c4a9..bda51e2655 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 @@ -46,7 +46,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -73,7 +73,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -100,7 +100,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -127,7 +127,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -154,7 +154,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -184,7 +184,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -203,7 +203,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -346,7 +346,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -409,7 +409,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 a8e951661f..a718ea0b75 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 @@ -54,7 +54,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -78,7 +78,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -226,7 +226,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -289,7 +289,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua b/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua index 53118b5bff..fdb72d3d0c 100644 --- a/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua +++ b/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua @@ -48,7 +48,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.mode.mode("0"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -67,7 +67,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -86,7 +86,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -105,7 +105,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -124,7 +124,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -143,7 +143,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -162,7 +162,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -181,7 +181,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -198,7 +198,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(10))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -215,7 +215,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(30))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -232,7 +232,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.mode.mode("4"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -244,7 +244,7 @@ test.register_coroutine_test( THIRDREALITY_WATERING_CLUSTER, WATERING_TIME_ATTR, 0x1407, data_types.Uint16, 20) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -256,7 +256,7 @@ test.register_coroutine_test( THIRDREALITY_WATERING_CLUSTER, WATERING_INTERVAL_ATTR, 0x1407, data_types.Uint8, 2) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -275,7 +275,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -294,7 +294,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -311,7 +311,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(0))) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua index 26a08b1a84..c64116d7fa 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua @@ -56,7 +56,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -78,7 +78,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -100,7 +100,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -133,7 +133,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -186,7 +186,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -213,7 +213,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -232,7 +232,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -273,7 +273,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -312,7 +312,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -351,7 +351,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) 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 index 71dc5c81a4..2653d47555 100644 --- 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 @@ -45,7 +45,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, read_0x0000_messge}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -69,7 +69,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_coroutine_test( capabilities.windowShade.windowShade.open())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -154,7 +154,7 @@ test.register_coroutine_test( capabilities.windowShade.windowShade.opening())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ test.register_coroutine_test( capabilities.windowShade.windowShade.closed())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -190,7 +190,7 @@ test.register_coroutine_test( capabilities.windowShade.windowShade.closing())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -208,7 +208,7 @@ test.register_coroutine_test( capabilities.windowShade.windowShade.partially_open())) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua index 145876ff64..30bc6bfa5c 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua @@ -55,7 +55,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -108,7 +108,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -132,7 +132,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -181,7 +181,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -209,7 +209,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -272,7 +272,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -298,7 +298,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -333,7 +333,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -367,7 +367,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -401,7 +401,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -456,7 +456,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -491,7 +491,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -526,7 +526,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -556,7 +556,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -588,7 +588,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.lua index ecf2586a5c..ba5b9c20a5 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_VWSDSTUST120H.lua @@ -51,7 +51,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, read_0x0001_messge}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -70,7 +70,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({mock_device.id, read_0x0001_messge}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -94,7 +94,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -121,7 +121,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -143,7 +143,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -160,7 +160,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -177,7 +177,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -194,7 +194,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -211,7 +211,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -229,7 +229,7 @@ test.register_coroutine_test( capabilities.mode.mode("Delete upper limit"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -247,7 +247,7 @@ test.register_coroutine_test( capabilities.mode.mode("Set the upper limit"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -265,7 +265,7 @@ test.register_coroutine_test( capabilities.mode.mode("Delete lower limit"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -283,7 +283,7 @@ test.register_coroutine_test( capabilities.mode.mode("Set the lower limit"))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -301,7 +301,7 @@ test.register_coroutine_test( capabilities.hardwareFault.hardwareFault.clear())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -319,7 +319,7 @@ test.register_coroutine_test( capabilities.hardwareFault.hardwareFault.detected())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -340,7 +340,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -361,7 +361,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara.lua index 08c71ec85a..23b54edab5 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara.lua @@ -143,7 +143,7 @@ test.register_coroutine_test( PREF_SOFT_TOUCH_ON) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -178,7 +178,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -200,7 +200,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -222,7 +222,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -244,7 +244,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -263,7 +263,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -282,7 +282,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -301,7 +301,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -348,7 +348,7 @@ test.register_coroutine_test( deviceInitialization.initializedState.initialized())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -380,7 +380,7 @@ test.register_coroutine_test( PREF_SOFT_TOUCH_OFF) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -404,7 +404,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -434,7 +434,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -464,7 +464,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -494,7 +494,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -514,7 +514,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -544,7 +544,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -576,7 +576,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -609,7 +609,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -631,7 +631,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -653,7 +653,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_curtain_driver_e1.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_curtain_driver_e1.lua index 0177c71467..82d5fac234 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_curtain_driver_e1.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_curtain_driver_e1.lua @@ -111,7 +111,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -158,7 +158,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -175,7 +175,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -190,7 +190,7 @@ test.register_coroutine_test( PRIVATE_CURTAIN_MANUAL_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, false) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -221,7 +221,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -250,7 +250,7 @@ test.register_coroutine_test( -- }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -280,7 +280,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -298,7 +298,7 @@ test.register_coroutine_test( PRIVATE_CURTAIN_LOCKING_SETTING_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -316,7 +316,7 @@ test.register_coroutine_test( PRIVATE_CURTAIN_LOCKING_SETTING_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x00) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -348,7 +348,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -368,7 +368,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -388,7 +388,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -407,7 +407,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -426,7 +426,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -445,7 +445,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_roller_shade_rotate.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_roller_shade_rotate.lua index 3e1e013c38..4f8c0780c7 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_roller_shade_rotate.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_aqara_roller_shade_rotate.lua @@ -116,7 +116,7 @@ test.register_coroutine_test( PREF_REVERSE_OFF) }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -151,7 +151,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -195,7 +195,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -217,7 +217,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -246,7 +246,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -275,7 +275,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -294,7 +294,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -313,7 +313,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -333,7 +333,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -371,7 +371,7 @@ test.register_coroutine_test( initializedStateWithGuide.initializedStateWithGuide.initialized())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -395,7 +395,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -427,7 +427,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -446,7 +446,7 @@ test.register_coroutine_test( initializedStateWithGuide.initializedStateWithGuide.notInitialized())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -466,7 +466,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -507,7 +507,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, message }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 fb63dd121d..333eca0f9e 100644 --- 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 @@ -82,7 +82,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -123,7 +123,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -176,7 +176,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -204,7 +204,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -236,7 +236,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -264,7 +264,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -296,7 +296,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -336,7 +336,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -378,7 +378,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -406,7 +406,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -441,7 +441,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -469,7 +469,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -506,7 +506,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -541,7 +541,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -586,7 +586,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -624,7 +624,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -679,7 +679,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) }) end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_rooms.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_rooms.lua index 9f80bfdc80..dfecbc28ab 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_rooms.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_rooms.lua @@ -58,7 +58,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -80,7 +80,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -124,7 +124,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -146,7 +146,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -189,7 +189,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -209,7 +209,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -229,7 +229,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -251,7 +251,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -296,7 +296,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -335,7 +335,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -384,7 +384,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua index a929eb4e98..17d8d61f76 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua @@ -64,7 +64,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -86,7 +86,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -115,7 +115,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -135,7 +135,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -155,7 +155,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -175,7 +175,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -196,7 +196,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -271,7 +271,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -303,7 +303,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -346,7 +346,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -389,7 +389,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -432,7 +432,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -457,7 +457,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_sombra.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_sombra.lua index 35ab9eab24..febbfe6ea5 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_sombra.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_sombra.lua @@ -59,7 +59,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -81,7 +81,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -126,7 +126,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -171,7 +171,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -191,7 +191,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -211,7 +211,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -231,7 +231,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -253,7 +253,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -275,7 +275,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -295,7 +295,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -320,7 +320,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -343,7 +343,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -395,7 +395,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -427,7 +427,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua index e772b46bb9..7e89c956ea 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua @@ -68,7 +68,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -121,7 +121,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -161,7 +161,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -243,7 +243,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -267,7 +267,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -294,7 +294,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -316,7 +316,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -344,7 +344,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -372,7 +372,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -400,7 +400,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -427,7 +427,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -448,7 +448,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -475,7 +475,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -500,7 +500,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -536,7 +536,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -561,7 +561,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -591,7 +591,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -621,7 +621,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -662,7 +662,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 15 } ) 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 8ef8ceea42..fb2b6cf81c 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 @@ -62,7 +62,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -83,7 +83,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -107,7 +107,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -131,7 +131,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -157,7 +157,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -183,7 +183,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -207,7 +207,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -232,7 +232,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -257,7 +257,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -281,7 +281,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -313,7 +313,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -347,7 +347,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -379,7 +379,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -408,7 +408,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -438,7 +438,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -465,7 +465,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -493,7 +493,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -570,7 +570,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 faa5125ebe..49ae2a0145 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 @@ -105,7 +105,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -188,7 +188,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 0fc5140c4e..6d5782c676 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 @@ -99,7 +99,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -202,7 +202,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -234,7 +234,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -265,7 +265,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -297,7 +297,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -321,7 +321,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -349,7 +349,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -398,7 +398,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -478,7 +478,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -509,7 +509,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -540,7 +540,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 a658806774..eea8a679a2 100644 --- a/drivers/SmartThings/zwave-bulb/src/test/test_zwave_bulb.lua +++ b/drivers/SmartThings/zwave-bulb/src/test/test_zwave_bulb.lua @@ -100,7 +100,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -133,7 +133,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -161,7 +161,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -195,7 +195,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -228,7 +228,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -263,7 +263,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -315,7 +315,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -386,7 +386,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -431,7 +431,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -479,7 +479,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) end diff --git a/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_nanomote_one.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_nanomote_one.lua index 7df9c6cada..a7fd84e0e6 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_nanomote_one.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_aeotec_nanomote_one.lua @@ -58,7 +58,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-button/src/test/test_zwave_button.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_button.lua index 664d048010..155157e51b 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_button.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_button.lua @@ -49,7 +49,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -68,7 +68,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -89,7 +89,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -110,7 +110,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -141,7 +141,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_coroutine_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-button/src/test/test_zwave_fibaro_button.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_fibaro_button.lua index 5418917ee5..39223ad82e 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_fibaro_button.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_fibaro_button.lua @@ -57,7 +57,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua b/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua index b88a85a800..5e1c27b371 100644 --- a/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua +++ b/drivers/SmartThings/zwave-button/src/test/test_zwave_multi_button.lua @@ -108,7 +108,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -137,7 +137,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -163,7 +163,7 @@ test.register_coroutine_test( capabilities.button.button.double({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -192,7 +192,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -221,7 +221,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -247,7 +247,7 @@ test.register_coroutine_test( capabilities.button.button.double({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -276,7 +276,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -305,7 +305,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -334,7 +334,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -359,7 +359,7 @@ test.register_coroutine_test( capabilities.button.button.held({state_change = true}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -378,7 +378,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -408,7 +408,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -437,7 +437,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -466,7 +466,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -495,7 +495,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -520,7 +520,7 @@ test.register_coroutine_test( mock_aeotec_keyfob_button:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -608,7 +608,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -649,7 +649,7 @@ test.register_coroutine_test( mock_fibaro_keyfob_button:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -765,7 +765,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -853,7 +853,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -913,7 +913,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -942,7 +942,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 391b3b2b0d..ed4d4f3609 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 @@ -54,7 +54,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -107,7 +107,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -139,7 +139,7 @@ test.register_coroutine_test( mock_meter:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 016aff4061..9534c5d2e1 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 @@ -54,7 +54,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -107,7 +107,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -143,7 +143,7 @@ test.register_coroutine_test( mock_meter:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 ce9b61f949..fad1510245 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 @@ -96,7 +96,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -180,7 +180,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -222,7 +222,7 @@ test.register_coroutine_test( mock_meter:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 d96d90aae1..e97be3bde3 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 @@ -54,7 +54,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -107,7 +107,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -127,7 +127,7 @@ test.register_coroutine_test( mock_meter:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 4df103e086..436862230d 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 @@ -47,7 +47,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -69,7 +69,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -100,7 +100,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_3_speed.lua b/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_3_speed.lua index 534c48698e..179baed5aa 100644 --- a/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_3_speed.lua +++ b/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_3_speed.lua @@ -64,7 +64,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -129,7 +129,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -160,7 +160,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -194,7 +194,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_4_speed.lua b/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_4_speed.lua index 80ccef948b..fc8a5b742c 100644 --- a/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_4_speed.lua +++ b/drivers/SmartThings/zwave-fan/src/test/test_zwave_fan_4_speed.lua @@ -63,7 +63,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -94,7 +94,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -127,7 +127,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -158,7 +158,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -192,7 +192,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_ecolink_garage_door_operator.lua b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_ecolink_garage_door_operator.lua index 0608ca2ef2..ae86832e4d 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_ecolink_garage_door_operator.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_ecolink_garage_door_operator.lua @@ -59,7 +59,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -78,7 +78,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_message_test( { test_init = test_init, inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -160,7 +160,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -183,7 +183,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -206,7 +206,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -234,7 +234,7 @@ test.register_coroutine_test( mock_garage_door:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -264,7 +264,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -294,7 +294,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -311,7 +311,7 @@ test.register_coroutine_test( test.mock_time.advance_time(1) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -328,7 +328,7 @@ test.register_coroutine_test( test.mock_time.advance_time(1) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_mimolite_garage_door.lua b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_mimolite_garage_door.lua index cc33391a4c..946978e0b0 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_mimolite_garage_door.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_mimolite_garage_door.lua @@ -59,7 +59,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -83,7 +83,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -107,7 +107,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -131,7 +131,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -155,7 +155,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -179,7 +179,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -213,7 +213,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -247,7 +247,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -277,7 +277,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -307,7 +307,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -327,7 +327,7 @@ test.register_coroutine_test( mock_garage_door:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -350,7 +350,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_zwave_garage_door_opener.lua b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_zwave_garage_door_opener.lua index 0d5da468e8..366824a8d2 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/src/test/test_zwave_garage_door_opener.lua +++ b/drivers/SmartThings/zwave-garage-door-opener/src/test/test_zwave_garage_door_opener.lua @@ -50,7 +50,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -69,7 +69,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -93,7 +93,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -156,7 +156,7 @@ test.register_message_test( }, { test_init = test_init, - min_api_version = 17 + min_api_version = 14 } ) @@ -177,7 +177,7 @@ test.register_message_test( { test_init = test_init, inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua index 6724094462..2b9d2d3d72 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock.lua @@ -89,6 +89,9 @@ test.register_coroutine_test( test.socket.zwave:__queue_receive({ mock_device.id, Notification:Report({notification_type = 6, event = 25}) } ) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.locked({data={method="manual"}}))) end -) +, + { + min_api_version = 17 + }) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock_legacy.lua b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock_legacy.lua index a5c7aee7d4..63a8729ce9 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock_legacy.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_keywe_lock_legacy.lua @@ -46,7 +46,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.unlocked())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -59,7 +59,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.locked())) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua b/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua index d9642851e1..3254edf58a 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_lock_battery.lua @@ -60,7 +60,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.unlocked())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -79,7 +79,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -98,7 +98,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -115,7 +115,7 @@ test.register_coroutine_test( test.socket.zwave:__expect_send(DoorLock:OperationGet({}):build_test_tx(mock_device.id)) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_coroutine_test( }):build_test_tx(mock_device.id)) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -157,7 +157,7 @@ test.register_coroutine_test( }):build_test_tx(mock_device.id)) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-lock/src/test/test_lock_credentials_commands.lua b/drivers/SmartThings/zwave-lock/src/test/test_lock_credentials_commands.lua index 96b192a03d..5daee2191a 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_lock_credentials_commands.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_lock_credentials_commands.lua @@ -175,7 +175,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -200,7 +203,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -229,7 +235,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) -- ============================================================================ @@ -253,7 +262,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -275,7 +287,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -297,7 +312,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) -- ============================================================================ @@ -321,7 +339,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -341,7 +362,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -363,7 +387,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -385,7 +412,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) -- ============================================================================ @@ -423,7 +453,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -445,7 +478,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) test.register_coroutine_test( @@ -474,7 +510,10 @@ test.register_coroutine_test( )) ) test.wait_for_events() - end + end, + { + min_api_version = 15 + } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_lock_pre_configured.lua b/drivers/SmartThings/zwave-lock/src/test/test_lock_pre_configured.lua index 0895ff3215..ef264e0c88 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_lock_pre_configured.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_lock_pre_configured.lua @@ -205,7 +205,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- updateUser — pre-configured device @@ -240,7 +243,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "updateUser (pre-configured): returns failure for a userIndex that does not exist", @@ -273,7 +279,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- deleteUser with associated credential — exercises clear_pin_code_response @@ -324,7 +333,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- addCredential response states — set_pin_code_response @@ -365,7 +377,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "addCredential (pre-configured): emits duplicate when the lock rejects with DUPLICATE_CODE", @@ -392,7 +407,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "addCredential (pre-configured): emits failure when the lock returns available", @@ -419,7 +437,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- updateCredential response states — set_pin_code_response @@ -450,7 +471,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "updateCredential (pre-configured): returns failure immediately for a non-existent credentialIndex", @@ -472,7 +496,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- deleteCredential standalone (lockCredentials.DELETE path) @@ -509,6 +536,9 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_lock_users_commands.lua b/drivers/SmartThings/zwave-lock/src/test/test_lock_users_commands.lua index da39c8c692..21c829c497 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_lock_users_commands.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_lock_users_commands.lua @@ -192,7 +192,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "addUser: assigns the next sequential userIndex when users already exist", @@ -223,7 +226,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "addUser: fills a gap left by a deleted user rather than appending beyond max", @@ -259,7 +265,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "addUser: returns resourceExhausted when totalUsersSupported has been reached", @@ -284,7 +293,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- updateUser @@ -322,7 +334,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "updateUser: creates user when userIndex does not exist", @@ -352,7 +367,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "updateUser: can change a user's type as well as name", @@ -380,7 +398,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- deleteUser (no associated credential — pure local delete) @@ -415,7 +436,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "deleteUser: returns failure when the target userIndex is not in the users table", @@ -435,7 +459,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- deleteAllUsers (injects deleteAllCredentials → ClearAllPINCodes z-wave flow) @@ -495,7 +522,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "deleteAllUsers: emits busy when another lock command is in progress", @@ -517,7 +547,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- State-consistency: add → delete → re-add (indices 1, 2, 3 lifecycle) @@ -590,7 +623,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "State-consistency: add users+credentials 1-3, deleteUser 2 (with credential), re-add user+credential reclaims index 2 cleanly", @@ -700,6 +736,9 @@ test.register_coroutine_test( }) == constants.COMMAND_RESULT.SUCCESS) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock_legacy.lua b/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock_legacy.lua index 81ba1df2ad..42cb17a2a2 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock_legacy.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_samsung_lock_legacy.lua @@ -51,7 +51,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -97,7 +97,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -114,7 +114,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("2 failed", { state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -131,7 +131,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged(2 .. " failed", { state_change = true }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -176,7 +176,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock_legacy.lua b/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock_legacy.lua index 38dabbd9dd..3c8a165f7f 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock_legacy.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_schlage_lock_legacy.lua @@ -77,7 +77,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -97,7 +97,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -167,7 +167,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -204,7 +204,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -232,7 +232,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -259,7 +259,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_legacy.lua b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_legacy.lua index 66038d0760..b7ddf56227 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_legacy.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_zwave_lock_legacy.lua @@ -62,7 +62,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.locked())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -81,7 +81,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -105,7 +105,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -137,7 +137,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -162,7 +162,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -179,7 +179,7 @@ test.register_coroutine_test( test.socket.zwave:__expect_send(DoorLock:OperationGet({}):build_test_tx(mock_device.id)) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -198,7 +198,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -209,7 +209,7 @@ test.register_coroutine_test( expect_reload_all_codes_messages() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -228,7 +228,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -244,7 +244,7 @@ test.register_coroutine_test( test.socket.zwave:__expect_send(UserCode:Get( {user_identifier = 1}):build_test_tx(mock_device.id)) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -264,7 +264,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -289,7 +289,7 @@ test.register_coroutine_test( {state_change = true}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -316,7 +316,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -341,7 +341,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -371,7 +371,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -395,7 +395,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -438,7 +438,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -463,7 +463,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -488,7 +488,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -519,7 +519,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -754,7 +754,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -779,7 +779,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -808,7 +808,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-lock/src/test/test_zwave_responses.lua b/drivers/SmartThings/zwave-lock/src/test/test_zwave_responses.lua index 71bf25209d..24db26a3ba 100644 --- a/drivers/SmartThings/zwave-lock/src/test/test_zwave_responses.lua +++ b/drivers/SmartThings/zwave-lock/src/test/test_zwave_responses.lua @@ -100,7 +100,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "user_code_report: sync completes when the last slot is reached", @@ -140,7 +143,10 @@ test.register_coroutine_test( assert(mock_device:get_field(constants.DRIVER_STATE.BUSY) == false, "BUSY must be false after sync completes") end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "user_code_report: out-of-band ENABLED code creates user and credential entries", @@ -173,7 +179,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- user_code_event_handler (ACCESS_CONTROL Notification:Report) @@ -208,7 +217,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "user_code_event: multi-byte event_parameter uses byte 3 to extract user_id", @@ -239,7 +251,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- door_operation_event_handler (ACCESS_CONTROL Notification:Report) @@ -279,7 +294,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "door_operation_event: KEYPAD_UNLOCK_OPERATION with unknown credential sets userIndex from event_parameter", @@ -306,7 +324,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "door_operation_event: AUTO_LOCK_LOCKED_OPERATION emits locked with auto method", @@ -327,7 +348,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- DoorLock:OperationReport → lock state @@ -346,7 +370,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.register_coroutine_test( "OperationReport: DOOR_UNSECURED emits unlocked event", @@ -360,7 +387,10 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) -- ============================================================================ -- users_number_report: UserCode:UsersNumberReport @@ -388,6 +418,9 @@ test.register_coroutine_test( ) test.wait_for_events() end -) +, + { + min_api_version = 15 + }) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-mouse-trap/src/test/test_zwave_mouse_trap.lua b/drivers/SmartThings/zwave-mouse-trap/src/test/test_zwave_mouse_trap.lua index 519f577c86..ca1ae9ede4 100644 --- a/drivers/SmartThings/zwave-mouse-trap/src/test/test_zwave_mouse_trap.lua +++ b/drivers/SmartThings/zwave-mouse-trap/src/test/test_zwave_mouse_trap.lua @@ -67,7 +67,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -89,7 +89,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -111,7 +111,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -133,7 +133,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -155,7 +155,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -177,7 +177,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -199,7 +199,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -221,7 +221,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -243,7 +243,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -262,7 +262,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -281,7 +281,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -335,7 +335,7 @@ test.register_coroutine_test( mock_mouse_trap:expect_metadata_update({provisioning_state = "PROVISIONED"}) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua index 472932fdfb..649855f9c3 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeon_multisensor.lua @@ -83,7 +83,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -108,7 +108,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua index 323c0d4807..9e7a02851b 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_6.lua @@ -108,7 +108,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) test.register_coroutine_test( @@ -231,7 +231,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -253,7 +253,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -288,7 +288,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -323,7 +323,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -345,7 +345,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -369,7 +369,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -405,7 +405,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -429,7 +429,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -454,7 +454,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua index 13442ec635..648fb9a5ad 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_7.lua @@ -110,7 +110,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) test.register_coroutine_test( @@ -229,7 +229,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -251,7 +251,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -286,7 +286,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -321,7 +321,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua index b6ede32bd5..25bba0e7b8 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_multisensor_gen5.lua @@ -98,7 +98,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua index e38ce96f37..62ab74cfde 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor.lua @@ -78,7 +78,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -97,7 +97,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -138,7 +138,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -160,7 +160,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -182,7 +182,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -204,7 +204,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -223,7 +223,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -245,7 +245,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -267,7 +267,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -289,7 +289,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -311,7 +311,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua index 0f3dc972de..26e61fae14 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_7.lua @@ -52,7 +52,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -74,7 +74,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -140,7 +140,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -168,7 +168,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua index 3559656e3a..73af4449dc 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_enerwave_motion_sensor.lua @@ -53,7 +53,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -98,7 +98,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua index a33f9b97ec..df57046912 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everpsring_sp817.lua @@ -51,7 +51,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -88,7 +88,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua index 8479a5f74f..9efe546309 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_PIR_sensor.lua @@ -58,7 +58,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -81,7 +81,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -108,7 +108,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -130,7 +130,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -153,7 +153,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -203,7 +203,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua index 3c288c0be4..1bef4fb686 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_ST814.lua @@ -50,7 +50,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua index ab124e8f80..dfce8285d9 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_illuminance_sensor.lua @@ -54,7 +54,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua index 0a31f97de5..0babb02dc1 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_everspring_motion_light_sensor.lua @@ -66,7 +66,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua index fb2138c0d5..08289dd301 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_ezmultipli_multipurpose_sensor.lua @@ -52,7 +52,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -81,7 +81,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -119,7 +119,7 @@ do )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ do )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -293,7 +293,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua index 05e306b6c7..386891a9cc 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor.lua @@ -73,7 +73,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -97,7 +97,7 @@ test.register_coroutine_test( mock_fibaro_door_window_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -138,7 +138,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -160,7 +160,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -183,7 +183,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -205,7 +205,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua index 6707dd7ae0..8e9c936237 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua @@ -121,7 +121,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -210,7 +210,7 @@ test.register_coroutine_test( mock_fibaro_door_window_sensor1:expect_metadata_update({provisioning_state = "PROVISIONED"}) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -229,7 +229,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -251,7 +251,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -273,7 +273,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -300,7 +300,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -327,7 +327,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -349,7 +349,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -370,7 +370,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -401,7 +401,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -431,7 +431,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua index 8fd23b208b..7a1ddb417e 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua @@ -59,7 +59,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -81,7 +81,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -103,7 +103,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -147,7 +147,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -191,7 +191,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -308,7 +308,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -349,7 +349,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -385,7 +385,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua index 4d3e6e0660..0b970eded6 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_with_temperature.lua @@ -85,7 +85,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_coroutine_test( mock_fibaro_door_window_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -132,7 +132,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -154,7 +154,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -176,7 +176,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -199,7 +199,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -221,7 +221,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -252,7 +252,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -282,7 +282,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -559,7 +559,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua index 0ac7e57418..7de8a345b3 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor.lua @@ -54,7 +54,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -73,7 +73,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -94,7 +94,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -115,7 +115,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -167,7 +167,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -189,7 +189,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -216,7 +216,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -243,7 +243,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -270,7 +270,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -297,7 +297,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -328,7 +328,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua index 530d1c03bb..dc47194f8c 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_flood_sensor_zw5.lua @@ -82,7 +82,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua index 3dbfd89bc7..3a2f5b3644 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor.lua @@ -87,7 +87,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -106,7 +106,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -133,7 +133,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -155,7 +155,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -177,7 +177,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -199,7 +199,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -221,7 +221,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -244,7 +244,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -266,7 +266,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -288,7 +288,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -315,7 +315,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -346,7 +346,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -376,7 +376,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua index ac2022e069..22f9df61db 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_motion_sensor_zw5.lua @@ -69,7 +69,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -208,7 +208,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua b/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua index bf4c68d5bb..e85f290f11 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua @@ -86,7 +86,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -108,7 +108,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -124,7 +124,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua index 3496ebb6d5..9f8019e240 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_generic_sensor.lua @@ -63,7 +63,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -86,7 +86,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -108,7 +108,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -131,7 +131,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -154,7 +154,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -176,7 +176,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -207,7 +207,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -238,7 +238,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -260,7 +260,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -283,7 +283,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -313,7 +313,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -344,7 +344,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -366,7 +366,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -388,7 +388,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -412,7 +412,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -456,7 +456,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -500,7 +500,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -522,7 +522,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -544,7 +544,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -571,7 +571,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -598,7 +598,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -692,7 +692,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -786,7 +786,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -808,7 +808,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -830,7 +830,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -852,7 +852,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -874,7 +874,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -896,7 +896,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -918,7 +918,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -940,7 +940,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -962,7 +962,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -984,7 +984,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1006,7 +1006,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1028,7 +1028,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1050,7 +1050,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1072,7 +1072,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1105,7 +1105,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1127,7 +1127,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1149,7 +1149,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1171,7 +1171,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1199,7 +1199,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1221,7 +1221,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1243,7 +1243,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1265,7 +1265,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1287,7 +1287,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1309,7 +1309,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1331,7 +1331,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1353,7 +1353,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1375,7 +1375,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1397,7 +1397,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1419,7 +1419,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1441,7 +1441,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1463,7 +1463,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1485,7 +1485,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1506,7 +1506,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1527,7 +1527,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1549,7 +1549,7 @@ test.register_coroutine_test( test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command(mock_device, Battery:Get({}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1571,7 +1571,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1593,7 +1593,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua index 926558036b..52a3a50b9a 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_glentronics_water_leak_sensor.lua @@ -70,7 +70,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -196,7 +196,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -222,7 +222,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua index 743ffc5298..dd9101edf7 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_homeseer_multi_sensor.lua @@ -51,7 +51,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -70,7 +70,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -92,7 +92,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -137,7 +137,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) test.register_coroutine_test( @@ -168,7 +168,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua b/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua index c88527f0cd..c9eb716c3b 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_no_wakeup_poll.lua @@ -100,7 +100,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua b/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua index fb9b519a42..0071f3f09a 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_sensative_strip.lua @@ -61,7 +61,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -72,7 +72,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ profile = "illuminance-temperature" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -94,7 +94,7 @@ test.register_coroutine_test( test.socket.zwave:__queue_receive({mock_sensor.id, WakeUp:Notification({})}) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua index e80e28dbd9..cf7d08ebdf 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_smartthings_water_leak_sensor.lua @@ -59,7 +59,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -81,7 +81,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -103,7 +103,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -147,7 +147,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -191,7 +191,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -222,7 +222,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -253,7 +253,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -272,7 +272,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -291,7 +291,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -310,7 +310,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua b/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua index 4cfd906636..0db72f1b50 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_v1_contact_event.lua @@ -57,7 +57,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -84,7 +84,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -104,7 +104,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua b/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua index 3cd16b6fa3..11d62dd11f 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_vision_motion_detector.lua @@ -58,7 +58,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -80,7 +80,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -124,7 +124,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -163,7 +163,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -187,7 +187,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua index fde2d5ebfe..63b0b2bb8a 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zooz_4_in_1_sensor.lua @@ -53,7 +53,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -103,7 +103,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -192,7 +192,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -214,7 +214,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -239,7 +239,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -260,7 +260,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua index 7a8adbeb10..5559a53c79 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_light_sensor.lua @@ -59,7 +59,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -86,7 +86,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -108,7 +108,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -129,7 +129,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -151,7 +151,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -196,7 +196,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -218,7 +218,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -245,7 +245,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -292,7 +292,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -307,7 +307,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua index 8e463e7625..b6c70bdadb 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_motion_temp_light_sensor.lua @@ -55,7 +55,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -82,7 +82,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -104,7 +104,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -126,7 +126,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -192,7 +192,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -247,7 +247,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua index 677204e5d1..29354370e4 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_sensor.lua @@ -131,7 +131,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -235,7 +235,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -272,7 +272,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -294,7 +294,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -316,7 +316,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -338,7 +338,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -359,7 +359,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -380,7 +380,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -411,7 +411,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -438,7 +438,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -460,7 +460,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -482,7 +482,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -504,7 +504,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -526,7 +526,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -548,7 +548,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -570,7 +570,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -602,7 +602,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -624,7 +624,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -646,7 +646,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -673,7 +673,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -700,7 +700,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -727,7 +727,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -749,7 +749,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -771,7 +771,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -794,7 +794,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -815,7 +815,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -836,7 +836,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua index 775130d87e..fa5f64bff0 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_zwave_water_sensor.lua @@ -57,7 +57,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -139,7 +139,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -161,7 +161,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -183,7 +183,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -202,7 +202,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -224,7 +224,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -246,7 +246,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -268,7 +268,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -290,7 +290,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 30b8333c5b..8e8137cc90 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_aeon_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_aeon_siren.lua @@ -56,7 +56,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -82,7 +82,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -176,7 +176,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -207,7 +207,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -238,7 +238,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -265,7 +265,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -293,7 +293,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -322,7 +322,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 31fb57fed2..d58fbfc207 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 @@ -113,7 +113,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -151,7 +151,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -189,7 +189,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -227,7 +227,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -265,7 +265,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -303,7 +303,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -341,7 +341,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -379,7 +379,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -424,7 +424,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -469,7 +469,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -514,7 +514,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -559,7 +559,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -604,7 +604,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -649,7 +649,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -694,7 +694,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -739,7 +739,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -779,7 +779,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -819,7 +819,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -859,7 +859,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -899,7 +899,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -939,7 +939,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -979,7 +979,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1019,7 +1019,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1059,7 +1059,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1099,7 +1099,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1139,7 +1139,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1179,7 +1179,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1219,7 +1219,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1259,7 +1259,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1299,7 +1299,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1339,7 +1339,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1379,7 +1379,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1419,7 +1419,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1459,7 +1459,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1499,7 +1499,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1539,7 +1539,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1579,7 +1579,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1619,7 +1619,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1659,7 +1659,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1699,7 +1699,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1739,7 +1739,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1779,7 +1779,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1819,7 +1819,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1859,7 +1859,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1899,7 +1899,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1939,7 +1939,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1979,7 +1979,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2019,7 +2019,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2059,7 +2059,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2099,7 +2099,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2139,7 +2139,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2179,7 +2179,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2219,7 +2219,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2259,7 +2259,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2299,7 +2299,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2339,7 +2339,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2379,7 +2379,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2419,7 +2419,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2459,7 +2459,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2499,7 +2499,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2539,7 +2539,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2579,7 +2579,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2619,7 +2619,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2659,7 +2659,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2682,7 +2682,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -2705,7 +2705,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -2738,7 +2738,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2754,7 +2754,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_siren:generate_info_changed({ preferences = _preferences})) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2779,7 +2779,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2793,7 +2793,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_siren:generate_info_changed({ preferences = _preferences})) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2818,7 +2818,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2832,7 +2832,7 @@ test.register_coroutine_test( test.socket.device_lifecycle:__queue_receive(mock_siren:generate_info_changed({ preferences = _preferences})) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2860,7 +2860,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2888,7 +2888,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2916,7 +2916,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2944,7 +2944,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2972,7 +2972,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -3000,7 +3000,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -3036,7 +3036,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -3056,7 +3056,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 c81ba1b262..ab6fab08a9 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 @@ -68,7 +68,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -106,7 +106,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -210,7 +210,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -248,7 +248,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -286,7 +286,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -313,7 +313,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) test.register_coroutine_test( @@ -347,7 +347,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -374,7 +374,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -400,7 +400,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -427,7 +427,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) test.register_coroutine_test( @@ -452,7 +452,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) test.register_coroutine_test( @@ -477,7 +477,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) test.register_coroutine_test( @@ -502,7 +502,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 61ae247d58..90a01396c8 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_fortrezz_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_fortrezz_siren.lua @@ -61,7 +61,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_siren:generate_test_message("main", capabilities.switch.switch.on({}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_siren:generate_test_message("main", capabilities.switch.switch.on({}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -128,7 +128,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_siren:generate_test_message("main", capabilities.switch.switch.on({}))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -161,7 +161,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_siren:generate_test_message("main", capabilities.switch.switch.off({}))) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 432de972dd..06255805f0 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 @@ -64,7 +64,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -168,7 +168,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -201,7 +201,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -224,7 +224,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -247,7 +247,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -281,7 +281,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -308,7 +308,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -333,7 +333,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -360,7 +360,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -387,7 +387,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -414,7 +414,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -441,7 +441,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -462,7 +462,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -484,7 +484,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -533,7 +533,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -558,7 +558,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 33801f1dbd..0632779570 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_utilitech_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_utilitech_siren.lua @@ -43,7 +43,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -63,7 +63,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 275cc89ce9..517947dfef 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_yale_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_yale_siren.lua @@ -54,7 +54,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -75,7 +75,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -152,7 +152,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -190,7 +190,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -227,7 +227,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -263,7 +263,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -311,7 +311,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -331,7 +331,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 0d39f0f990..6c5672928b 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_zipato_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_zipato_siren.lua @@ -63,7 +63,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -94,7 +94,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -114,7 +114,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -135,7 +135,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -156,7 +156,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-siren/src/test/test_zooz_zse50.lua b/drivers/SmartThings/zwave-siren/src/test/test_zooz_zse50.lua index 1454458687..c7d5723d64 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_zooz_zse50.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_zooz_zse50.lua @@ -66,7 +66,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -176,7 +176,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -198,7 +198,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -220,7 +220,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -242,7 +242,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -264,7 +264,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -296,7 +296,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -341,7 +341,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -372,7 +372,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -403,7 +403,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -434,7 +434,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -453,7 +453,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -472,7 +472,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -491,7 +491,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -516,7 +516,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -541,7 +541,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -566,7 +566,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -591,7 +591,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -616,7 +616,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -641,7 +641,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -663,7 +663,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -710,7 +710,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 61893d7dd6..27a2833d17 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 @@ -52,7 +52,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -74,7 +74,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -146,7 +146,7 @@ test.register_coroutine_test( mock_siren:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 e80d79e338..9fa63cfcba 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 @@ -51,7 +51,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -78,7 +78,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -100,7 +100,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -127,7 +127,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 3e33929169..2311a1dafa 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_zwave_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_zwave_siren.lua @@ -84,7 +84,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -110,7 +110,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -162,7 +162,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -188,7 +188,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -214,7 +214,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -240,7 +240,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -266,7 +266,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -297,7 +297,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -328,7 +328,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -360,7 +360,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -391,7 +391,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -422,7 +422,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -463,7 +463,7 @@ test.register_coroutine_test( test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) 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 b796b1b9d0..716c05ac0d 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 @@ -50,7 +50,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -72,7 +72,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -91,7 +91,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_co_sensor_zw5.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_co_sensor_zw5.lua index e860e4608f..7031ffb88b 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_co_sensor_zw5.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_co_sensor_zw5.lua @@ -70,7 +70,7 @@ test.register_coroutine_test( mock_fibaro_CO_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -89,7 +89,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -199,7 +199,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -226,7 +226,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -253,7 +253,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -279,7 +279,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -307,7 +307,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -334,7 +334,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -361,7 +361,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -392,7 +392,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -501,7 +501,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_smoke_sensor.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_smoke_sensor.lua index 39c79e0498..c877d18e47 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_smoke_sensor.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_fibaro_smoke_sensor.lua @@ -150,7 +150,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -186,7 +186,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -209,7 +209,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_alarm_v1.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_alarm_v1.lua index bdeb10535f..af2588b959 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_alarm_v1.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_alarm_v1.lua @@ -54,7 +54,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -98,7 +98,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -165,7 +165,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -187,7 +187,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -209,7 +209,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_co_detector.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_co_detector.lua index 60710942bc..3bf9d563a1 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_co_detector.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_co_detector.lua @@ -55,7 +55,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -77,7 +77,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) test.register_message_test( @@ -98,7 +98,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -142,7 +142,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -164,7 +164,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -181,7 +181,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_smoke_detector.lua b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_smoke_detector.lua index c62dab7ec6..838527222b 100644 --- a/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_smoke_detector.lua +++ b/drivers/SmartThings/zwave-smoke-alarm/src/test/test_zwave_smoke_detector.lua @@ -56,7 +56,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -78,7 +78,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -100,7 +100,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -122,7 +122,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -166,7 +166,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -188,7 +188,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -205,7 +205,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -253,7 +253,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -275,7 +275,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -297,7 +297,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -319,7 +319,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -341,7 +341,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -363,7 +363,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -407,7 +407,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 d0db48c559..86b5d5c539 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 @@ -152,7 +152,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -241,7 +241,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -292,7 +292,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -343,7 +343,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -377,7 +377,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -411,7 +411,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -445,7 +445,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -515,7 +515,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -549,7 +549,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -621,7 +621,7 @@ test.register_coroutine_test( mock_metering_switch:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -755,7 +755,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -889,7 +889,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -980,7 +980,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1071,7 +1071,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1162,7 +1162,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1253,7 +1253,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 ad9197c159..6829cd1c1b 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 @@ -60,7 +60,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -139,7 +139,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -179,7 +179,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -223,7 +223,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -280,7 +280,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -312,7 +312,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -336,7 +336,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -405,7 +405,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -475,7 +475,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -510,7 +510,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 6f35d6200c..d951926231 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 @@ -113,7 +113,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 2d9b6e24c5..73228366f8 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 @@ -57,7 +57,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -81,7 +81,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_coroutine_test( Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -167,7 +167,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -193,7 +193,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -246,7 +246,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -273,7 +273,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -300,7 +300,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -327,7 +327,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -354,7 +354,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -381,7 +381,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -408,7 +408,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -435,7 +435,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -462,7 +462,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -489,7 +489,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -516,7 +516,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 c26c0403e4..eb022e95b5 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 @@ -65,7 +65,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 2c009b5ec7..7c86c0c830 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 @@ -84,7 +84,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -163,7 +163,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -203,7 +203,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -247,7 +247,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -304,7 +304,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -336,7 +336,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -360,7 +360,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -429,7 +429,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -499,7 +499,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -534,7 +534,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 5a349d80cb..2022dbf055 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 @@ -59,7 +59,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 c61e1835a8..507aa2484f 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 @@ -60,7 +60,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -90,7 +90,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 fcaed8070a..4cbfdea3a6 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 @@ -66,7 +66,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -175,7 +175,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -222,7 +222,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 51f7b504ca..47a222e06e 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 @@ -54,7 +54,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -84,7 +84,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -114,7 +114,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -138,7 +138,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -162,7 +162,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -193,7 +193,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 565d6b57aa..4effe6c50a 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 @@ -55,7 +55,7 @@ test.register_coroutine_test( mock_switch:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 6615af4a12..5270baa847 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 @@ -49,7 +49,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -71,7 +71,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 3fe41b79db..1fe37d24ca 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 @@ -77,7 +77,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -106,7 +106,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -138,7 +138,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -202,7 +202,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -234,7 +234,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -266,7 +266,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -298,7 +298,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -321,7 +321,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -385,7 +385,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 29ff5d699e..9cbfd0b38b 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 @@ -76,7 +76,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -147,7 +147,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -235,7 +235,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -273,7 +273,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -333,7 +333,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -381,7 +381,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -398,7 +398,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -458,7 +458,7 @@ test.register_coroutine_test( -- if group_id and scene_id are same, do nothing. end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -483,7 +483,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -501,7 +501,7 @@ test.register_coroutine_test( -- driver should do nothing. end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -521,7 +521,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -541,7 +541,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 103f856373..863f6b5d91 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 @@ -44,7 +44,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -68,7 +68,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -95,7 +95,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -130,7 +130,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -176,7 +176,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) end @@ -203,7 +203,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -229,7 +229,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -257,7 +257,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 3c1fdfc9eb..79f6390aea 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 @@ -73,7 +73,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -119,7 +119,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -142,7 +142,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -165,7 +165,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -184,7 +184,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -203,7 +203,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 10a784d8b4..5b1623758c 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 @@ -43,7 +43,7 @@ test.register_coroutine_test( mock_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 51ad9b8299..e5ccfc170a 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_ecolink_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_ecolink_switch.lua @@ -63,7 +63,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -94,7 +94,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -177,7 +177,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -217,7 +217,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -249,7 +249,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -274,7 +274,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -299,7 +299,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 29e6b9566e..81705116e3 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 @@ -135,7 +135,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -187,7 +187,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) test.register_message_test( @@ -237,7 +237,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -288,7 +288,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -328,7 +328,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -368,7 +368,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -408,7 +408,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -448,7 +448,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -601,7 +601,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -653,7 +653,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 4c8d058acf..3792254e03 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 @@ -55,7 +55,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -109,7 +109,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -142,7 +142,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -175,7 +175,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -208,7 +208,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -238,7 +238,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -267,7 +267,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -290,7 +290,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -319,7 +319,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -342,7 +342,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -371,7 +371,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -394,7 +394,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -423,7 +423,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -446,7 +446,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -475,7 +475,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -497,7 +497,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -526,7 +526,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -548,7 +548,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -577,7 +577,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -691,7 +691,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) 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 669b01f1bd..f35d61d836 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 @@ -108,7 +108,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) 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 a051ce72b2..c6ab67534f 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 @@ -57,7 +57,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 2fafc237b2..0a49231bc0 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 @@ -71,7 +71,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -108,7 +108,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -142,7 +142,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -176,7 +176,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -210,7 +210,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -244,7 +244,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -258,7 +258,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -340,7 +340,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) 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 3dd35e66b9..cae11364c1 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 @@ -53,7 +53,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -77,7 +77,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -101,7 +101,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -149,7 +149,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -174,7 +174,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -198,7 +198,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 2ced0ef6a3..93d19204f4 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 @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -173,7 +173,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -220,7 +220,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -254,7 +254,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -310,7 +310,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -366,7 +366,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -413,7 +413,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -452,7 +452,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -522,7 +522,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -591,7 +591,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -658,7 +658,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -727,7 +727,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -771,7 +771,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -806,7 +806,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -841,7 +841,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 1e2cfe45aa..9001400001 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 @@ -53,7 +53,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -73,7 +73,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -93,7 +93,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -133,7 +133,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -153,7 +153,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 5116eceada..a5865cd219 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 @@ -45,7 +45,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -71,7 +71,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -114,7 +114,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -187,7 +187,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -216,7 +216,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -245,7 +245,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -275,7 +275,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 4827c600d8..a83e2eddc1 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 @@ -49,7 +49,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 66cbf38817..ed0c1bfed1 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_honeywell_dimmer.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_honeywell_dimmer.lua @@ -55,7 +55,7 @@ test.register_coroutine_test( mock_dimmer:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 65ccf723b3..32de5e2715 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 @@ -97,7 +97,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -161,7 +161,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -204,7 +204,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -247,7 +247,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -290,7 +290,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -333,7 +333,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -405,7 +405,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -478,7 +478,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -538,7 +538,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -598,7 +598,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -662,7 +662,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -726,7 +726,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -774,7 +774,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -822,7 +822,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -862,7 +862,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -903,7 +903,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -943,7 +943,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -984,7 +984,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 96668cfc24..8bf0b6f6f9 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_button.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_button.lua @@ -131,7 +131,7 @@ test.register_coroutine_test( end end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -186,7 +186,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -213,7 +213,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 0e71010aa3..bb62be473b 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer.lua @@ -47,7 +47,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -73,7 +73,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -101,7 +101,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -200,7 +200,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -233,7 +233,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) end 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 48fde4f573..59588c0e52 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 @@ -99,7 +99,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -135,7 +135,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 4a3ab7f64a..dfdaf4ac54 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 @@ -70,7 +70,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -112,7 +112,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -162,7 +162,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -200,7 +200,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -243,7 +243,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -276,7 +276,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -307,7 +307,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -340,7 +340,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 701fd6513a..a0c83ed6ae 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 @@ -56,7 +56,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -84,7 +84,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -112,7 +112,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -140,7 +140,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -169,7 +169,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 81e4eff5c0..aa89df6bf2 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 @@ -59,7 +59,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -82,7 +82,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -105,7 +105,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -128,7 +128,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -151,7 +151,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -174,7 +174,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -197,7 +197,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -220,7 +220,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -243,7 +243,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -266,7 +266,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -289,7 +289,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) 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 index 5a9b2ccafd..cec88fe366 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua @@ -130,7 +130,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -188,7 +188,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -222,7 +222,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -248,7 +248,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -274,7 +274,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -333,7 +333,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 index 4fcdf9e65c..243bd08640 100644 --- 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 @@ -90,7 +90,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -179,7 +179,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -238,7 +238,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -301,7 +301,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -344,7 +344,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 index 3c3e9aa7b8..be6db7e82b 100644 --- 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 @@ -60,7 +60,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -86,7 +86,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -112,7 +112,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -138,7 +138,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -162,7 +162,7 @@ do }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 ad95d3edbb..3586b93aab 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 @@ -116,7 +116,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -168,7 +168,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -220,7 +220,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -248,7 +248,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -276,7 +276,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -304,7 +304,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -332,7 +332,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -381,7 +381,7 @@ test.register_coroutine_test( mock_parent_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -424,7 +424,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -467,7 +467,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -497,7 +497,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -531,7 +531,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -563,7 +563,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -595,7 +595,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -630,7 +630,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -663,7 +663,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -757,7 +757,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -774,7 +774,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 6c835ff893..c16703f52b 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_multichannel_device.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_multichannel_device.lua @@ -169,7 +169,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -206,7 +206,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -243,7 +243,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -280,7 +280,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -330,7 +330,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -375,7 +375,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -420,7 +420,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -470,7 +470,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -520,7 +520,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -565,7 +565,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -610,7 +610,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -660,7 +660,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -703,7 +703,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -746,7 +746,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -789,7 +789,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -832,7 +832,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -888,7 +888,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -931,7 +931,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -987,7 +987,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1057,7 +1057,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1099,7 +1099,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1141,7 +1141,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1183,7 +1183,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1229,7 +1229,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1283,7 +1283,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1312,7 +1312,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1341,7 +1341,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1375,7 +1375,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1409,7 +1409,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1533,7 +1533,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1657,7 +1657,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1786,7 +1786,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1815,7 +1815,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1844,7 +1844,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1873,7 +1873,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1902,7 +1902,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1931,7 +1931,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1960,7 +1960,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1989,7 +1989,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2018,7 +2018,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2047,7 +2047,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2076,7 +2076,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2105,7 +2105,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2134,7 +2134,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2172,7 +2172,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -2201,7 +2201,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2231,7 +2231,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2281,7 +2281,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -2337,7 +2337,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -2465,7 +2465,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -2509,7 +2509,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -2531,7 +2531,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2550,7 +2550,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2572,7 +2572,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2591,7 +2591,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2613,7 +2613,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2632,7 +2632,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2654,7 +2654,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -2673,7 +2673,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 8586267722..b972f5c936 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 @@ -49,7 +49,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 151b6b416e..8688cd7ec4 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 @@ -70,7 +70,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -125,7 +125,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -165,7 +165,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -206,7 +206,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -250,7 +250,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -307,7 +307,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -339,7 +339,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -363,7 +363,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -382,7 +382,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -451,7 +451,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -521,7 +521,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -555,7 +555,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 3a2ef76aa2..86e3f8460b 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 @@ -56,7 +56,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -80,7 +80,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -104,7 +104,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -128,7 +128,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -152,7 +152,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -177,7 +177,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -201,7 +201,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 959bb2068d..2c1d6f337b 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 @@ -48,7 +48,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -72,7 +72,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 89b03dae43..f67ac02a67 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 @@ -48,7 +48,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -72,7 +72,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 53e8744577..646370e760 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 @@ -106,7 +106,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -138,7 +138,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -187,7 +187,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -215,7 +215,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -240,7 +240,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -265,7 +265,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -292,7 +292,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -319,7 +319,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -354,7 +354,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -389,7 +389,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -418,7 +418,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -460,7 +460,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -502,7 +502,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -544,7 +544,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -586,7 +586,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -639,7 +639,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 b894310832..e050946859 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 @@ -48,7 +48,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -72,7 +72,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -96,7 +96,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -144,7 +144,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 42e01fe944..7352331b05 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 @@ -61,7 +61,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -156,7 +156,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -197,7 +197,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -241,7 +241,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -298,7 +298,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -330,7 +330,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -354,7 +354,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -371,7 +371,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' }))) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -440,7 +440,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -510,7 +510,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -544,7 +544,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 7256e3579a..7dad04cd8c 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 @@ -52,7 +52,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -100,7 +100,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -124,7 +124,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -148,7 +148,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -176,7 +176,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -204,7 +204,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 347995922e..95c994a637 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 @@ -51,7 +51,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -75,7 +75,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -147,7 +147,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -171,7 +171,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -195,7 +195,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -220,7 +220,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -244,7 +244,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 c1c4f31f0b..d510e9cfed 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 @@ -51,7 +51,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -75,7 +75,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -147,7 +147,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -196,7 +196,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -220,7 +220,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 2614927458..0606989b4d 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 @@ -59,7 +59,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -140,7 +140,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -196,7 +196,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -221,7 +221,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -290,7 +290,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -360,7 +360,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -404,7 +404,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 c13617986b..7a2c7ea56b 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 @@ -60,7 +60,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -92,7 +92,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -117,7 +117,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_coroutine_test( mock_device:expect_native_attr_handler_registration("switch", "switch") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -224,7 +224,7 @@ test.register_coroutine_test( mock_device:expect_native_attr_handler_registration("switch", "switch") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -268,7 +268,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 0d3759c314..28165e645c 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 @@ -115,7 +115,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -167,7 +167,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -247,7 +247,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -275,7 +275,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -303,7 +303,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -331,7 +331,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -374,7 +374,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -417,7 +417,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -447,7 +447,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -481,7 +481,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -513,7 +513,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -545,7 +545,7 @@ do } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -580,7 +580,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -613,7 +613,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -707,7 +707,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -724,7 +724,7 @@ test.register_coroutine_test( }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 c7288aafaf..a09be9a417 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_wyfy_touch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_wyfy_touch.lua @@ -71,7 +71,7 @@ test.register_coroutine_test( mock_parent_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -99,7 +99,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -127,7 +127,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -155,7 +155,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -183,7 +183,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -216,7 +216,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -249,7 +249,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -283,7 +283,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -316,7 +316,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -350,7 +350,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -369,7 +369,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 fbf70da972..fcd724acab 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 @@ -48,7 +48,7 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 7826e3d4e4..7725238086 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 @@ -73,7 +73,7 @@ test.register_coroutine_test( mock_parent:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -124,7 +124,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -174,7 +174,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -201,7 +201,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -229,7 +229,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -256,7 +256,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -284,7 +284,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -326,7 +326,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -368,7 +368,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -410,7 +410,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -452,7 +452,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -476,7 +476,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -500,7 +500,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -532,7 +532,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -564,7 +564,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) 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 8ffb5f93f6..15ef4f227d 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 @@ -85,7 +85,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -124,7 +124,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -163,7 +163,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -202,7 +202,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -241,7 +241,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -280,7 +280,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -319,7 +319,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -359,7 +359,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -399,7 +399,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -439,7 +439,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -483,7 +483,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -526,7 +526,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -569,7 +569,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -612,7 +612,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -655,7 +655,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -698,7 +698,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -741,7 +741,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -784,7 +784,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -827,7 +827,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -870,7 +870,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -932,7 +932,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1011,7 +1011,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1090,7 +1090,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1126,7 +1126,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1162,7 +1162,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1198,7 +1198,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1234,7 +1234,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1270,7 +1270,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1306,7 +1306,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1342,7 +1342,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1378,7 +1378,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1414,7 +1414,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -1450,7 +1450,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 b42d511166..40105722ac 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 @@ -94,7 +94,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -114,7 +114,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -146,7 +146,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -178,7 +178,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -218,7 +218,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -263,7 +263,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -312,7 +312,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -348,7 +348,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -380,7 +380,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -412,7 +412,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -443,7 +443,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -474,7 +474,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -505,7 +505,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -536,7 +536,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -572,7 +572,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -621,7 +621,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -648,7 +648,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -675,7 +675,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -702,7 +702,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -729,7 +729,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -756,7 +756,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -783,7 +783,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -810,7 +810,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -837,7 +837,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -864,7 +864,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -891,7 +891,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -918,7 +918,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -945,7 +945,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -972,7 +972,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -999,7 +999,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1026,7 +1026,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1053,7 +1053,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1080,7 +1080,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1107,7 +1107,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1129,7 +1129,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1157,7 +1157,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1185,7 +1185,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1224,7 +1224,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1272,7 +1272,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -1291,7 +1291,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 b021a25aa4..ab3923bb2a 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 @@ -79,7 +79,7 @@ do test.wait_for_events() end, { - min_api_version = 17 + min_api_version = 14 } ) 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 cd47374f82..efb6bd1ee2 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 @@ -72,7 +72,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -157,7 +157,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -214,7 +214,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -246,7 +246,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -270,7 +270,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -339,7 +339,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -408,7 +408,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -441,7 +441,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 16b15348ce..989bc0f054 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 @@ -82,7 +82,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -150,7 +150,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -184,7 +184,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -218,7 +218,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -252,7 +252,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -286,7 +286,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -320,7 +320,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -360,7 +360,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -400,7 +400,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -440,7 +440,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -480,7 +480,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -520,7 +520,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -560,7 +560,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -600,7 +600,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -640,7 +640,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -664,7 +664,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -688,7 +688,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 d11dd78b8c..ef2e4997b2 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 @@ -74,7 +74,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -101,7 +101,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 15fbe8b065..7c8da1f4ef 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch.lua @@ -78,7 +78,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -105,7 +105,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -129,7 +129,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -155,7 +155,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -178,7 +178,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -204,7 +204,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -238,7 +238,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -272,7 +272,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -306,7 +306,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -340,7 +340,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 608d92a052..ad20f7a864 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 @@ -45,7 +45,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -76,7 +76,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -110,7 +110,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 5ec6b0a340..e008f82e94 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 @@ -57,7 +57,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -79,7 +79,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -118,7 +118,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -160,7 +160,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 f1b382ee67..ad3597766c 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 @@ -63,7 +63,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -85,7 +85,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -150,7 +150,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -162,7 +162,7 @@ test.register_coroutine_test( test.socket.zwave:__expect_send(zw_test_utils.zwave_test_build_send_command( mock_switch, Meter:Get({scale = Meter.scale.electric_meter.KILOWATT_HOURS}))) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 1841b7c97e..53b09297a0 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 @@ -46,7 +46,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -72,7 +72,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) 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 7ea67ed56d..fef5989b44 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 @@ -68,7 +68,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -85,7 +85,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -116,7 +116,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -150,7 +150,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_aeotec_radiator_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_aeotec_radiator_thermostat.lua index 341926c301..9bb950ffca 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_aeotec_radiator_thermostat.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_aeotec_radiator_thermostat.lua @@ -95,7 +95,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -114,7 +114,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -133,7 +133,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -164,7 +164,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -184,7 +184,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -207,7 +207,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -235,7 +235,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -268,7 +268,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_ct100_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_ct100_thermostat.lua index 29645b580d..dce072706c 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_ct100_thermostat.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_ct100_thermostat.lua @@ -137,7 +137,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -165,7 +165,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -193,7 +193,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -221,7 +221,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -280,7 +280,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -339,7 +339,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -404,7 +404,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -473,7 +473,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -499,7 +499,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -524,7 +524,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_fibaro_heat_controller.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_fibaro_heat_controller.lua index eee99d0445..8f5f9011e1 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_fibaro_heat_controller.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_fibaro_heat_controller.lua @@ -116,7 +116,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -174,7 +174,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -193,7 +193,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -216,7 +216,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -235,7 +235,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -266,7 +266,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -286,7 +286,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -309,7 +309,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -355,7 +355,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -383,7 +383,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -416,7 +416,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_popp_radiator_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_popp_radiator_thermostat.lua index 76b6dcc423..2c74e4c5be 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_popp_radiator_thermostat.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_popp_radiator_thermostat.lua @@ -62,7 +62,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -81,7 +81,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -112,7 +112,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -135,7 +135,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -159,7 +159,7 @@ test.register_coroutine_test( test.mock_time.advance_time(200) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -203,7 +203,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_qubino_flush_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_qubino_flush_thermostat.lua index d2cc7cd938..a1842ae574 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_qubino_flush_thermostat.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_qubino_flush_thermostat.lua @@ -123,7 +123,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -146,7 +146,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -172,7 +172,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -193,7 +193,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -213,7 +213,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -240,7 +240,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -267,7 +267,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -324,7 +324,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -376,7 +376,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -404,7 +404,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -437,7 +437,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -465,7 +465,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -487,7 +487,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -517,7 +517,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -570,7 +570,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_stelpro_ki_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_stelpro_ki_thermostat.lua index 2af3f3e24c..bb4043f969 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_stelpro_ki_thermostat.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_stelpro_ki_thermostat.lua @@ -68,7 +68,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -107,7 +107,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -146,7 +146,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -190,7 +190,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -234,7 +234,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -273,7 +273,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -312,7 +312,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -330,7 +330,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -351,7 +351,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -377,7 +377,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_thermostat_heating_battery.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_thermostat_heating_battery.lua index bf6fc0e42e..70ec5c90d2 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_thermostat_heating_battery.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_thermostat_heating_battery.lua @@ -104,7 +104,7 @@ test.register_coroutine_test( do_initial_setup() end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -123,7 +123,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -155,7 +155,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -217,7 +217,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -239,7 +239,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -271,7 +271,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -303,7 +303,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -332,7 +332,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -425,7 +425,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -539,7 +539,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -642,7 +642,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -728,7 +728,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -770,7 +770,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -810,7 +810,7 @@ test.register_coroutine_test( end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_zwave_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_zwave_thermostat.lua index 900331c254..f4d0a6c811 100644 --- a/drivers/SmartThings/zwave-thermostat/src/test/test_zwave_thermostat.lua +++ b/drivers/SmartThings/zwave-thermostat/src/test/test_zwave_thermostat.lua @@ -144,7 +144,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -163,7 +163,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -182,7 +182,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -201,7 +201,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -226,7 +226,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -250,7 +250,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -281,7 +281,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 15 } ) @@ -304,7 +304,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -324,7 +324,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -344,7 +344,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -367,7 +367,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -390,7 +390,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -418,7 +418,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -446,7 +446,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -474,7 +474,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -502,7 +502,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -530,7 +530,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -558,7 +558,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -591,7 +591,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -645,7 +645,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -698,7 +698,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -753,7 +753,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-valve/src/test/test_inverse.valve.lua b/drivers/SmartThings/zwave-valve/src/test/test_inverse.valve.lua index 801a1608eb..7e53d6e943 100644 --- a/drivers/SmartThings/zwave-valve/src/test/test_inverse.valve.lua +++ b/drivers/SmartThings/zwave-valve/src/test/test_inverse.valve.lua @@ -49,7 +49,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -70,7 +70,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -91,7 +91,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -112,7 +112,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -145,7 +145,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -178,7 +178,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-valve/src/test/test_zwave_valve.lua b/drivers/SmartThings/zwave-valve/src/test/test_zwave_valve.lua index 3743f62fd5..1b689e6903 100644 --- a/drivers/SmartThings/zwave-valve/src/test/test_zwave_valve.lua +++ b/drivers/SmartThings/zwave-valve/src/test/test_zwave_valve.lua @@ -71,7 +71,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -90,7 +90,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -121,7 +121,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -152,7 +152,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -185,7 +185,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -218,7 +218,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -251,7 +251,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -284,7 +284,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) diff --git a/drivers/SmartThings/zwave-virtual-momentary-switch/src/test/test_zwave_virtual_momentary_switch.lua b/drivers/SmartThings/zwave-virtual-momentary-switch/src/test/test_zwave_virtual_momentary_switch.lua index e496b5a629..0971b8653e 100644 --- a/drivers/SmartThings/zwave-virtual-momentary-switch/src/test/test_zwave_virtual_momentary_switch.lua +++ b/drivers/SmartThings/zwave-virtual-momentary-switch/src/test/test_zwave_virtual_momentary_switch.lua @@ -67,7 +67,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -86,7 +86,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -113,7 +113,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -136,7 +136,7 @@ test.register_message_test( }, { inner_block_ordering = "relaxed", - min_api_version = 17 + min_api_version = 14 } ) @@ -186,7 +186,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -236,7 +236,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -266,7 +266,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 ffeb175b43..85567ad47d 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 @@ -68,7 +68,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -94,7 +94,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -120,7 +120,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -153,7 +153,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -186,7 +186,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -219,7 +219,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -253,7 +253,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -287,7 +287,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -309,7 +309,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -343,7 +343,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -377,7 +377,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -405,7 +405,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -431,7 +431,7 @@ test.register_coroutine_test( assert(mock_fibaro_roller_shutter:get_field("calibration") == "done", "Calibration should be done") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -456,7 +456,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -483,7 +483,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -510,7 +510,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -537,7 +537,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -564,7 +564,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -591,7 +591,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -618,7 +618,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -645,7 +645,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -672,7 +672,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -699,7 +699,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -726,7 +726,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -753,7 +753,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -780,7 +780,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -807,7 +807,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -834,7 +834,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -851,7 +851,7 @@ test.register_coroutine_test( mock_fibaro_roller_shutter_venetian:expect_metadata_update({ profile = "fibaro-roller-shutter" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -867,7 +867,7 @@ test.register_coroutine_test( mock_fibaro_roller_shutter_venetian:expect_metadata_update({ profile = "fibaro-roller-shutter-venetian" }) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 4127e8f7c4..9208b2d198 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 @@ -76,7 +76,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -109,7 +109,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -142,7 +142,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -175,7 +175,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -208,7 +208,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -230,7 +230,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -264,7 +264,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -297,7 +297,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -325,7 +325,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -358,7 +358,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -383,7 +383,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -410,7 +410,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -437,7 +437,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -464,7 +464,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -491,7 +491,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -518,7 +518,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -563,7 +563,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -582,7 +582,7 @@ test.register_coroutine_test( mock_qubino_flush_shutter:expect_metadata_update({ profile = "qubino-flush-shutter-venetian" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -601,7 +601,7 @@ test.register_coroutine_test( mock_qubino_flush_shutter:expect_metadata_update({ profile = "qubino-flush-shutter" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -623,7 +623,7 @@ test.register_coroutine_test( assert(targetValue == mock_qubino_flush_shutter.transient_store.shade_target, "driver should chache correct level value") end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -653,7 +653,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -686,7 +686,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -713,7 +713,7 @@ test.register_message_test( } }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -739,7 +739,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 15 } ) @@ -772,7 +772,7 @@ do ) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 8f6442b0ee..b2b968750c 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 @@ -48,7 +48,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -69,7 +69,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -102,7 +102,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -141,7 +141,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -170,7 +170,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -199,7 +199,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -240,7 +240,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -287,7 +287,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -301,7 +301,7 @@ test.register_coroutine_test( mock_window_button:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -334,7 +334,7 @@ test.register_message_test( }, }, { - min_api_version = 17 + min_api_version = 14 } ) @@ -349,7 +349,7 @@ test.register_coroutine_test( ) end, { - min_api_version = 17 + min_api_version = 14 } ) @@ -369,7 +369,7 @@ test.register_coroutine_test( )) end, { - min_api_version = 17 + min_api_version = 14 } ) 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 1cf3335d9e..23f96507ca 100644 --- a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_curtain.lua +++ b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_curtain.lua @@ -59,7 +59,10 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_simple_device.id, zigbee_test_utils.build_bind_request(mock_simple_device, zigbee_test_utils.mock_hub_eui, Basic.ID) }) test.socket.zigbee:__expect_send({ mock_simple_device.id, Basic.attributes.ApplicationVersion:configure_reporting(mock_simple_device, 30, 300, 1) }) mock_simple_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end + end, + { + min_api_version = 17 + } ) test.register_coroutine_test( @@ -89,7 +92,10 @@ test.register_coroutine_test( tuya_utils.build_send_tuya_command(mock_simple_device, '\x05', tuya_utils.DP_TYPE_ENUM, '\x00', 1) } ) - end + end, + { + min_api_version = 17 + } ) test.register_coroutine_test( @@ -131,7 +137,10 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_simple_device:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}})) ) - end + end, + { + min_api_version = 17 + } ) test.register_message_test( @@ -152,7 +161,10 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) } - } + }, + { + min_api_version = 17 + } ) test.register_message_test( @@ -173,7 +185,10 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) } - } + }, + { + min_api_version = 17 + } ) test.register_message_test( @@ -189,7 +204,10 @@ test.register_message_test( direction = "send", message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x01', tuya_utils.DP_TYPE_ENUM, '\x01', 0) } } - } + }, + { + min_api_version = 17 + } ) test.register_message_test( @@ -205,7 +223,10 @@ test.register_message_test( 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', 0) } } - } + }, + { + min_api_version = 17 + } ) test.register_message_test( @@ -221,7 +242,10 @@ test.register_message_test( 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', 0) } } - } + }, + { + min_api_version = 17 + } ) test.register_message_test( @@ -242,7 +266,10 @@ test.register_message_test( direction = "send", message = mock_simple_device:generate_test_message("main", capabilities.windowShade.windowShade("partially open")) } - } + }, + { + min_api_version = 17 + } ) test.register_message_test( @@ -259,7 +286,10 @@ test.register_message_test( -- For _TZE284_nladmfvf, level is inverted: 100 - 10 = 90 (0x5A) message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x5a', 0) } } - } + }, + { + min_api_version = 17 + } ) test.register_message_test( @@ -276,7 +306,10 @@ test.register_message_test( -- For _TZE284_nladmfvf, level is inverted: 100 - 0 = 100 (0x64), but clamped to 0 so 100 - 0 = 100 message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x64', 0) } } - } + }, + { + min_api_version = 17 + } ) test.register_coroutine_test( @@ -305,7 +338,10 @@ test.register_coroutine_test( mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x28', 0) }) - end + end, + { + min_api_version = 17 + } ) test.register_coroutine_test( @@ -334,7 +370,10 @@ test.register_coroutine_test( mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x00', 0) }) - end + end, + { + min_api_version = 17 + } ) test.register_coroutine_test( @@ -363,7 +402,10 @@ test.register_coroutine_test( mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x64', 0) }) - end + end, + { + min_api_version = 17 + } ) test.register_coroutine_test( @@ -405,7 +447,10 @@ test.register_coroutine_test( mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x28', 1) }) - end + end, + { + min_api_version = 17 + } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() 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 97537c36ad..da6c87843a 100644 --- a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_switch.lua +++ b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_switch.lua @@ -254,6 +254,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x02', tuya_utils.DP_TYPE_BOOL, '\x01', 0) } } + }, + { + min_api_version = 17 } ) @@ -270,6 +273,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x03', tuya_utils.DP_TYPE_BOOL, '\x01', 0) } } + }, + { + min_api_version = 17 } ) @@ -286,6 +292,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x04', tuya_utils.DP_TYPE_BOOL, '\x01', 0) } } + }, + { + min_api_version = 17 } ) @@ -302,6 +311,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x05', tuya_utils.DP_TYPE_BOOL, '\x01', 0) } } + }, + { + min_api_version = 17 } ) @@ -318,6 +330,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x06', tuya_utils.DP_TYPE_BOOL, '\x01', 0) } } + }, + { + min_api_version = 17 } ) @@ -334,6 +349,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x01', tuya_utils.DP_TYPE_BOOL, '\x00', 0) } } + }, + { + min_api_version = 17 } ) @@ -350,6 +368,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x02', tuya_utils.DP_TYPE_BOOL, '\x00', 0) } } + }, + { + min_api_version = 17 } ) @@ -366,6 +387,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x03', tuya_utils.DP_TYPE_BOOL, '\x00', 0) } } + }, + { + min_api_version = 17 } ) @@ -382,6 +406,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x04', tuya_utils.DP_TYPE_BOOL, '\x00', 0) } } + }, + { + min_api_version = 17 } ) @@ -398,6 +425,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x05', tuya_utils.DP_TYPE_BOOL, '\x00', 0) } } + }, + { + min_api_version = 17 } ) @@ -414,6 +444,9 @@ test.register_message_test( direction = "send", message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x06', tuya_utils.DP_TYPE_BOOL, '\x00', 0) } } + }, + { + min_api_version = 17 } ) @@ -460,4 +493,4 @@ test.register_coroutine_test( end ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() diff --git a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_thermostat.lua b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_thermostat.lua index 4df634bf18..6198f22f94 100644 --- a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_thermostat.lua +++ b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_thermostat.lua @@ -166,7 +166,9 @@ test.register_message_test( } } }, - {} + { + min_api_version = 17 + } ) test.register_message_test( @@ -200,7 +202,9 @@ test.register_message_test( } } }, - {} + { + min_api_version = 17 + } ) test.register_message_test( @@ -234,7 +238,9 @@ test.register_message_test( } } }, - {} + { + min_api_version = 17 + } ) test.register_message_test( @@ -268,7 +274,9 @@ test.register_message_test( } } }, - {} + { + min_api_version = 17 + } ) test.register_message_test( @@ -302,7 +310,9 @@ test.register_message_test( } } }, - {} + { + min_api_version = 17 + } ) test.register_message_test( @@ -336,7 +346,9 @@ test.register_message_test( } } }, - {} + { + min_api_version = 17 + } ) test.register_message_test( From d934797966cc312862659900343147e8b200bd01 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 18 Aug 2026 11:46:03 -0500 Subject: [PATCH 500/538] WWSTCERT-12230 IP65 Outdoor Zwave Smart Plug with 2 Individual Outlets (#3113) --- drivers/SmartThings/zwave-switch/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index 2ca0fa3c73..c9e58b38f1 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -879,6 +879,12 @@ zwaveManufacturer: productType: 0xFF00 productId: 0xFF0E deviceProfileName: metering-switch + - id: "0312/FF01/FF97" + deviceLabel: IP65 Outdoor Zwave Smart Plug with 2 Individual Outlets + manufacturerId: 0x0312 + productId: 0xFF97 + productType: 0xFF01 + deviceProfileName: switch-binary - id: Evolve/RelaySwitch deviceLabel: Evolve Switch manufacturerId: 0x0113 From b92335eff9d6e783e48f0a08dd80d8da3156aa30 Mon Sep 17 00:00:00 2001 From: seojune79 <119141897+seojune79@users.noreply.github.com> Date: Wed, 19 Aug 2026 03:29:48 +0900 Subject: [PATCH 501/538] [Aqara] Aqara Dual Relay Module T2 (#3146) * add Aqara Dual Relay Module T2 * restore previous naming convention * improve test cases * correct incorrect model information * remove whitespace * Changed the single-element table to a boolean flag for coercing the preference value. * Add comment explaining omission of unsupported mode --- .../zigbee-switch/fingerprints.yml | 5 + .../aqara-dual-relay-module-unified.yml | 40 +++ .../zigbee-switch/src/aqara/can_handle.lua | 2 + .../zigbee-switch/src/aqara/fingerprints.lua | 37 +-- .../zigbee-switch/src/aqara/init.lua | 135 ++++++++- .../src/aqara/multi-switch/can_handle.lua | 2 + .../src/aqara/multi-switch/fingerprints.lua | 19 +- .../src/aqara/multi-switch/init.lua | 43 ++- .../src/test/test_aqara_dual_relay_module.lua | 268 ++++++++++++++++++ .../src/test/test_aqara_switch_no_power.lua | 89 +++--- .../src/test/test_aqara_switch_power.lua | 59 +++- 11 files changed, 606 insertions(+), 93 deletions(-) create mode 100644 drivers/SmartThings/zigbee-switch/profiles/aqara-dual-relay-module-unified.yml create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_aqara_dual_relay_module.lua diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 08eb875591..bebe5295a2 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -111,6 +111,11 @@ zigbeeManufacturer: manufacturer: LUMI model: lumi.switch.n0acn2 deviceProfileName: aqara-switch-module + - id: "Aqara/lumi.switch.acn047" + deviceLabel: Aqara Dual Relay Module T2 (With Neutral) 1 + manufacturer: Aqara + model: lumi.switch.acn047 + deviceProfileName: aqara-dual-relay-module-unified - id: "LUMI/lumi.light.acn004" deviceLabel: Aqara Smart Dimmer Controller T1 Pro manufacturer: LUMI diff --git a/drivers/SmartThings/zigbee-switch/profiles/aqara-dual-relay-module-unified.yml b/drivers/SmartThings/zigbee-switch/profiles/aqara-dual-relay-module-unified.yml new file mode 100644 index 0000000000..b77d7eac0f --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/aqara-dual-relay-module-unified.yml @@ -0,0 +1,40 @@ +name: aqara-dual-relay-module-unified +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: button + version: 1 + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch + - id: interlock + capabilities: + - id: mode + version: 1 + categories: + - name: Switch + - id: devicemode + capabilities: + - id: mode + version: 1 + categories: + - name: Switch +preferences: + - preferenceId: stse.powerOffMemory + explicit: true + - preferenceId: stse.pulseIntervalSetting + explicit: true + - preferenceId: stse.switchType + explicit: true + - preferenceId: stse.changeToWirelessSwitch + explicit: true +metadata: + mnmn: SolutionsEngineering + vid: SmartThings-smartthings-Aqara_Dual_Relay_Module_T2 diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/aqara/can_handle.lua index e183f78266..c0eb27343e 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/can_handle.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/can_handle.lua @@ -1,6 +1,8 @@ -- Copyright 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +-- Matches any device whose manufacturer/model is listed in aqara/fingerprints.lua, routing it to +-- the aqara sub-driver (and, in turn, its version / multi-switch sub-drivers). return function(opts, driver, device) local FINGERPRINTS = require("aqara.fingerprints") for _, fingerprint in ipairs(FINGERPRINTS) do diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/aqara/fingerprints.lua index 63f94e2be3..3fc2fbc3e3 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/fingerprints.lua @@ -2,21 +2,22 @@ -- 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" } - } + { 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" }, + { mfr = "Aqara", model = "lumi.switch.acn047" } +} diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/init.lua b/drivers/SmartThings/zigbee-switch/src/aqara/init.lua index 0c64b594fe..d3666a3dcb 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/init.lua @@ -23,9 +23,31 @@ local CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID = 0x0200 local RESTORE_TURN_OFF_INDICATOR_LIGHT_ATTRIBUTE_ID = 0x0203 local MAX_POWER_ATTRIBUTE_ID = 0x020B local ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID = 0x000A - +-- Aqara private cluster (0xFCC0) attributes used by the Dual Relay Module T2 (lumi.switch.acn047) +local DEVICE_MODE_ATTRIBUTE_ID = 0x0289 -- relay working mode (wet/dry contact, pulse), Uint8 0..3 +local INTERLOCK_ATTRIBUTE_ID = 0x02D0 -- interlock between the two relays, Boolean +local POWER_OFF_MEMORY_ATTRIBUTE_ID = 0x0517 -- power-off memory behavior, Uint8 (see powerOffMemory value_map) +local PULSE_INTERVAL_ATTRIBUTE_ID = 0x00EB -- pulse width in ms when running in pulse mode, Uint16 local LAST_REPORT_TIME = "LAST_REPORT_TIME" local PRIVATE_MODE = "PRIVATE_MODE" +-- "interlock" / "devicemode" are extra profile components on aqara-dual-relay-module-unified.yml. +-- The order of the SUPPORTED_* lists matches the raw device values (0-based), see the handlers below. +local COMPONENT_INTERLOCK = "interlock" +local SUPPORTED_INTERLOCK = { "normal", "interlock" } +local COMPONENT_DEVICE_MODE = "devicemode" +-- Relay working modes mapped to their raw device values. dry_contact_open_pulse_mode (raw 2) is +-- intentionally not exposed; the remaining modes keep their original device values (on_off stays 3). +-- This mode is not supported by the Aqara app and cannot be entered by the device, so it is omitted. +local DEVICE_MODE_TO_VALUE = { + wet_contact_mode = 0, + dry_contact_closed_pulse_mode = 1, + dry_contact_on_off_mode = 3, +} +local DEVICE_MODE_FROM_VALUE = { + [0] = "wet_contact_mode", + [1] = "dry_contact_closed_pulse_mode", + [3] = "dry_contact_on_off_mode", +} local preference_map = { ["stse.restorePowerState"] = { @@ -39,7 +61,7 @@ local preference_map = { attribute_id = CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID, mfg_code = MFG_CODE, data_type = data_types.Uint8, - value_map = { [true] = 0x00,[false] = 0x01 }, + value_map = { [true] = 0x00, [false] = 0x01 }, }, ["stse.maxPower"] = { cluster_id = PRIVATE_CLUSTER_ID, @@ -112,26 +134,71 @@ local preference_map = { data_type = data_types.Uint8, value_map = { rocker = 0x01, rebound = 0x02 }, }, + -- External switch wiring type (same attribute as stse.electricSwitchType, with an extra "nc" + -- option): rocker = maintained, rebound/button = momentary, nc = external switch ignored (normally closed). + ["stse.switchType"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID, + mfg_code = MFG_CODE, + data_type = data_types.Uint8, + value_map = { rocker = 0x01, rebound = 0x02, nc = 0x00 }, + }, ["stse.turnOffIndicatorLight"] = { cluster_id = PRIVATE_CLUSTER_ID, attribute_id = RESTORE_TURN_OFF_INDICATOR_LIGHT_ATTRIBUTE_ID, mfg_code = MFG_CODE, data_type = data_types.Boolean, }, + ["stse.powerOffMemory"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = POWER_OFF_MEMORY_ATTRIBUTE_ID, + mfg_code = MFG_CODE, + data_type = data_types.Uint8, + value_map = { restore = 0x01, on = 0x00, off = 0x02, reverse = 0x03 }, + }, + ["stse.pulseIntervalSetting"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = PULSE_INTERVAL_ATTRIBUTE_ID, + mfg_code = MFG_CODE, + data_type = data_types.Uint16, + coerce_number = true, + } } - +-- Handles reports of the private-mode attribute. Caches the current private-mode state and, when the +-- device is not yet in private mode, forces it into private mode and configures energy reporting. +-- acn047 is excluded from being forced into private mode (it stays on standard clusters). local function private_mode_handler(driver, device, value, zb_rx) device:set_field(PRIVATE_MODE, value.value, { persist = true }) if value.value ~= 1 then - device:send(cluster_base.write_manufacturer_specific_attribute(device, - PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) -- private + if device:get_model() ~= "lumi.switch.acn047" then + device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) -- private + end device:send(SimpleMetering.attributes.CurrentSummationDelivered:configure_reporting(device, 900, 3600, 1)) -- minimal interval : 15min device:set_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, 10, { persist = true }) device:set_field(constants.SIMPLE_METERING_DIVISOR_KEY, 1000, { persist = true }) end end +-- Reflect the device's interlock state onto the "interlock" component. +-- value.value is a Boolean; +1 converts the 0/1 state into a Lua (1-based) list index. +local function interlock_switch_handler(driver, device, value, zb_rx) + local component = device.profile.components[COMPONENT_INTERLOCK] + if component == nil then return end + local cur_state = 0 + if value.value then cur_state = 1 end + device:emit_component_event(component, capabilities.mode.mode(SUPPORTED_INTERLOCK[cur_state + 1])) +end +-- Reflect the relay working mode onto the "devicemode" component. +-- value.value is the raw device value; only emit for modes we expose (open_pulse is ignored). +local function device_mode_handler(driver, device, value, zb_rx) + local component = device.profile.components[COMPONENT_DEVICE_MODE] + if component == nil then return end + local mode = DEVICE_MODE_FROM_VALUE[value.value] + if mode ~= nil then + device:emit_component_event(component, capabilities.mode.mode(mode)) + end +end local function wireless_switch_handler(driver, device, value, zb_rx) if value.value == 1 then @@ -151,7 +218,7 @@ local function energy_meter_power_consumption_report(driver, device, value, zb_r if raw_value < 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}) + device:set_field(constants.ENERGY_METER_OFFSET, offset, { persist = true }) end device:emit_event(capabilities.energyMeter.energy({ value = raw_value - offset, unit = "Wh" })) @@ -166,8 +233,7 @@ local function energy_meter_power_consumption_report(driver, device, value, zb_r -- power consumption report local delta_energy = 0.0 - local current_power_consumption = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, - capabilities.powerConsumptionReport.powerConsumption.NAME) + 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 - current_power_consumption.energy, 0.0) end @@ -184,14 +250,40 @@ local function power_meter_handler(driver, device, value, zb_rx) device:emit_event(capabilities.powerMeter.power({ value = raw_value, unit = "W" })) end +-- setMode command handler shared by the "interlock" and "devicemode" components. +-- The target component is used to decide which private-cluster attribute to write. +local function mode_handler(driver, device, command) + if command.component == COMPONENT_INTERLOCK then + -- interlock attribute is a Boolean: true = relays interlocked, false = independent + local interlock_mode = false + if command.args.mode == SUPPORTED_INTERLOCK[2] then interlock_mode = true end + device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, INTERLOCK_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, interlock_mode)) + elseif command.component == COMPONENT_DEVICE_MODE then + -- map the selected mode string to its raw device value + local device_mode = DEVICE_MODE_TO_VALUE[command.args.mode] + if device_mode ~= nil then + device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, DEVICE_MODE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, device_mode)) + end + end +end +-- Read back switch state, power/energy (standard clusters) and, when present, the interlock and +-- device-mode private attributes. local function do_refresh(self, device) device:send(OnOff.attributes.OnOff:read(device)) if (device:supports_capability_by_id(capabilities.powerMeter.ID)) then device:send(ElectricalMeasurement.attributes.ActivePower:read(device)) device:send(SimpleMetering.attributes.CurrentSummationDelivered:read(device)) end + if device.profile.components[COMPONENT_INTERLOCK] then + device:send(cluster_base.read_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, INTERLOCK_ATTRIBUTE_ID, MFG_CODE)) + end + if device.profile.components[COMPONENT_DEVICE_MODE] then + device:send(cluster_base.read_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, DEVICE_MODE_ATTRIBUTE_ID, MFG_CODE)) + end end +-- On preference change, write any preference whose value changed to its mapped private-cluster +-- attribute (see preference_map). value_map translates enum strings; value_type coerces numbers. local function device_info_changed(driver, device, event, args) local preferences = device.preferences local old_preferences = args.old_st_store.preferences @@ -203,28 +295,36 @@ local function device_info_changed(driver, device, event, args) if attr.value_map ~= nil then value = attr.value_map[value] end - device:send(cluster_base.write_manufacturer_specific_attribute(device, attr.cluster_id, attr.attribute_id, - attr.mfg_code, attr.data_type, value)) + -- numeric preferences (e.g. pulseInterval) are coerced to a Lua number before being written + if attr.coerce_number ~= nil and attr.coerce_number then + value = tonumber(value) + end + device:send(cluster_base.write_manufacturer_specific_attribute(device, attr.cluster_id, attr.attribute_id, attr.mfg_code, attr.data_type, value)) end end end end +-- Standard configuration: bind/report standard clusters, read the private-mode attribute, clear any +-- groups (required by these devices) and refresh current state. local function do_configure(self, device) device:configure() - device:send(cluster_base.read_manufacturer_specific_attribute(device, - PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE)) + device:send(cluster_base.read_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE)) device:send(Groups.server.commands.RemoveAllGroups(device)) -- required do_refresh(self, device) end +-- On add, advertise supported button values and restore the last known power/energy (so the values +-- are not blanked to 0 on re-add). local function device_added(driver, device) if (device:supports_capability_by_id(capabilities.button.ID)) then device:emit_event(capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } })) end if (device:supports_capability_by_id(capabilities.powerMeter.ID)) then - device:emit_event(capabilities.powerMeter.power({ value = 0.0, unit = "W" })) - device:emit_event(capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) + local lastPower = device:get_latest_state("main", capabilities.powerMeter.ID, capabilities.powerMeter.power.NAME) or 0.0 + local lastEnergy = device:get_latest_state("main", capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME) or 0.0 + device:emit_event(capabilities.powerMeter.power({ value = lastPower, unit = "W" })) + device:emit_event(capabilities.energyMeter.energy({ value = lastEnergy, unit = "Wh" })) end end @@ -236,6 +336,9 @@ local aqara_switch_handler = { infoChanged = device_info_changed }, capability_handlers = { + [capabilities.mode.ID] = { + [capabilities.mode.commands.setMode.NAME] = mode_handler + }, [capabilities.refresh.ID] = { [capabilities.refresh.commands.refresh.NAME] = do_refresh } @@ -252,7 +355,9 @@ local aqara_switch_handler = { [WIRELESS_SWITCH_ATTRIBUTE_ID] = wireless_switch_handler }, [PRIVATE_CLUSTER_ID] = { - [PRIVATE_ATTRIBUTE_ID] = private_mode_handler + [PRIVATE_ATTRIBUTE_ID] = private_mode_handler, + [INTERLOCK_ATTRIBUTE_ID] = interlock_switch_handler, + [DEVICE_MODE_ATTRIBUTE_ID] = device_mode_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 index 1524296cff..16d61cfbbf 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/can_handle.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/can_handle.lua @@ -1,6 +1,8 @@ -- Copyright 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +-- Matches multi-endpoint Aqara switches/modules listed in aqara/multi-switch/fingerprints.lua +-- (devices that create child devices for their extra switch endpoints). return function(opts, driver, device) local FINGERPRINTS = require("aqara.multi-switch.fingerprints") for _, fingerprint in ipairs(FINGERPRINTS) do diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/fingerprints.lua index 71ed1b26b3..22887a7e88 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/fingerprints.lua @@ -2,13 +2,14 @@ -- 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" } + { 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" }, + { mfr = "Aqara", model = "lumi.switch.acn047", children = 2, 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 4b2146f03c..07022c0b67 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/init.lua @@ -11,8 +11,13 @@ local switch_utils = require "switch_utils" local PRIVATE_CLUSTER_ID = 0xFCC0 local PRIVATE_ATTRIBUTE_ID = 0x0009 local MFG_CODE = 0x115F +local COMPONENT_INTERLOCK = "interlock" +local SUPPORTED_INTERLOCK = { "normal", "interlock" } +local COMPONENT_DEVICE_MODE = "devicemode" +local SUPPORTED_DEVICE_MODE = { "wet_contact_mode", "dry_contact_closed_pulse_mode", "dry_contact_on_off_mode" } local FINGERPRINTS = require("aqara.multi-switch.fingerprints") +-- Number of switch endpoints (parent + children) for this model, from the fingerprint table. local function get_children_amount(device) for _, fingerprint in ipairs(FINGERPRINTS) do if device:get_model() == fingerprint.model then @@ -21,6 +26,7 @@ local function get_children_amount(device) end end +-- Profile name to assign to the created child devices for this model. local function get_child_profile_name(device) for _, fingerprint in ipairs(FINGERPRINTS) do if device:get_model() == fingerprint.model then @@ -37,6 +43,7 @@ local function find_child(parent, ep_id) return parent:get_child_by_parent_assigned_key(string.format("%02X", ep_id)) end +-- Create one EDGE_CHILD device per extra switch endpoint and initialize parent-only state. local function device_added(driver, device) -- Only create children for the actual Zigbee device and not the children if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then @@ -60,22 +67,36 @@ local function device_added(driver, device) end -- for wireless button - device:emit_event(capabilities.button.numberOfButtons({ value = children_amount }, - { visibility = { displayed = false } })) - device:emit_event(capabilities.powerMeter.power({ value = 0.0, unit = "W" })) - device:emit_event(capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) + device:emit_event(capabilities.button.numberOfButtons({ value = children_amount }, { visibility = { displayed = false } })) - device:send(cluster_base.write_manufacturer_specific_attribute(device, - PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) -- private + -- report the static supported modes for the interlock / devicemode components (when present) + if device.profile.components[COMPONENT_INTERLOCK] then + device:emit_component_event(device.profile.components[COMPONENT_INTERLOCK], capabilities.mode.supportedModes(SUPPORTED_INTERLOCK, { visibility = { displayed = false } })) + end + if device.profile.components[COMPONENT_DEVICE_MODE] then + device:emit_component_event(device.profile.components[COMPONENT_DEVICE_MODE], capabilities.mode.supportedModes(SUPPORTED_DEVICE_MODE, { visibility = { displayed = false } })) + end + + -- acn047 (Dual Relay Module T2) must not be forced into Aqara private mode; all other + -- multi-switch models are switched into private mode here. + if device:get_model() ~= "lumi.switch.acn047" then + device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) -- private + end elseif device.network_type == "DEVICE_EDGE_CHILD" then - device:emit_event(capabilities.button.numberOfButtons({ value = 1 }, - { visibility = { displayed = false } })) + device:emit_event(capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } })) + end + device:emit_event(capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } })) + switch_utils.emit_event_if_latest_state_missing(device, "main", capabilities.button, capabilities.button.button.NAME, capabilities.button.button.pushed({ state_change = false })) + -- restore the last known power/energy (instead of resetting to 0) so values survive re-adds + if (device:supports_capability_by_id(capabilities.powerMeter.ID)) then + local lastPower = device:get_latest_state("main", capabilities.powerMeter.ID, capabilities.powerMeter.power.NAME) or 0.0 + local lastEnergy = device:get_latest_state("main", capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME) or 0.0 + device:emit_event(capabilities.powerMeter.power({ value = lastPower, unit = "W" })) + device:emit_event(capabilities.energyMeter.energy({ value = lastEnergy, unit = "Wh" })) end - device:emit_event(capabilities.button.supportedButtonValues({ "pushed" }, - { visibility = { displayed = false } })) - switch_utils.emit_event_if_latest_state_missing(device, "main", capabilities.button, capabilities.button.button.NAME, capabilities.button.button.pushed({state_change = false})) end +-- Register the endpoint->child routing function so reports from child endpoints reach the children. local function device_init(self, device) -- for multiple switch if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_dual_relay_module.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_dual_relay_module.lua new file mode 100644 index 0000000000..3dd95f7bb1 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_dual_relay_module.lua @@ -0,0 +1,268 @@ +-- 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.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local OnOff = clusters.OnOff +local ElectricalMeasurement = clusters.ElectricalMeasurement +local SimpleMetering = clusters.SimpleMetering + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local MFG_CODE = 0x115F +local INTERLOCK_ATTRIBUTE_ID = 0x02D0 +local DEVICE_MODE_ATTRIBUTE_ID = 0x0289 +local POWER_OFF_MEMORY_ATTRIBUTE_ID = 0x0517 +local PULSE_INTERVAL_ATTRIBUTE_ID = 0x00EB +local ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID = 0x000A +local CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID = 0x0200 + +local PRIVATE_MODE = "PRIVATE_MODE" + +local SUPPORTED_INTERLOCK = { "normal", "interlock" } +local SUPPORTED_DEVICE_MODE = { "wet_contact_mode", "dry_contact_closed_pulse_mode", "dry_contact_on_off_mode" } + +-- acn047 (Dual Relay Module T2) runs on standard clusters (never Aqara private mode) +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-dual-relay-module-unified.yml"), + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Aqara", + model = "lumi.switch.acn047", + server_clusters = { OnOff.ID, ElectricalMeasurement.ID, SimpleMetering.ID } + } + } + } +) + +-- acn047 reports children = 2, so a single child device (relay 2) exists on the second endpoint +local mock_child = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("aqara-switch-child.yml"), + 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() + test.mock_device.add_test_device(mock_device) + test.mock_device.add_test_device(mock_child) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "added: child is reused, button info reported, power/energy restored (0.0 on first add) and no private write", + function() + test.socket.zigbee:__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", capabilities.button.numberOfButtons({ value = 2 }, + { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("interlock", + capabilities.mode.supportedModes(SUPPORTED_INTERLOCK, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("devicemode", + capabilities.mode.supportedModes(SUPPORTED_DEVICE_MODE, { visibility = { displayed = false } }))) + 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", capabilities.button.button.pushed({ state_change = false }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 0.0, unit = "W" }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" }))) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "refresh reads OnOff, power/energy (standard clusters) and the interlock/devicemode attributes", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } }) + 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, SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, INTERLOCK_ATTRIBUTE_ID, MFG_CODE) }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, DEVICE_MODE_ATTRIBUTE_ID, MFG_CODE) }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "switch on command : parent device", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "switch", component = "main", command = "on", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.On(mock_device) }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "switch off command : child device (endpoint 2)", + function() + test.socket.capability:__queue_receive({ mock_child.id, + { capability = "switch", component = "main", command = "off", args = {} } }) + mock_child:expect_native_cmd_handler_registration("switch", "off") + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.server.commands.Off(mock_device):to_endpoint(0x02) }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "power meter report (standard ElectricalMeasurement cluster)", + function() + mock_device:set_field(PRIVATE_MODE, 0, { persist = true }) + test.socket.zigbee:__queue_receive({ + mock_device.id, + ElectricalMeasurement.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, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "interlock attribute report updates the interlock component", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, { + { INTERLOCK_ATTRIBUTE_ID, data_types.Boolean.ID, true } + }, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("interlock", + capabilities.mode.mode("interlock"))) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "device mode attribute report updates the devicemode component", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, { + { DEVICE_MODE_ATTRIBUTE_ID, data_types.Uint8.ID, 1 } + }, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("devicemode", + capabilities.mode.mode("dry_contact_closed_pulse_mode"))) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "setMode on the interlock component writes the interlock attribute", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "mode", component = "interlock", command = "setMode", args = { "interlock" } } }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, INTERLOCK_ATTRIBUTE_ID, + MFG_CODE, data_types.Boolean, true) }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "setMode on the devicemode component (on/off) writes device value 3", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "mode", component = "devicemode", command = "setMode", args = { "dry_contact_on_off_mode" } } }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, DEVICE_MODE_ATTRIBUTE_ID, + MFG_CODE, data_types.Uint8, 3) }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "preference powerOffMemory is written on infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.powerOffMemory"] = "on" } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + POWER_OFF_MEMORY_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0) }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "preference pulseInterval is written on infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.pulseIntervalSetting"] = 500 } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + PULSE_INTERVAL_ATTRIBUTE_ID, MFG_CODE, data_types.Uint16, 500) }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "preference switchType (nc) is written on infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.switchType"] = "nc" } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0) }) + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "preference changeToWirelessSwitch is written on infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.changeToWirelessSwitch"] = true } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0) }) + end, + { + min_api_version = 17 + } +) + +test.run_registered_tests() 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 4f12a94a55..74e6eaaa6a 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 @@ -75,28 +75,35 @@ end test.set_test_init_function(test_init) test.register_coroutine_test( - "Lifecycle - added test", + "Lifecycle - added test : parent device", function() - -- The initial switch event should be send during the device's first time onboarding + -- The initial switch event should be send during the device's first time onboarding. + -- This profile has no powerMeter/energyMeter, so no meter initialization event may be sent: + -- any extra capability event would fail the strict channel assertions below. test.socket.zigbee:__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", capabilities.button.numberOfButtons({ value = 2 }, - { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 2 }, + { visibility = { displayed = false } }))) 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.button.supportedButtonValues({ "pushed" }, - { visibility = { displayed = false } }))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) + 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", + capabilities.button.button.pushed({ state_change = false }))) -- Avoid sending the initial switch event after driver switch-over, as the switch-over event itself re-triggers the added lifecycle. test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.numberOfButtons({ value = 2 }, - { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 2 }, + { visibility = { displayed = false } }))) 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.button.supportedButtonValues({ "pushed" }, - { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed" }, + { visibility = { displayed = false } }))) end, { min_api_version = 14 @@ -104,23 +111,29 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "Lifecycle - added test", + "Lifecycle - added test : child device", function() - -- The initial switch event should be send during the device's first time onboarding + -- The initial switch event should be send during the device's first time onboarding. + -- The child profile has no powerMeter/energyMeter either, so no meter initialization event is sent. test.socket.zigbee:__set_channel_ordering("relaxed") test.socket.device_lifecycle:__queue_receive({ mock_child.id, "added" }) - test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.numberOfButtons({ value = 1 }, - { visibility = { displayed = false } }))) - test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed" }, - { visibility = { displayed = false } }))) - test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }, + { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed" }, + { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", + capabilities.button.button.pushed({ state_change = false }))) -- Avoid sending the initial switch event after driver switch-over, as the switch-over event itself re-triggers the added lifecycle. test.socket.zigbee:__set_channel_ordering("relaxed") test.socket.device_lifecycle:__queue_receive({ mock_child.id, "added" }) - test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.numberOfButtons({ value = 1 }, - { visibility = { displayed = false } }))) - test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed" }, - { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }, + { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed" }, + { visibility = { displayed = false } }))) end, { min_api_version = 14 @@ -128,26 +141,30 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "Lifecycle - added test", + "Lifecycle - added test : child device creation", function() -- The initial switch event should be send during the device's first time onboarding test.socket.zigbee:__set_channel_ordering("relaxed") test.socket.device_lifecycle:__queue_receive({ mock_base_device.id, "added" }) - mock_base_device:expect_device_create({ - type = "EDGE_CHILD", - label = "Aqara Smart Wall Switch H1 EU (No Neutral, Double Rocker) 2", - profile = "aqara-switch-child", - parent_device_id = mock_base_device.id, - parent_assigned_child_key = "02" - }) - test.socket.capability:__expect_send(mock_base_device:generate_test_message("main", capabilities.button.numberOfButtons({ value = 2 }, - { visibility = { displayed = false } }))) + mock_base_device:expect_device_create({ + type = "EDGE_CHILD", + label = "Aqara Smart Wall Switch H1 EU (No Neutral, Double Rocker) 2", + profile = "aqara-switch-child", + parent_device_id = mock_base_device.id, + parent_assigned_child_key = "02" + }) + test.socket.capability:__expect_send(mock_base_device:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 2 }, + { visibility = { displayed = false } }))) test.socket.zigbee:__expect_send({ mock_base_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_base_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, + cluster_base.write_manufacturer_specific_attribute(mock_base_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, + MFG_CODE, data_types.Uint8, 1) }) - test.socket.capability:__expect_send(mock_base_device:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed" }, - { visibility = { displayed = false } }))) - test.socket.capability:__expect_send(mock_base_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) + test.socket.capability:__expect_send(mock_base_device:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed" }, + { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_base_device:generate_test_message("main", + capabilities.button.button.pushed({ state_change = false }))) end, { min_api_version = 14 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 ffe37bb293..9c1f27b33c 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 @@ -69,29 +69,79 @@ end test.set_test_init_function(test_init) test.register_coroutine_test( - "Lifecycle - added test", + "Lifecycle - added test : parent device", function() test.socket.zigbee:__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", capabilities.button.numberOfButtons({ value = 3 }, { visibility = { displayed = false } }))) + 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.button.supportedButtonValues({ "pushed" }, + { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) + -- On the first onboarding there is no previous power/energy state, so both meters are initialized to 0 test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 0.0, unit = "W" }))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" }))) + end, + { + min_api_version = 14 + } +) + +test.register_coroutine_test( + "Lifecycle - added test : power/energy already reported should be restored instead of reset to 0", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + mock_device:set_field(LAST_REPORT_TIME, os.time() - 60 * 20) + test.socket.zigbee:__set_channel_ordering("relaxed") + + -- report power and energy first so the device has a last known meter state + test.socket.zigbee:__queue_receive({ + mock_device.id, + AnalogInput.attributes.PresentValue:build_test_attr_report(mock_device, + SinglePrecisionFloat(0, 9, 0.953125)):from_endpoint(POWER_METER_ENDPOINT) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.powerMeter.power({ value = 1000.0, unit = "W" }))) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + AnalogInput.attributes.PresentValue:build_test_attr_report(mock_device, + SinglePrecisionFloat(0, 9, 0.953125)):from_endpoint(ENERGY_METER_ENDPOINT) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ value = 1000000.0, unit = "Wh" }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 1000000.0 }))) + test.wait_for_events() + + -- re-adding the device must restore the reported values rather than blank them out + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.numberOfButtons({ value = 3 }, + { visibility = { displayed = false } }))) 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.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) - + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.powerMeter.power({ value = 1000.0, unit = "W" }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ value = 1000000.0, unit = "Wh" }))) end, { - min_api_version = 14 + min_api_version = 17 } ) test.register_coroutine_test( - "Lifecycle - added test", + "Lifecycle - added test : child device", function() test.socket.zigbee:__set_channel_ordering("relaxed") test.socket.device_lifecycle:__queue_receive({ mock_child.id, "added" }) @@ -100,6 +150,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) + -- the child profile has no powerMeter/energyMeter, so no meter initialization event is sent end, { min_api_version = 14 From 9744a0e5cec63a3377690b5bec753f66381319a4 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 18 Aug 2026 14:48:16 -0500 Subject: [PATCH 502/538] WWSTCERT-12431 Update smart plug label (#3173) Co-authored-by: Claude Sonnet 5 --- drivers/SmartThings/zwave-switch/fingerprints.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index c9e58b38f1..f606edf119 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -874,7 +874,7 @@ zwaveManufacturer: productId: 0x1000 deviceProfileName: metering-switch - id: 0312/FF00/FF0E - deviceLabel: Minoston Outlet + deviceLabel: Smart Plug manufacturerId: 0x0312 productType: 0xFF00 productId: 0xFF0E From 92fb3421bcfb70c58cb0224368e1edb75cb8c5e2 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Wed, 19 Aug 2026 12:07:02 -0500 Subject: [PATCH 503/538] WWSTCERT-11699 Z-Wave Scene Control Switch (#2977) * WWSTCERT-11699 Z-Wave Scene Control Switch * Fix newline issue in fingerprints.yml * Update fingerprint ID for Z-Wave Scene Control Switch --- drivers/SmartThings/zwave-button/fingerprints.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zwave-button/fingerprints.yml b/drivers/SmartThings/zwave-button/fingerprints.yml index 6a2995f5cc..df4a621220 100644 --- a/drivers/SmartThings/zwave-button/fingerprints.yml +++ b/drivers/SmartThings/zwave-button/fingerprints.yml @@ -78,4 +78,11 @@ zwaveManufacturer: manufacturerId: 0x0460 productType: 0x0009 productId: 0x0082 - deviceProfileName: multi-button-4-no-battery \ No newline at end of file + deviceProfileName: multi-button-4-no-battery + - id: "0312/0924/D001" + deviceLabel: Z-Wave Scene Control Switch + manufacturerId: 0x0312 + productId: 0xD001 + productType: 0x0924 + deviceProfileName: multi-button-4 + From 53c74eb115b90e5f207c5ee2cd9e35ae4b221b23 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Wed, 19 Aug 2026 12:10:14 -0500 Subject: [PATCH 504/538] WWSTCERT-13083 OSRAM IRIS (#3187) --- drivers/SmartThings/matter-switch/fingerprints.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 6a4321c1a5..29c85f6c79 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -2120,6 +2120,16 @@ matterManufacturer: vendorId: 0x1189 productId: 0x1001 deviceProfileName: light-color-level + - id: "4489/4573" + deviceLabel: OSRAM IRIS + vendorId: 0x1189 + productId: 0x11DD + deviceProfileName: light-color-level + - id: "4489/4572" + deviceLabel: OSRAM GISELA + vendorId: 0x1189 + productId: 0x11DC + deviceProfileName: light-color-level #Shelly - id: "5264/1" deviceLabel: Shelly Plug S MTR Gen3 From 4ea2b77c038312b168b79d4e811d584ed34c8c36 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Mon, 24 Aug 2026 13:57:05 -0500 Subject: [PATCH 505/538] Fix PTZ bugs with unsupported & unchanged fields (#3192) * Fix PTZ feature naming bug * Fix PTZ optionality for absolute commands as well * Add defensive check on `ib.data.elements` --- .../camera_handlers/attribute_handlers.lua | 13 +- .../camera_handlers/capability_handlers.lua | 30 ++-- .../camera_utils/device_configuration.lua | 8 +- .../src/test/test_matter_camera.lua | 129 +++++++++++++++++- 4 files changed, 150 insertions(+), 30 deletions(-) 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 index 53b35712e7..075f5cc487 100644 --- 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 @@ -292,6 +292,7 @@ function CameraAttributeHandlers.dptz_streams_handler(driver, device, ib, respon end function CameraAttributeHandlers.ptz_position_handler(driver, device, ib, response) + if not ib.data.elements then return end local ptz_map = camera_utils.get_ptz_map(device) local emit_event = function(idx, value) if value ~= ptz_map[idx].current then @@ -300,13 +301,13 @@ function CameraAttributeHandlers.ptz_position_handler(driver, device, ib, respon )) end end - if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MPAN) then + if ib.data.elements.pan and camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN) 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 + if ib.data.elements.tilt and camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_TILT) 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 + if ib.data.elements.zoom and camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_ZOOM) then emit_event(camera_fields.ZOOM_IDX, ib.data.elements.zoom.value) end end @@ -317,13 +318,13 @@ function CameraAttributeHandlers.ptz_presets_handler(driver, device, ib, respons 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 + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN) then pan = preset.settings.elements.pan.value end - if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MTILT) then + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_TILT) then tilt = preset.settings.elements.tilt.value end - if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MZOOM) then + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_ZOOM) 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 }) 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 index a26afd0ca7..738d6d3556 100644 --- 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 @@ -139,9 +139,11 @@ 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 + -- Only the axis being moved should be included; the other two must be omitted (nil) rather + -- than sent as 0, since each delta field's conformance is tied to its own feature bit. + local pan_delta = index == camera_fields.PAN_IDX and cmd.args.delta or nil + local tilt_delta = index == camera_fields.TILT_IDX and cmd.args.delta or nil + local zoom_delta = index == camera_fields.ZOOM_IDX and cmd.args.delta or nil device:send(clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove( device, endpoint_id, pan_delta, tilt_delta, zoom_delta )) @@ -151,23 +153,23 @@ end CameraCapabilityHandlers.ptz_set_position_factory = function(command) return function (driver, device, cmd) local ptz_map = camera_utils.get_ptz_map(device) + -- MPTZSetPosition coordinates are optional; + -- a single-axis command should only set (and clamp) the axis it targets. + local pan, tilt, zoom 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 + pan = utils.clamp_value(cmd.args.pan, ptz_map[camera_fields.PAN_IDX].range.minimum, ptz_map[camera_fields.PAN_IDX].range.maximum) + tilt = utils.clamp_value(cmd.args.tilt, ptz_map[camera_fields.TILT_IDX].range.minimum, ptz_map[camera_fields.TILT_IDX].range.maximum) + zoom = utils.clamp_value(cmd.args.zoom, ptz_map[camera_fields.ZOOM_IDX].range.minimum, ptz_map[camera_fields.ZOOM_IDX].range.maximum) elseif command == capabilities.mechanicalPanTiltZoom.commands.setPan then - ptz_map[camera_fields.PAN_IDX].current = cmd.args.pan + pan = utils.clamp_value(cmd.args.pan, ptz_map[camera_fields.PAN_IDX].range.minimum, ptz_map[camera_fields.PAN_IDX].range.maximum) elseif command == capabilities.mechanicalPanTiltZoom.commands.setTilt then - ptz_map[camera_fields.TILT_IDX].current = cmd.args.tilt + tilt = utils.clamp_value(cmd.args.tilt, ptz_map[camera_fields.TILT_IDX].range.minimum, ptz_map[camera_fields.TILT_IDX].range.maximum) 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) + zoom = utils.clamp_value(cmd.args.zoom, ptz_map[camera_fields.ZOOM_IDX].range.minimum, ptz_map[camera_fields.ZOOM_IDX].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 + device:send(clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZSetPosition( + device, endpoint_id, pan, tilt, zoom )) end end 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 index 0619194926..7cb8edd930 100644 --- 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 @@ -46,19 +46,19 @@ end local function build_ptz_supported_attributes(device) local supported_attributes = {} - if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MPAN) then + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN) 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 + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_TILT) 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 + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_ZOOM) 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 + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PRESETS) then table.insert(supported_attributes, "presets") table.insert(supported_attributes, "maxPresets") end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 3926b63fe9..020b7bb906 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -167,6 +167,38 @@ local mock_device_no_per_zone_sensitivity = test.mock_device.build_test_matter_d } }) +local mock_device_pan_tilt_only = 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.CameraAvSettingsUserLevelManagement.ID, + feature_map = clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN | + clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_TILT | + clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PRESETS, + cluster_type = "SERVER" + } + }, + device_types = { + {device_type_id = 0x0142, device_type_revision = 1} -- Camera + } + } + } +}) + local mock_device_no_user_defined_zone = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("camera.yml"), manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, @@ -297,6 +329,31 @@ local function test_init_no_user_defined_zone() mock_device_no_user_defined_zone:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end +-- mock_device_pan_tilt_only has no CameraAvStreamManagement cluster at all, so do_configure's +-- `if #device:get_endpoints(clusters.CameraAvStreamManagement.ID) == 0` branch calls match_profile +-- directly and synchronously during doConfigure, rather than waiting on an AttributeList report +-- like the other reduced fixtures above. That means the profile/capability metadata update and the +-- "PROVISIONED" transition both happen back-to-back off of the same doConfigure lifecycle event. +local subscribe_request_pan_tilt_only + +local function test_init_pan_tilt_only() + test.mock_device.add_test_device(mock_device_pan_tilt_only) + test.socket.device_lifecycle:__queue_receive({ mock_device_pan_tilt_only.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_pan_tilt_only.id, "init" }) + subscribe_request_pan_tilt_only = cluster_base.subscribe( + mock_device_pan_tilt_only, nil, camera_fields.CameraAVSULMFeatureMapAttr.cluster, camera_fields.CameraAVSULMFeatureMapAttr.ID + ) + test.socket.matter:__expect_send({mock_device_pan_tilt_only.id, subscribe_request_pan_tilt_only}) + test.socket.device_lifecycle:__queue_receive({ mock_device_pan_tilt_only.id, "doConfigure" }) + mock_device_pan_tilt_only:expect_metadata_update({ + optional_component_capabilities = { + { "main", { "mechanicalPanTiltZoom" } } + }, + profile = "camera" + }) + mock_device_pan_tilt_only:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + local additional_subscribed_attributes = { clusters.CameraAvStreamManagement.attributes.HDRModeEnabled, clusters.CameraAvStreamManagement.attributes.ImageRotation, @@ -616,6 +673,40 @@ local function update_device_profile_no_user_defined_zone() test.socket.matter:__expect_send({mock_device_no_user_defined_zone.id, subscribe_request_no_user_defined_zone}) end +local additional_subscribed_attributes_pan_tilt_only = { + 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, +} + +-- Unlike the other reduced fixtures, mock_device_pan_tilt_only's profile/capability update already +-- happened synchronously during doConfigure (see test_init_pan_tilt_only), so this only needs to +-- simulate the platform pushing the updated profile back down via infoChanged, then confirm the PTZ +-- capability is initialized with pan/tilt/presets only -- no zoom or zoomRange, since this device's +-- CameraAvSettingsUserLevelManagement feature_map never claimed MECHANICAL_ZOOM support. +local function update_device_profile_pan_tilt_only() + local updated_device_profile = t_utils.get_profile_definition( + "camera.yml", {enabled_optional_capabilities = { + { "main", { "mechanicalPanTiltZoom" } } + }} + ) + test.socket.device_lifecycle:__queue_receive(mock_device_pan_tilt_only:generate_info_changed({ profile = updated_device_profile })) + test.socket.capability:__expect_send( + mock_device_pan_tilt_only:generate_test_message("main", capabilities.mechanicalPanTiltZoom.supportedAttributes( + {"pan", "panRange", "tilt", "tiltRange", "presets", "maxPresets"} + )) + ) + for _, attr in ipairs(additional_subscribed_attributes_pan_tilt_only) do + subscribe_request_pan_tilt_only:merge(attr:subscribe(mock_device_pan_tilt_only)) + end + test.socket.matter:__expect_send({mock_device_pan_tilt_only.id, subscribe_request_pan_tilt_only}) +end + -- Matter Handler UTs test.register_coroutine_test( @@ -1358,6 +1449,32 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "PTZ Position report with zoom omitted should be handled normally on a device with no zoom feature", + function() + update_device_profile_pan_tilt_only() + test.wait_for_events() + -- This device has no zoom feature, so its MPTZPosition report never has a zoom field; + -- pan/tilt should still be handled normally. + test.socket.matter:__queue_receive({ + mock_device_pan_tilt_only.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition:build_test_report_data( + mock_device_pan_tilt_only, CAMERA_EP, {pan = 10, tilt = 20}) + }) + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send( + mock_device_pan_tilt_only:generate_test_message("main", capabilities.mechanicalPanTiltZoom.pan(10)) + ) + test.socket.capability:__expect_send( + mock_device_pan_tilt_only:generate_test_message("main", capabilities.mechanicalPanTiltZoom.tilt(20)) + ) + end, + { + min_api_version = 14, + test_init = test_init_pan_tilt_only + } +) + test.register_coroutine_test( "PTZ Presets reports should generate appropriate events", function() @@ -2006,21 +2123,21 @@ test.register_coroutine_test( { 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) + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, 10, nil, nil) }) 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) + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, nil, -35, nil) }) 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) + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, nil, nil, 80) }) end, { @@ -2061,7 +2178,7 @@ test.register_coroutine_test( { 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) + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZSetPosition(mock_device, CAMERA_EP, 50, nil, nil) }) test.socket.matter:__queue_receive({ mock_device.id, @@ -2077,7 +2194,7 @@ test.register_coroutine_test( { 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) + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZSetPosition(mock_device, CAMERA_EP, nil, -44, nil) }) test.socket.matter:__queue_receive({ mock_device.id, @@ -2093,7 +2210,7 @@ test.register_coroutine_test( { 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) + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZSetPosition(mock_device, CAMERA_EP, nil, nil, 5) }) test.socket.matter:__queue_receive({ mock_device.id, From 8f2511c494da96a929c7213a36fa2234eba41706 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed, 26 Aug 2026 09:04:44 -0500 Subject: [PATCH 506/538] Matter Sensor: Support MCD for FP400 and reprofiling (#3118) --- .../matter-sensor/profiles/aqara-fp400.yml | 56 +++++ .../sensor_handlers/attribute_handlers.lua | 4 +- .../sensor_utils/update_metadata_request.lua | 45 ++++ .../matter-sensor/src/sensor_utils/utils.lua | 44 ++++ .../device_configuration.lua | 44 ++-- .../air_quality_sensor_utils/utils.lua | 44 ---- .../sub_drivers/air_quality_sensor/init.lua | 3 +- .../src/sub_drivers/aqara_fp400/init.lua | 52 ++++- .../src/test/test_matter_aqara_fp400.lua | 192 ++++++++++++++++-- 9 files changed, 391 insertions(+), 93 deletions(-) create mode 100644 drivers/SmartThings/matter-sensor/src/sensor_utils/update_metadata_request.lua diff --git a/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml b/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml index c2d5b7b037..d4c9fde930 100644 --- a/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml +++ b/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml @@ -12,3 +12,59 @@ components: version: 1 categories: - name: PresenceSensor +- id: sensor1 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor +- id: sensor2 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor +- id: sensor3 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor +- id: sensor4 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor +- id: sensor5 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor +- id: sensor6 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor +- id: sensor7 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor +- id: sensor8 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor diff --git a/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua index 1867d65e0b..514bb622cf 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua @@ -185,9 +185,9 @@ end 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()) + device:emit_event_for_endpoint(ib.endpoint_id, 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")) + device:emit_event_for_endpoint(ib.endpoint_id, ib.data.value == 0x01 and capabilities.presenceSensor.presence("present") or capabilities.presenceSensor.presence("not present")) end end diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/update_metadata_request.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/update_metadata_request.lua new file mode 100644 index 0000000000..abeb04e548 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/update_metadata_request.lua @@ -0,0 +1,45 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local UpdateMetadataRequest = {} +UpdateMetadataRequest.__index = UpdateMetadataRequest + +function UpdateMetadataRequest.init() + local update_metadata_request = setmetatable({}, UpdateMetadataRequest) + update_metadata_request.profile = nil + update_metadata_request.enabled_components = {} + return update_metadata_request +end + +function UpdateMetadataRequest:add_capabilities_to_component(component_id, capability_ids) + if not self.enabled_components[component_id] then + self.enabled_components[component_id] = {} + end + for _, capability_id in ipairs(capability_ids) do + table.insert(self.enabled_components[component_id], capability_id) + end + return self +end + +function UpdateMetadataRequest:add_profile(profile) + self.profile = profile + return self +end + +function UpdateMetadataRequest:formatted_enabled_components() + local formatted_enabled_components = {} + for component_id, capability_ids in pairs(self.enabled_components) do + table.insert(formatted_enabled_components, { component_id, capability_ids }) + end + return formatted_enabled_components +end + +function UpdateMetadataRequest:format_request() + local formatted_request = { + profile = self.profile, + optional_component_capabilities = self:formatted_enabled_components() + } + return formatted_request +end + +return UpdateMetadataRequest diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua index 513c9f4602..75aa284aab 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua @@ -36,4 +36,48 @@ function utils.tbl_contains(array, value) return false end +--- Deeply compare two values. +--- Handles metatables. Can optionally ignore cycle checking and/or function differences. +--- +--- @param a any +--- @param b any +--- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } +--- @param seen table|nil +--- @return boolean +function utils.deep_equals(a, b, opts, seen) + if a == b then return true end -- same object + if type(a) ~= type(b) then return false end -- different type + if type(a) == "function" and opts and opts.ignore_functions then return true end + if type(a) ~= "table" then return false end -- same type but not table, thus was already compared + + -- check for cycles in table references and preserve reference topology. + if not (opts and opts.ignore_cycles) then + seen = seen or {} + seen[a] = seen[a] or {} + if seen[a][b] then + return seen[a][b] + end + seen[a][b] = true + end + + -- Compare keys/values from a + for k, v in pairs(a) do + if not utils.deep_equals(v, b[k], opts, seen) then + return false + end + end + + -- Ensure b doesn't have extra keys + for k in pairs(b) do + if a[k] == nil then + return false + end + end + + -- Compare metatables + local mt_a = getmetatable(a) + local mt_b = getmetatable(b) + return utils.deep_equals(mt_a, mt_b, opts, seen) +end + return utils 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 index a30eaed0f8..9a8215feff 100644 --- 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 @@ -30,53 +30,37 @@ function DeviceConfiguration.supported_level_measurements(device) return measurement_caps, level_caps end --- Match Modular Profile function DeviceConfiguration.match_profile(device) + local update_metadata_request = require "sensor_utils.update_metadata_request" + 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 + local updated_metadata = update_metadata_request.init() + local preference_tags = "" if #temp_eps > 0 then - table.insert(main_component_capabilities, capabilities.temperatureMeasurement.ID) + updated_metadata:add_capabilities_to_component("main", { capabilities.temperatureMeasurement.ID }) + preference_tags = preference_tags .. "-temp" end if #humidity_eps > 0 then - table.insert(main_component_capabilities, capabilities.relativeHumidityMeasurement.ID) + updated_metadata:add_capabilities_to_component("main", { capabilities.relativeHumidityMeasurement.ID }) + preference_tags = preference_tags .. "-humidity" end local measurement_caps, level_caps = DeviceConfiguration.supported_level_measurements(device) + updated_metadata:add_capabilities_to_component("main", measurement_caps) + updated_metadata:add_capabilities_to_component("main", level_caps) - 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:try_update_metadata(updated_metadata:add_profile("aqs-modular" .. preference_tags):format_request()) -- 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 + local MAIN_COMPONENT_IDX = 1 + local CAPABILITIES_LIST_IDX = 2 -- add mandatory capabilities for subscription - local total_supported_capabilities = optional_supported_component_capabilities + local total_supported_capabilities = updated_metadata:formatted_enabled_components() 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) 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 index 2e8d9e487b..a24149cce3 100644 --- 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 @@ -90,48 +90,4 @@ function AirQualitySensorUtils.convert_value_to_unit(value, from_unit, to_unit, return conversion_function(value, fields.molecular_weights[capability_name]) end ---- Deeply compare two values. ---- Handles metatables. Can optionally ignore cycle checking and/or function differences. ---- ---- @param a any ---- @param b any ---- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } ---- @param seen table|nil ---- @return boolean -function AirQualitySensorUtils.deep_equals(a, b, opts, seen) - if a == b then return true end -- same object - if type(a) ~= type(b) then return false end -- different type - if type(a) == "function" and opts and opts.ignore_functions then return true end - if type(a) ~= "table" then return false end -- same type but not table, thus was already compared - - -- check for cycles in table references and preserve reference topology. - if not (opts and opts.ignore_cycles) then - seen = seen or {} - seen[a] = seen[a] or {} - if seen[a][b] then - return seen[a][b] - end - seen[a][b] = true - end - - -- Compare keys/values from a - for k, v in pairs(a) do - if not AirQualitySensorUtils.deep_equals(v, b[k], opts, seen) then - return false - end - end - - -- Ensure b doesn't have extra keys - for k in pairs(b) do - if a[k] == nil then - return false - end - end - - -- Compare metatables - local mt_a = getmetatable(a) - local mt_b = getmetatable(b) - return AirQualitySensorUtils.deep_equals(mt_a, mt_b, opts, seen) -end - return AirQualitySensorUtils 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 index 383af643a6..1ce1cc2176 100644 --- 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 @@ -4,6 +4,7 @@ local version = require "version" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" +local sensor_utils = require "sensor_utils.utils" 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" @@ -67,7 +68,7 @@ function AirQualitySensorLifecycleHandlers.device_init(driver, device) end function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, args) - if not aqs_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then + if not sensor_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) 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) diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua index 5f47c86047..d9e9ad6378 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua @@ -1,21 +1,67 @@ -- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local capabilities = require "st.capabilities" +local sensor_utils = require "sensor_utils.utils" + +local COMPONENT_TO_ENDPOINT_MAP = "__COMPONENT_TO_ENDPOINT_MAP" + +local function endpoint_to_component(device, endpoint_id) + local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {} + for component_id, ep_id in pairs(map) do + if ep_id == endpoint_id then return component_id end + end + return "main" +end + +local function configure_profile(device) + local update_metadata_request = require "sensor_utils.update_metadata_request" + + local updated_component_to_endpoint_map = {} + local updated_metadata = update_metadata_request.init() + for _, ep in ipairs(device.endpoints) do + -- >2 since EP0 is the root node, EP1 is a permanent Presence Sensor, and EP2 is a permanent Light Sensor + if ep.endpoint_id > 2 then + local component_id = string.format("sensor%d", ep.endpoint_id - 2) -- ex. sensor1, sensor2, etc. for EP3, EP4, etc. + updated_component_to_endpoint_map[component_id] = ep.endpoint_id + updated_metadata:add_capabilities_to_component(component_id, { capabilities.presenceSensor.ID }) + end + end + device:set_field(COMPONENT_TO_ENDPOINT_MAP, updated_component_to_endpoint_map, { persist = true }) + device:try_update_metadata(updated_metadata:add_profile("aqara-fp400"):format_request()) +end + + local Fp400LifecycleHandlers = {} --- overwrite to avoid unnecessary metadata update calls -function Fp400LifecycleHandlers.do_configure() end +function Fp400LifecycleHandlers.do_configure(driver, device) + configure_profile(device) +end --- overwrite to avoid unnecessary metadata update calls function Fp400LifecycleHandlers.driver_switched(driver, device) + configure_profile(device) device:try_update_metadata({provisioning_state = "PROVISIONED"}) end +function Fp400LifecycleHandlers.info_changed(driver, device, event, args) + if not sensor_utils.deep_equals(args.old_st_store.endpoints, device.endpoints) then + configure_profile(device) + device:subscribe() + end +end + +function Fp400LifecycleHandlers.device_init(driver, device) + device:set_endpoint_to_component_fn(endpoint_to_component) + device:subscribe() +end + local aqara_fp400_handler = { NAME = "aqara-fp400", lifecycle_handlers = { doConfigure = Fp400LifecycleHandlers.do_configure, driverSwitched = Fp400LifecycleHandlers.driver_switched, + infoChanged = Fp400LifecycleHandlers.info_changed, + init = Fp400LifecycleHandlers.device_init, }, can_handle = require("sub_drivers.aqara_fp400.can_handle"), } diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua index ba4f2fb296..6ce8abf91d 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua @@ -2,9 +2,11 @@ -- Licensed under the Apache License, Version 2.0 local test = require "integration_test" +local st_utils = require "st.utils" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local t_utils = require "integration_test.utils" +local sensor_utils = require "sensor_utils.utils" local matter_endpoints = { { @@ -20,16 +22,49 @@ local matter_endpoints = { endpoint_id = 1, clusters = { {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- Occupancy Sensor + } + }, + { + endpoint_id = 2, + clusters = { {cluster_id = clusters.IlluminanceMeasurement.ID, cluster_type = "SERVER"}, }, + device_types = { + {device_type_id = 0x0106, device_type_revision = 1} -- Light Sensor + } + }, + { + endpoint_id = 3, + clusters = { + {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, device_types = { {device_type_id = 0x0107, device_type_revision = 1} -- Occupancy Sensor } - } + }, + { + endpoint_id = 5, + clusters = { + {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- Occupancy Sensor + } + }, +} + +local enabled_optional_component_capability_pairs = { + { "sensor1", { capabilities.presenceSensor.ID } }, + { "sensor3", { capabilities.presenceSensor.ID } } } local mock_device = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("aqara-fp400.yml"), + profile = t_utils.get_profile_definition("aqara-fp400.yml", + { enabled_optional_capabilities = enabled_optional_component_capability_pairs } + ), manufacturer_info = { vendor_id = 0x115F, product_id = 0x2009, @@ -37,31 +72,34 @@ local mock_device = test.mock_device.build_test_matter_device({ endpoints = matter_endpoints }) -local function subscribe_on_init(dev) +local function do_subscribe(dev) local subscribe_request = clusters.OccupancySensing.attributes.Occupancy:subscribe(dev) subscribe_request:merge(clusters.IlluminanceMeasurement.attributes.MeasuredValue:subscribe(dev)) return subscribe_request end local function test_init() - test.socket.matter:__set_channel_ordering("relaxed") - local subscribe_request = subscribe_on_init(mock_device) + local subscribe_request = do_subscribe(mock_device) 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) test.register_coroutine_test( - "Test no profile change on doConfigure for FP400", + "Test profile change on doConfigure for FP400", function() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) - -- The FP400 sub-driver overrides doConfigure to be a no-op -- When doConfigure completes successfully, the framework automatically provisions the device + mock_device:expect_metadata_update({ profile = "aqara-fp400", optional_component_capabilities = enabled_optional_component_capability_pairs }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) test.wait_for_events() + assert(sensor_utils.deep_equals(st_utils.deep_copy(mock_device:get_field("__COMPONENT_TO_ENDPOINT_MAP")), { + sensor1 = 3, + sensor3 = 5 + }), "Component to endpoint map should be set correctly on doConfigure") end, { - min_api_version = 14 + min_api_version = 21 } ) @@ -70,20 +108,21 @@ test.register_coroutine_test( function() local current_profile = mock_device.profile.id test.socket.device_lifecycle:__queue_receive({ mock_device.id, "driverSwitched" }) - -- The FP400 sub-driver overrides driverSwitched to only update provisioning state + mock_device:expect_metadata_update({ profile = "aqara-fp400", optional_component_capabilities = enabled_optional_component_capability_pairs }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -- Ensure profile has not changed test.wait_for_events() assert(mock_device.profile.id == current_profile, "Profile should not change on driverSwitched") end, { - min_api_version = 14 + min_api_version = 21 } ) test.register_message_test( "Occupancy reports should generate correct presence messages", { + -- from EP1 { channel = "matter", direction = "receive", @@ -109,10 +148,73 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_device:generate_test_message("main", capabilities.presenceSensor.presence("not present")) - } + }, + -- from EP3 + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 3, 1) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("sensor1", capabilities.presenceSensor.presence("present")) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 3, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("sensor1", capabilities.presenceSensor.presence("not present")) + }, + -- from EP5 + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 5, 1) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("sensor3", capabilities.presenceSensor.presence("present")) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 5, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("sensor3", capabilities.presenceSensor.presence("not present")) + }, }, { - min_api_version = 14 + test_init = function () + local subscribe_request = do_subscribe(mock_device) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.mock_device.add_test_device(mock_device) + -- we can assume this will be set elsewhere-- see other tests. + mock_device:set_field("__COMPONENT_TO_ENDPOINT_MAP", + { sensor1 = 3, sensor3 = 5 }, { persist = true } + ) + end, + min_api_version = 14 } ) @@ -124,7 +226,7 @@ test.register_message_test( direction = "receive", message = { mock_device.id, - clusters.IlluminanceMeasurement.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 21370) + clusters.IlluminanceMeasurement.attributes.MeasuredValue:build_test_report_data(mock_device, 2, 21370) } }, { @@ -138,4 +240,68 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Test profile change on endpoints increment in infoChanged for FP400", + function() + local current_profile_id = mock_device.profile.id + local incremented_matter_endpoints = matter_endpoints + table.insert(incremented_matter_endpoints, { + endpoint_id = 4, + clusters = { + {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- Occupancy Sensor + } + }) + local incremented_enabled_optional_component_capability_pairs = { + { "sensor1", { capabilities.presenceSensor.ID } }, + { "sensor3", { capabilities.presenceSensor.ID } }, + { "sensor2", { capabilities.presenceSensor.ID } }, + } + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ endpoints = incremented_matter_endpoints })) + mock_device:expect_metadata_update({ profile = "aqara-fp400", optional_component_capabilities = incremented_enabled_optional_component_capability_pairs }) + local subscribe_request = do_subscribe(mock_device) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + -- Ensure profile has not changed + test.wait_for_events() + assert(mock_device.profile.id == current_profile_id, "Profile should not change on infoChanged") + assert(sensor_utils.deep_equals(st_utils.deep_copy(mock_device:get_field("__COMPONENT_TO_ENDPOINT_MAP")), { + sensor1 = 3, + sensor3 = 5, + sensor2 = 4 + }), "Component to endpoint map should be set correctly on doConfigure") + end, + { + min_api_version = 21 + } +) + +test.register_coroutine_test( + "Test profile change on endpoints decrement in infoChanged for FP400", + function() + local current_profile_id = mock_device.profile.id + local decremented_matter_endpoints = matter_endpoints + table.remove(decremented_matter_endpoints, 4) -- remove EP3 + local decremented_enabled_optional_component_capability_pairs = { + { "sensor3", { capabilities.presenceSensor.ID } }, + { "sensor2", { capabilities.presenceSensor.ID } }, + } + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ endpoints = decremented_matter_endpoints })) + mock_device:expect_metadata_update({ profile = "aqara-fp400", optional_component_capabilities = decremented_enabled_optional_component_capability_pairs }) + local subscribe_request = do_subscribe(mock_device) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + -- Ensure profile has not changed + test.wait_for_events() + assert(mock_device.profile.id == current_profile_id, "Profile should not change on infoChanged") + assert(sensor_utils.deep_equals(st_utils.deep_copy(mock_device:get_field("__COMPONENT_TO_ENDPOINT_MAP")), { + sensor3 = 5, + sensor2 = 4 + }), "Component to endpoint map should be set correctly on doConfigure") + end, + { + min_api_version = 21 + } +) + test.run_registered_tests() From 3cc5dbfc973af9eba832430df6cd2a70e6f441f0 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 27 Aug 2026 11:40:36 -0500 Subject: [PATCH 507/538] WWSTCERT-13182 Govee Strip Light 2 (#3196) * WWSTCERT-13182 Govee Strip Light 2 * Update fingerprints.yml * Update fingerprints.yml --- .../matter-switch/fingerprints.yml | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 29c85f6c79..1a56ecce38 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -809,7 +809,7 @@ matterManufacturer: productId: 0x6870 deviceProfileName: light-color-level - id: "4999/26272" - deviceLabel: Govee TV Backlight 3 Pro 55-65 + deviceLabel: Govee TV Backlight 3 PRO / 3S PRO vendorId: 0x1387 productId: 0x66A0 deviceProfileName: light-color-level @@ -898,6 +898,26 @@ matterManufacturer: vendorId: 0x1387 productId: 0x60B3 deviceProfileName: light-color-level + - id: "4999/6723" + deviceLabel: Govee Strip Light 2 + vendorId: 0x1387 + productId: 0x1A43 + deviceProfileName: light-color-level + - id: "4999/6001" + deviceLabel: Govee Wallwash Table Lamp + vendorId: 0x1387 + productId: 0x1771 + deviceProfileName: light-color-level + - id: "4999/6161" + deviceLabel: Govee Bubble Projector Light + vendorId: 0x1387 + productId: 0x1811 + deviceProfileName: light-color-level + - id: "4999/10816" + deviceLabel: Govee TV Backlight 3 + vendorId: 0x1387 + productId: 0x2A40 + deviceProfileName: light-color-level # Hager - id: "4741/8" deviceLabel: Hager matter 2 buttons (battery) From dd29cc88b5075b1f8f0984912087b2a24eff9f14 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 27 Aug 2026 11:48:28 -0500 Subject: [PATCH 508/538] WWSTCERT-11168 Smart Radiator Thermostat X (#2949) --- .../matter-thermostat/fingerprints.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/SmartThings/matter-thermostat/fingerprints.yml b/drivers/SmartThings/matter-thermostat/fingerprints.yml index 814261790e..399f3861fc 100644 --- a/drivers/SmartThings/matter-thermostat/fingerprints.yml +++ b/drivers/SmartThings/matter-thermostat/fingerprints.yml @@ -89,6 +89,21 @@ matterManufacturer: vendorId: 0x134E productId: 0x0002 deviceProfileName: thermostat-humidity-heating-only-nostate-nobattery + - id: "4942/7" + deviceLabel: Smart Thermostat X (2nd Gen) + vendorId: 0x134E + productId: 0x0007 + deviceProfileName: thermostat-humidity-heating-only-nostate-batteryLevel + - id: "4942/8" + deviceLabel: Wireless Temperature Sensor X (2nd Gen) + vendorId: 0x134E + productId: 0x0008 + deviceProfileName: thermostat-humidity-heating-only-nostate-batteryLevel + - id: "4942/9" + deviceLabel: Smart Radiator Thermostat X + vendorId: 0x134E + productId: 0x0009 + deviceProfileName: thermostat-humidity-heating-only-nostate-batteryLevel #Taruie - id: "5151/4101" deviceLabel: TARUIE AC Remote From 72b71ab860db9b1e2e7018321e998763d5c1e26f Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 27 Aug 2026 13:38:44 -0500 Subject: [PATCH 509/538] WWSTCERT-13051 Govee Monitor Light Bar (#3178) --- .../matter-switch/fingerprints.yml | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 1a56ecce38..3609cb7910 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -898,6 +898,35 @@ matterManufacturer: vendorId: 0x1387 productId: 0x60B3 deviceProfileName: light-color-level + - id: "4999/10240" + deviceLabel: Govee Monitor Light Bar + vendorId: 0x1387 + productId: 0x2800 + deviceProfileName: light-color-level + - id: "4999/6833" + deviceLabel: Govee COB Strip Light 2 9.8ft/3m + vendorId: 0x1387 + productId: 0x1AB1 + deviceProfileName: light-color-level + - id: "4999/6834" + deviceLabel: Govee COB Strip Light 2 16.4ft/5m + vendorId: 0x1387 + productId: 0x1AB2 + deviceProfileName: light-color-level + - id: "4999/6835" + deviceLabel: Govee COB Strip Light 2 32.8ft/10m + vendorId: 0x1387 + productId: 0x1AB3 + deviceProfileName: light-color-level + - id: "4999/6818" + deviceLabel: Govee COB Strip Light 2 Pro 6.5ft/2m + vendorId: 0x1387 + productId: 0x1AA2 + deviceProfileName: light-color-level + - id: "4999/6821" + deviceLabel: Govee COB Strip Light 2 Pro 16.4ft/5m + vendorId: 0x1387 + productId: 0x1AA5 - id: "4999/6723" deviceLabel: Govee Strip Light 2 vendorId: 0x1387 From e0f54fbf28f9620ebd257580fb3eeeca8a9073e9 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 27 Aug 2026 14:01:29 -0500 Subject: [PATCH 510/538] WWSTCERT-13508 Onvis Outlet T20 (#3199) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 3609cb7910..b840abbd57 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1688,6 +1688,11 @@ matterManufacturer: vendorId: 0x143D productId: 0x1001 deviceProfileName: plug-binary + - id: "5181/4098" + deviceLabel: Onvis Outlet T20 + vendorId: 0x143D + productId: 0x1002 + deviceProfileName: plug-power-energy-powerConsumption #Osram - id: "4489/2564" deviceLabel: OSRAM MATTER PLUG UK From 6b8a9cf69462d314a7c81b7a709037744d7d788d Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Fri, 28 Aug 2026 11:30:22 -0500 Subject: [PATCH 511/538] Fix profile name in fingerprint for Govee COB Strip Light (#3200) --- drivers/SmartThings/matter-switch/fingerprints.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index b840abbd57..dbfe3156b7 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -927,6 +927,7 @@ matterManufacturer: deviceLabel: Govee COB Strip Light 2 Pro 16.4ft/5m vendorId: 0x1387 productId: 0x1AA5 + deviceProfileName: light-color-level - id: "4999/6723" deviceLabel: Govee Strip Light 2 vendorId: 0x1387 From ab673162b891f6a54c06dd5c35dc771db6976470 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Wed, 22 Jul 2026 14:28:22 -0500 Subject: [PATCH 512/538] GHA: Validate Fingerprints --- .github/scripts/validate_fingerprints.py | 361 ++++++++++++++++++ .github/workflows/validate-fingerprints.yml | 24 ++ drivers/SmartThings/hub/fingerprints.yml | 2 +- .../matter-switch/fingerprints.yml | 5 - .../zigbee-button/fingerprints.yml | 2 +- .../zigbee-smoke-detector/fingerprints.yml | 2 +- .../zigbee-switch/fingerprints.yml | 12 +- .../zigbee-water-leak-sensor/fingerprints.yml | 2 +- .../zwave-garage-door-opener/fingerprints.yml | 2 +- .../SmartThings/zwave-switch/fingerprints.yml | 2 +- .../fingerprints.yml | 1 - .../zwave-window-treatment/fingerprints.yml | 2 +- 12 files changed, 393 insertions(+), 24 deletions(-) create mode 100644 .github/scripts/validate_fingerprints.py create mode 100644 .github/workflows/validate-fingerprints.yml diff --git a/.github/scripts/validate_fingerprints.py b/.github/scripts/validate_fingerprints.py new file mode 100644 index 0000000000..4b7dbc12b2 --- /dev/null +++ b/.github/scripts/validate_fingerprints.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python3 +""" +Validate fingerprints.yml files changed in a PR. + +Required fields per manufacturer section: + matterManufacturer → id, deviceLabel, vendorId, productId, deviceProfileName + zigbeeManufacturer → id, deviceLabel, manufacturer, model, deviceProfileName + zwaveManufacturer → id, deviceLabel, manufacturerId, deviceProfileName + + at least one of: productId, productType + +Hex fields (vendorId, productId, productType, manufacturerId) must use 0xNNNN notation. +String fields must not be empty or have leading/trailing whitespace. +id values that contain YAML-special characters must be quoted. + +Indentation rules (spaces only, no tabs): + Section key: col 0 e.g. "matterManufacturer:" + Entry opening (- id: ...): 2-space e.g. " - id: ..." + All other entry fields: 4-space e.g. " vendorId: 0x115F" + +No trailing whitespace on any line. +No duplicate id values within a file. + +Generic sections (zigbeeGeneric, zwaveGeneric, matterGeneric, etc.) are skipped. + +Usage: + python3 tools/validate_fingerprints.py # auto-detect via git diff + python3 tools/validate_fingerprints.py path/fingerprints.yml ... +""" + +import os +import re +import sys +import subprocess +from pathlib import Path + +try: + import yaml +except ImportError: + print("Error: pyyaml is required. pip install pyyaml", file=sys.stderr) + sys.exit(2) + +# ── Section configuration ───────────────────────────────────────────────────── + +MANUFACTURER_SECTIONS = {'matterManufacturer', 'zigbeeManufacturer', 'zwaveManufacturer'} + +# All fields that must be present in every entry for each section. +# Z-Wave also needs productId OR productType (checked separately). +REQUIRED_FIELDS = { + 'matterManufacturer': ['id', 'deviceLabel', 'vendorId', 'productId', 'deviceProfileName'], + # manufacturer and model are checked together below: at least one must be present + 'zigbeeManufacturer': ['id', 'deviceLabel', 'deviceProfileName'], + 'zwaveManufacturer': ['id', 'deviceLabel', 'manufacturerId', 'deviceProfileName'], +} + +# These fields must be formatted as hex literals (0xNNNN). +HEX_FIELDS = {'vendorId', 'productId', 'productType', 'manufacturerId'} + +# YAML characters that force quoting when present in an unquoted scalar. +_YAML_SPECIAL_RE = re.compile(r'[:{}\[\],&#*?|<>=!%@`]') + +# Matches a valid hex literal. +_HEX_RE = re.compile(r'^0x[0-9A-Fa-f]+$') + +# Matches a field line: (indent)(key): (value) +_FIELD_RE = re.compile(r'^( *)([\w]+): *(.*?) *$') + + +# ── Raw-text analysis ───────────────────────────────────────────────────────── + +def analyse_lines(lines, filepath): + """ + Single-pass raw-line analysis. Returns a list of errors and a dict: + section_entry_lines[section] = list of (lineno, field, raw_value) + for structured cross-checking later. + """ + errors = [] + section = None # current top-level key + entry_indent = None # indent of the " - id:" line for current entry + in_manufacturer_section = False + + for lineno, raw in enumerate(lines, 1): + line = raw.rstrip('\n') + + # ── No tabs ─────────────────────────────────────────────────────────── + if '\t' in line: + errors.append(f"{filepath}:{lineno}: tab character found (use spaces)") + + # ── Trailing whitespace ─────────────────────────────────────────────── + if line != line.rstrip(): + errors.append(f"{filepath}:{lineno}: trailing whitespace") + + stripped = line.strip() + if not stripped or stripped.startswith('#'): + continue + + indent = len(line) - len(line.lstrip()) + + # ── Top-level section key detection ─────────────────────────────────── + if indent == 0 and line.endswith(':') and not line.startswith(' '): + section = line[:-1].strip() + in_manufacturer_section = section in MANUFACTURER_SECTIONS + entry_indent = None + continue + + if not in_manufacturer_section: + continue + + # ── Entry opening line: " - id: ..." ─────────────────────────────── + if stripped.startswith('- '): + if indent != 2: + errors.append( + f"{filepath}:{lineno}: [{section}] entry list item must be indented " + f"2 spaces, found {indent}" + ) + entry_indent = indent + + # Check that this is the id field + rest = stripped[2:] # strip "- " + m = _FIELD_RE.match(' ' + rest) # re-prefix spaces for consistent match + if m: + field = m.group(2) + raw_val = m.group(3) + if field == 'id': + _check_id_quoting(raw_val, lineno, section, filepath, errors) + continue + + # ── Subsequent fields of an entry ───────────────────────────────────── + if entry_indent is not None: + expected_indent = entry_indent + 2 # 2 + 2 = 4 + if indent != expected_indent: + errors.append( + f"{filepath}:{lineno}: [{section}] field must be indented " + f"{expected_indent} spaces, found {indent}" + ) + + m = _FIELD_RE.match(line) + if not m: + continue + field = m.group(2) + raw_val = m.group(3).split('#')[0].strip() # strip inline comment + + # ── Hex field format ────────────────────────────────────────────── + if field in HEX_FIELDS and raw_val: + if not _HEX_RE.match(raw_val): + errors.append( + f"{filepath}:{lineno}: [{section}] field '{field}' " + f"value {raw_val!r} must be hex notation (e.g. 0x115F)" + ) + + # ── String field whitespace ─────────────────────────────────────── + display_val = _strip_quotes(raw_val) + if field not in HEX_FIELDS and raw_val: + if display_val != display_val.strip(): + errors.append( + f"{filepath}:{lineno}: [{section}] field '{field}' " + f"value has leading/trailing whitespace: {raw_val!r}" + ) + + return errors + + +def _strip_yaml_inline_comment(raw_val): + """ + Strip an inline YAML comment from a raw scalar value. + + Handles: + "quoted value" # comment → "quoted value" + 'quoted value' # comment → 'quoted value' + bare value # comment → bare value + """ + if raw_val and raw_val[0] in ('"', "'"): + q = raw_val[0] + i = 1 + while i < len(raw_val): + ch = raw_val[i] + if q == '"' and ch == '\\': + i += 2 # skip escaped character + continue + if q == "'" and ch == "'" and i + 1 < len(raw_val) and raw_val[i + 1] == "'": + i += 2 # escaped single-quote inside single-quoted string + continue + if ch == q: + return raw_val[:i + 1] # return up to and including closing quote + i += 1 + return raw_val # unclosed quote — return as-is + # Unquoted: strip from ' #' (space + hash = inline comment marker) + idx = raw_val.find(' #') + if idx != -1: + return raw_val[:idx].rstrip() + return raw_val + + +def _check_id_quoting(raw_val, lineno, section, filepath, errors): + """Require quoting when the id value contains YAML-special characters.""" + raw_val = _strip_yaml_inline_comment(raw_val) + is_quoted = ( + len(raw_val) >= 2 + and raw_val[0] in ('"', "'") + and raw_val[-1] == raw_val[0] + ) + inner = raw_val[1:-1] if is_quoted else raw_val + if not is_quoted and _YAML_SPECIAL_RE.search(inner): + errors.append( + f"{filepath}:{lineno}: [{section}] id value {raw_val!r} contains " + f"special characters and must be quoted" + ) + + +def _strip_quotes(val): + if len(val) >= 2 and val[0] in ('"', "'") and val[-1] == val[0]: + return val[1:-1] + return val + + +# ── YAML structural checks ──────────────────────────────────────────────────── + +def check_structure(data, filepath): + errors = [] + + for section, entries in data.items(): + if section not in MANUFACTURER_SECTIONS: + continue + + if not isinstance(entries, list): + errors.append(f"{filepath}: [{section}] expected a list of entries, got {type(entries).__name__}") + continue + + seen_ids = {} + for entry in entries: + if not isinstance(entry, dict): + errors.append(f"{filepath}: [{section}] entry is not a mapping: {entry!r}") + continue + + entry_id = entry.get('id', '') + + # ── Duplicate id ────────────────────────────────────────────────── + if entry_id in seen_ids: + errors.append(f"{filepath}: [{section}] duplicate id {entry_id!r}") + seen_ids[entry_id] = True + + # ── Required fields ─────────────────────────────────────────────── + # manufacturer and model may legitimately be "" (device reports no value). + ALLOW_EMPTY = {'manufacturer', 'model'} + for field in REQUIRED_FIELDS[section]: + if field not in entry: + errors.append( + f"{filepath}: [{section}] id={entry_id!r}: " + f"missing required field '{field}'" + ) + elif entry[field] is None: + errors.append( + f"{filepath}: [{section}] id={entry_id!r}: " + f"field '{field}' has a null value" + ) + elif field not in ALLOW_EMPTY and isinstance(entry[field], str) and entry[field].strip() == '': + errors.append( + f"{filepath}: [{section}] id={entry_id!r}: " + f"field '{field}' is empty" + ) + + # ── Zigbee: manufacturer or model ───────────────────────────────── + if section == 'zigbeeManufacturer': + mfr = entry.get('manufacturer') + mdl = entry.get('model') + mfr_blank = mfr is None or (isinstance(mfr, str) and mfr.strip() == '') + mdl_blank = mdl is None or (isinstance(mdl, str) and mdl.strip() == '') + if mfr_blank and mdl_blank: + errors.append( + f"{filepath}: [{section}] id={entry_id!r}: " + "at least one of 'manufacturer' or 'model' must be provided" + ) + + # ── Z-Wave: productId or productType ────────────────────────────── + if section == 'zwaveManufacturer': + if 'productId' not in entry and 'productType' not in entry: + errors.append( + f"{filepath}: [{section}] id={entry_id!r}: " + "missing 'productId' or 'productType' (at least one required)" + ) + + return errors + + +# ── File validator ──────────────────────────────────────────────────────────── + +def validate_file(filepath: Path) -> list: + try: + raw = filepath.read_text(encoding='utf-8') + except OSError as exc: + return [f"{filepath}: cannot read file — {exc}"] + + lines = raw.splitlines(keepends=True) + + # Raw-text pass (indentation, whitespace, hex format, id quoting) + errors = analyse_lines(lines, str(filepath)) + + # YAML structural pass (required fields, duplicates, null/empty values) + try: + data = yaml.safe_load(raw) + except yaml.YAMLError as exc: + return errors + [f"{filepath}: YAML parse error — {exc}"] + + if not isinstance(data, dict): + return errors + [f"{filepath}: unexpected top-level YAML structure"] + + errors.extend(check_structure(data, str(filepath))) + return errors + + +# ── Git helper ──────────────────────────────────────────────────────────────── + +def get_changed_files() -> list: + base = os.environ.get('GITHUB_BASE_REF', 'main') + for ref in (f'origin/{base}', base, 'HEAD~1'): + try: + result = subprocess.run( + ['git', 'diff', '--name-only', '--diff-filter=AM', f'{ref}...HEAD'], + capture_output=True, text=True, check=True + ) + return [ + Path(f) for f in result.stdout.splitlines() + if f.endswith('fingerprints.yml') and Path(f).exists() + ] + except subprocess.CalledProcessError: + continue + return [] + + +# ── Main ────────────────────────────────────────────────────────────────────── + +def main(): + if len(sys.argv) > 1: + files = [Path(f) for f in sys.argv[1:]] + else: + files = get_changed_files() + if not files: + print("No changed fingerprints.yml files detected.") + sys.exit(0) + + all_errors = [] + for f in files: + if not f.exists(): + print(f"Warning: {f} does not exist, skipping", file=sys.stderr) + continue + print(f"Checking {f} ...") + errs = validate_file(f) + all_errors.extend(errs) + + if all_errors: + print() + for err in all_errors: + print(err) + print(f"\n✗ {len(all_errors)} error(s) found.") + sys.exit(1) + else: + print(f"\n✓ {len(files)} file(s) passed validation.") + sys.exit(0) + + +if __name__ == '__main__': + main() diff --git a/.github/workflows/validate-fingerprints.yml b/.github/workflows/validate-fingerprints.yml new file mode 100644 index 0000000000..6fc32268bd --- /dev/null +++ b/.github/workflows/validate-fingerprints.yml @@ -0,0 +1,24 @@ +name: Validate fingerprints + +on: + pull_request: + types: [opened, synchronize] + paths: + - 'drivers/**/fingerprints.yml' + +jobs: + validate-fingerprints: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install pyyaml + run: pip install pyyaml --quiet + + - name: Validate changed fingerprints.yml files + env: + GITHUB_BASE_REF: ${{ github.base_ref }} + run: python .github/scripts/validate_fingerprints.py diff --git a/drivers/SmartThings/hub/fingerprints.yml b/drivers/SmartThings/hub/fingerprints.yml index 317df13f8b..887f1a4455 100644 --- a/drivers/SmartThings/hub/fingerprints.yml +++ b/drivers/SmartThings/hub/fingerprints.yml @@ -17,7 +17,7 @@ hub: deviceProfileName: washer-dryer-hub - id: "cooktop-hub" deviceLabel: SmartThings Hub - hardwareType: SAMSUNG_COOKTOP_TIZEN_OPEN + hardwareType: SAMSUNG_COOKTOP_TIZEN_OPEN deviceProfileName: cooktop-hub - id: "v4-hub" deviceLabel: SmartThings Hub diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index dbfe3156b7..4590ba0a74 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -2380,11 +2380,6 @@ matterManufacturer: vendorId: 0x100b productId: 0x21B3 deviceProfileName: light-color-level-2200K-6500K - - id: "4107/8627" - deviceLabel: WiZ Downlight - vendorId: 0x100b - productId: 0x21B3 - deviceProfileName: light-color-level-2200K-6500K - id: "4107/8796" deviceLabel: WiZ Downlight vendorId: 0x100b diff --git a/drivers/SmartThings/zigbee-button/fingerprints.yml b/drivers/SmartThings/zigbee-button/fingerprints.yml index 347fa818df..119b8a18ee 100644 --- a/drivers/SmartThings/zigbee-button/fingerprints.yml +++ b/drivers/SmartThings/zigbee-button/fingerprints.yml @@ -248,7 +248,7 @@ zigbeeManufacturer: deviceLabel: ITM Switch manufacturer: Samsung Electronics model: SAMSUNG-ITM-Z-005 - deviceProfileName: SLED-three-buttons + deviceProfileName: SLED-three-buttons - id: "Linxura Smart Controller" deviceLabel: Linxura Smart Controller manufacturer: Linxura diff --git a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml index 6fce8d0d3a..837463ab08 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml +++ b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml @@ -5,7 +5,7 @@ zigbeeManufacturer: model: lumi.sensor_gas.acn02 deviceProfileName: gas-lifetime-selfcheck-aqara - id: "LUMI/lumi.sensor_smoke.acn03" - deviceLabel: Aqara Smart Smoke Detector + deviceLabel: Aqara Smart Smoke Detector manufacturer: LUMI model: lumi.sensor_smoke.acn03 deviceProfileName: smoke-battery-aqara diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index bebe5295a2..bb2ce5f027 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -1744,7 +1744,7 @@ zigbeeManufacturer: deviceLabel: SMART ZIGBEE PLUG EU EM T manufacturer: LEDVANCE model: PLUG EU EM T - deviceProfileName: switch-power-energy + deviceProfileName: switch-power-energy - id: "OSRAM/LIGHTIFY Edge-lit flushmount" deviceLabel: SYLVANIA Light manufacturer: OSRAM @@ -2115,11 +2115,6 @@ zigbeeManufacturer: manufacturer: OSRAM model: CLA60 RGBW OSRAM deviceProfileName: rgbw-bulb - - id: "OSRAM/Flex RGBW" - deviceLabel: OSRAM Light - manufacturer: OSRAM - model: Flex RGBW - deviceProfileName: rgbw-bulb - id: "OSRAM/Gardenpole RGBW-Lightify" deviceLabel: OSRAM Light manufacturer: OSRAM @@ -2230,11 +2225,6 @@ zigbeeManufacturer: manufacturer: IKEA of Sweden model: JORMLIEN door WS 40x80 deviceProfileName: color-temp-bulb-2200K-4000K - - id: "OSRAM/Classic B40 TW - LIGHTIFY" - deviceLabel: OSRAM Light - manufacturer: OSRAM - model: Classic B40 TW - LIGHTIFY - deviceProfileName: color-temp-bulb - id: "OSRAM/CLA60 TW OSRAM" deviceLabel: OSRAM Light manufacturer: OSRAM diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/fingerprints.yml b/drivers/SmartThings/zigbee-water-leak-sensor/fingerprints.yml index e7007de4f7..9745b5c652 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/fingerprints.yml +++ b/drivers/SmartThings/zigbee-water-leak-sensor/fingerprints.yml @@ -108,7 +108,7 @@ zigbeeManufacturer: deviceLabel: Sengled Water Leak Sensor manufacturer: sengled model: E1L-G7K - deviceProfileName: water-battery + deviceProfileName: water-battery - id: NEO/NAS_WS11 deviceLabel: NEO Water Leak Sensor manufacturer: NEO diff --git a/drivers/SmartThings/zwave-garage-door-opener/fingerprints.yml b/drivers/SmartThings/zwave-garage-door-opener/fingerprints.yml index e15aee1a63..006334ea60 100644 --- a/drivers/SmartThings/zwave-garage-door-opener/fingerprints.yml +++ b/drivers/SmartThings/zwave-garage-door-opener/fingerprints.yml @@ -41,4 +41,4 @@ zwaveGeneric: commandClasses: supported: - 0x98 - deviceProfileName: base-garage-door + deviceProfileName: base-garage-door diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index f606edf119..8084a7bbf0 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -965,7 +965,7 @@ zwaveManufacturer: manufacturerId: 0x0460 productId: 0x0083 productType: 0x0002 - deviceProfileName: switch-binary + deviceProfileName: switch-binary - id: 1120/2/132 deviceLabel: Wave 1PM manufacturerId: 0x0460 diff --git a/drivers/SmartThings/zwave-virtual-momentary-switch/fingerprints.yml b/drivers/SmartThings/zwave-virtual-momentary-switch/fingerprints.yml index b93e7f071a..b62288b90e 100644 --- a/drivers/SmartThings/zwave-virtual-momentary-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-virtual-momentary-switch/fingerprints.yml @@ -1,4 +1,3 @@ -zwaveManufacturer: zwaveGeneric: - id: "GenericSwitch/1" deviceLabel: Switch diff --git a/drivers/SmartThings/zwave-window-treatment/fingerprints.yml b/drivers/SmartThings/zwave-window-treatment/fingerprints.yml index 0eed587497..13c648f3e7 100644 --- a/drivers/SmartThings/zwave-window-treatment/fingerprints.yml +++ b/drivers/SmartThings/zwave-window-treatment/fingerprints.yml @@ -62,7 +62,7 @@ zwaveManufacturer: manufacturerId: 0x0115 productId: 0x0010 productType: 0x0211 - deviceProfileName: window-treatment-preset-reverse + deviceProfileName: window-treatment-preset-reverse zwaveGeneric: - id: window-treatment/generic/1 deviceLabel: Z-Wave Window Treatment From 8cf0b5025b6ee284f1be7ae98bc41f2156a482ab Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Mon, 31 Aug 2026 11:35:41 -0500 Subject: [PATCH 513/538] WWSTCERT-13411 Z-Wave Smart Plug (#3207) --- drivers/SmartThings/zwave-switch/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index 8084a7bbf0..77aba90d2c 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -885,6 +885,12 @@ zwaveManufacturer: productId: 0xFF97 productType: 0xFF01 deviceProfileName: switch-binary + - id: "0312/FF00/FF0C" + deviceLabel: Z-Wave Smart Plug + manufacturerId: 0x0312 + productId: 0xFF0C + productType: 0xFF00 + deviceProfileName: smartplug-binary - id: Evolve/RelaySwitch deviceLabel: Evolve Switch manufacturerId: 0x0113 From f46b6470978a6736ce634e01d054835a919d9284 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Mon, 31 Aug 2026 13:04:02 -0500 Subject: [PATCH 514/538] Fix hard privacy mode presence in `supportedAttributes` (#3201) * Fix hard privacy mode presence in `supportedAttributes` * Make hardPrivacyMode independant of soft one --- .../camera_handlers/attribute_handlers.lua | 7 +- .../camera_utils/device_configuration.lua | 51 +++++- .../camera/camera_utils/fields.lua | 1 + .../src/test/test_matter_camera.lua | 163 +++++++++++++++++- 4 files changed, 206 insertions(+), 16 deletions(-) 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 index 075f5cc487..075f9af600 100644 --- 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 @@ -18,8 +18,6 @@ CameraAttributeHandlers.enabled_state_factory = function(attribute) 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 @@ -450,7 +448,7 @@ 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 status_light_enabled_present, status_light_brightness_present, hard_privacy_mode_present = false, false, false local attribute_ids = {} for _, attr in ipairs(ib.data.elements) do if attr.value == clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID then @@ -459,6 +457,8 @@ function CameraAttributeHandlers.camera_av_stream_management_attribute_list_hand elseif attr.value == clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID then status_light_brightness_present = true table.insert(attribute_ids, clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) + elseif attr.value == clusters.CameraAvStreamManagement.attributes.HardPrivacyModeOn.ID then + hard_privacy_mode_present = true end end local component_map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {} @@ -469,6 +469,7 @@ function CameraAttributeHandlers.camera_av_stream_management_attribute_list_hand } device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist=true}) camera_cfg.update_status_light_attribute_presence(device, status_light_enabled_present, status_light_brightness_present) + camera_cfg.update_hard_privacy_mode_attribute_presence(device, hard_privacy_mode_present) camera_cfg.reconcile_profile_and_capabilities(device) end 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 index 7cb8edd930..282d33677f 100644 --- 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 @@ -33,6 +33,14 @@ local function set_status_light_presence(device, status_light_enabled_present, s device:set_field(camera_fields.STATUS_LIGHT_BRIGHTNESS_PRESENT, status_light_brightness_present == true, { persist = true }) end +local function get_hard_privacy_mode_presence(device) + return device:get_field(camera_fields.HARD_PRIVACY_MODE_PRESENT) +end + +local function set_hard_privacy_mode_presence(device, hard_privacy_mode_present) + device:set_field(camera_fields.HARD_PRIVACY_MODE_PRESENT, hard_privacy_mode_present == true, { persist = true }) +end + local function build_webrtc_supported_features() return { bundle = true, @@ -100,12 +108,32 @@ local function build_video_stream_settings_supported_features(device) return supported_features end -local function build_camera_privacy_supported_attributes() - return { "softRecordingPrivacyMode", "softLivestreamPrivacyMode" } +local function camera_privacy_feature_supported(device) + return camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.PRIVACY) end -local function build_camera_privacy_supported_commands() - return { "setSoftRecordingPrivacyMode", "setSoftLivestreamPrivacyMode" } +-- SoftRecordingPrivacyModeEnabled/SoftLivestreamPrivacyModeEnabled are conditionally mandatory on the PRIV +-- feature, while HardPrivacyModeOn has independent optional conformance. +-- A device can support either, both, or neither, so each is built independently. +local function build_camera_privacy_supported_attributes(device) + local supported_attributes = {} + if camera_privacy_feature_supported(device) then + table.insert(supported_attributes, "softRecordingPrivacyMode") + table.insert(supported_attributes, "softLivestreamPrivacyMode") + end + if get_hard_privacy_mode_presence(device) then + table.insert(supported_attributes, "hardPrivacyMode") + end + return supported_attributes +end + +local function build_camera_privacy_supported_commands(device) + local supported_commands = {} + if camera_privacy_feature_supported(device) then + table.insert(supported_commands, "setSoftRecordingPrivacyMode") + table.insert(supported_commands, "setSoftLivestreamPrivacyMode") + end + return supported_commands end local function capabilities_needing_reinit(device) @@ -155,8 +183,8 @@ local function capabilities_needing_reinit(device) capabilities_to_reinit.video_stream_settings = true end - if should_init(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedAttributes, build_camera_privacy_supported_attributes()) or - should_init(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedCommands, build_camera_privacy_supported_commands()) then + if should_init(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedAttributes, build_camera_privacy_supported_attributes(device)) or + should_init(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedCommands, build_camera_privacy_supported_commands(device)) then capabilities_to_reinit.camera_privacy_mode = true end @@ -194,6 +222,7 @@ end function CameraDeviceConfiguration.match_profile(device) local status_light_enabled_present, status_light_brightness_present = get_status_light_presence(device) + local hard_privacy_mode_present = get_hard_privacy_mode_presence(device) local profile_update_requested = false local optional_supported_component_capabilities = {} local main_component_capabilities = {} @@ -234,7 +263,7 @@ function CameraDeviceConfiguration.match_profile(device) 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 + if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.PRIVACY) or hard_privacy_mode_present then table.insert(main_component_capabilities, capabilities.cameraPrivacyMode.ID) end if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.SPEAKER) then @@ -361,8 +390,8 @@ end local function init_camera_privacy_mode(device) if device:supports_capability(capabilities.cameraPrivacyMode) then 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(build_camera_privacy_supported_attributes())) - device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedCommands(build_camera_privacy_supported_commands())) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedAttributes(build_camera_privacy_supported_attributes(device))) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedCommands(build_camera_privacy_supported_commands(device))) end end @@ -442,6 +471,10 @@ function CameraDeviceConfiguration.update_status_light_attribute_presence(device set_status_light_presence(device, status_light_enabled_present, status_light_brightness_present) end +function CameraDeviceConfiguration.update_hard_privacy_mode_attribute_presence(device, hard_privacy_mode_present) + set_hard_privacy_mode_presence(device, hard_privacy_mode_present) +end + function CameraDeviceConfiguration.reinitialize_changed_camera_capabilities_and_subscriptions(device, old_profile, new_profile) local changed_capabilities = changed_capabilities_from_profiles(old_profile, new_profile) initialize_selected_camera_capabilities(device, changed_capabilities) 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 index c88f177707..edb4deb125 100644 --- 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 @@ -16,6 +16,7 @@ CameraFields.TRIGGERED_ZONES = "__triggered_zones" CameraFields.DPTZ_VIEWPORTS = "__dptz_viewports" CameraFields.STATUS_LIGHT_ENABLED_PRESENT = "__status_light_enabled_present" CameraFields.STATUS_LIGHT_BRIGHTNESS_PRESENT = "__status_light_brightness_present" +CameraFields.HARD_PRIVACY_MODE_PRESENT = "__hard_privacy_mode_present" CameraFields.CameraAVSMFeatureMapAttr = { ID = 0xFFFC, cluster = clusters.CameraAvStreamManagement.ID } CameraFields.CameraAVSULMFeatureMapAttr = { ID = 0xFFFC, cluster = clusters.CameraAvSettingsUserLevelManagement.ID } diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 020b7bb906..52c5e7bb9c 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -800,6 +800,7 @@ test.register_coroutine_test( subscribe = function() subscribe_called = true end, supports_capability = function() return false end, get_endpoints = function() return { DOORBELL_EP } end, + get_field = function() return nil end, } local original_match_profile = camera_cfg.match_profile @@ -894,6 +895,9 @@ test.register_coroutine_test( get_endpoints = function() return { CAMERA_EP } end, + get_field = function() + return nil + end, emit_event_for_endpoint = function() init_event_count = init_event_count + 1 end @@ -909,6 +913,132 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Camera privacy mode should be added to profile when HardPrivacyModeOn is present even without the PRIV feature", + function() + local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" + + local updated_metadata = nil + local fake_device = { + profile = { components = {} }, + endpoints = { + { + endpoint_id = CAMERA_EP, + device_types = { + {device_type_id = 0x0142, device_type_revision = 1} -- Camera + }, + clusters = { + { + cluster_id = clusters.CameraAvStreamManagement.ID, + feature_map = clusters.CameraAvStreamManagement.types.Feature.VIDEO, -- no PRIVACY feature + cluster_type = "SERVER" + } + } + } + }, + get_field = function(_, field) + return field == camera_fields.HARD_PRIVACY_MODE_PRESENT + end, + get_endpoints = function() return {} end, + try_update_metadata = function(_, metadata) updated_metadata = metadata end, + } + + camera_cfg.match_profile(fake_device) + + assert(updated_metadata ~= nil, "profile update should be requested when HardPrivacyModeOn is present") + local main_capabilities + for _, component in ipairs(updated_metadata.optional_component_capabilities) do + if component[1] == "main" then + main_capabilities = component[2] + end + end + local found = false + for _, cap_id in ipairs(main_capabilities or {}) do + if cap_id == capabilities.cameraPrivacyMode.ID then + found = true + end + end + assert(found, "cameraPrivacyMode should be added to the profile based on HardPrivacyModeOn presence alone") + end, + { + min_api_version = 14 + } +) + +test.register_coroutine_test( + "Camera privacy mode supportedAttributes/supportedCommands should only expose hardPrivacyMode when PRIV feature is absent", + function() + local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" + + local emitted = {} + local fake_device = { + endpoints = { + { + endpoint_id = CAMERA_EP, + clusters = { + { + cluster_id = clusters.CameraAvStreamManagement.ID, + feature_map = clusters.CameraAvStreamManagement.types.Feature.VIDEO, -- no PRIVACY feature + cluster_type = "SERVER" + } + } + } + }, + supports_capability = function(_, capability) + return capability == capabilities.cameraPrivacyMode + end, + get_field = function(_, field) + return field == camera_fields.HARD_PRIVACY_MODE_PRESENT + end, + get_endpoints = function(self, cluster_id, opts) + opts = opts or {} + local eps = {} + for _, ep in ipairs(self.endpoints) do + for _, clus in ipairs(ep.clusters) do + if clus.cluster_id == cluster_id and + (opts.feature_bitmap == nil or (clus.feature_map & opts.feature_bitmap) == opts.feature_bitmap) then + table.insert(eps, ep.endpoint_id) + end + end + end + return eps + end, + emit_event_for_endpoint = function(_, _, capability_event) + table.insert(emitted, capability_event) + end, + } + + camera_cfg.initialize_camera_capabilities(fake_device) + + local function list_equals(a, b) + if #a ~= #b then return false end + for i, v in ipairs(a) do + if v ~= b[i] then return false end + end + return true + end + + local supported_attributes_event, supported_commands_event + for _, event in ipairs(emitted) do + if event.attribute == capabilities.cameraPrivacyMode.supportedAttributes then + supported_attributes_event = event + elseif event.attribute == capabilities.cameraPrivacyMode.supportedCommands then + supported_commands_event = event + end + end + + assert(supported_attributes_event ~= nil, "supportedAttributes should be emitted") + assert(list_equals(supported_attributes_event.value.value, {"hardPrivacyMode"}), + "supportedAttributes should contain only hardPrivacyMode when PRIV feature is absent") + assert(supported_commands_event ~= nil, "supportedCommands should be emitted") + assert(list_equals(supported_commands_event.value.value, {}), + "supportedCommands should be empty when PRIV feature is absent, since HardPrivacyModeOn is read-only") + end, + { + min_api_version = 14 + } +) + test.register_coroutine_test( "Reports mapping to EnabledState capability data type should generate appropriate events", function() @@ -940,10 +1070,6 @@ test.register_coroutine_test( 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, @@ -3432,6 +3558,35 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Camera privacy mode supportedAttributes should include hardPrivacyMode when reported in AttributeList", + 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), + uint32(clusters.CameraAvStreamManagement.attributes.HardPrivacyModeOn.ID) + }) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.cameraPrivacyMode.supportedAttributes( + {"softRecordingPrivacyMode", "softLivestreamPrivacyMode", "hardPrivacyMode"} + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.cameraPrivacyMode.supportedCommands( + {"setSoftRecordingPrivacyMode", "setSoftLivestreamPrivacyMode"} + )) + ) + end, + { + min_api_version = 14 + } +) + test.register_coroutine_test( "Camera profile should include zoneManagement when USER_DEFINED feature is present", function() From 7d5651edf219681918d2dc769ef346db23f3e3bb Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Tue, 8 Sep 2026 12:15:55 -0500 Subject: [PATCH 515/538] Matter Switch: restrict Eve sub driver to specific PIDS (#3213) --- .../src/sub_drivers/eve_energy/can_handle.lua | 7 ++++++- .../matter-switch/src/switch_utils/fields.lua | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) 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 index 02bc9e06d2..2bc395ae43 100644 --- 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 @@ -10,9 +10,14 @@ return function(opts, driver, device) -- this sub driver loads for devices that: -- 1. Contain the Eve Private Cluster (0x130AFC01) -- 2. Do NOT have the Standard Electrical Sensor device type + -- 3. Match one of the known Eve Energy vendor/product ID combinations + -- We should still check that the device does not have the Electrical Sensor + -- device type because this sub driver is only needed for cases where devices + -- may be on old FW and do not support the standard Electical Sensor related clusters. if device.network_type == device_lib.NETWORK_TYPE_MATTER and #device:get_endpoints(EVE_PRIVATE_CLUSTER_ID) > 0 and - #switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.ELECTRICAL_SENSOR) == 0 then + #switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.ELECTRICAL_SENSOR) == 0 and + switch_utils.get_product_override_field(device, "needs_eve_energy_subdriver") then return true, require("sub_drivers.eve_energy") end return false diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua index 5873cb40b2..d16214d2fd 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -126,6 +126,15 @@ SwitchFields.vendor_overrides = { [0x0007] = { needs_hager_subdriver = true }, -- Hager HBnet PIR 1.1M [0x000A] = { needs_hager_subdriver = true }, -- Hager HBnet PIR 2.2M }, + [0x130A] = { -- EVE_MANUFACTURER_ID + [0x0050] = { needs_eve_energy_subdriver = true }, -- Eve Energy EU + [0x0053] = { needs_eve_energy_subdriver = true }, -- Eve Energy US + [0x0054] = { needs_eve_energy_subdriver = true }, -- Eve Energy UK + [0x005E] = { needs_eve_energy_subdriver = true }, -- Eve Energy AU + [0x0069] = { needs_eve_energy_subdriver = true }, -- Eve Energy Outlet US + [0x006A] = { needs_eve_energy_subdriver = true }, -- Eve Energy CH + [0x006B] = { needs_eve_energy_subdriver = true }, -- Eve Energy Outlet EU + }, [0x1321] = { -- SONOFF_MANUFACTURER_ID [0x000C] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, [0x000D] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, From 71bbd3da8178b1c38332a6d9342ea23a3ce5b3dd Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Tue, 8 Sep 2026 16:19:40 -0500 Subject: [PATCH 516/538] Handle nil in PTZ command for axis clamping (#3217) --- .../camera_handlers/capability_handlers.lua | 12 +++++++++--- .../src/test/test_matter_camera.lua | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) 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 index 738d6d3556..ef52efcdbe 100644 --- 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 @@ -157,9 +157,15 @@ CameraCapabilityHandlers.ptz_set_position_factory = function(command) -- a single-axis command should only set (and clamp) the axis it targets. local pan, tilt, zoom if command == capabilities.mechanicalPanTiltZoom.commands.setPanTiltZoom then - pan = utils.clamp_value(cmd.args.pan, ptz_map[camera_fields.PAN_IDX].range.minimum, ptz_map[camera_fields.PAN_IDX].range.maximum) - tilt = utils.clamp_value(cmd.args.tilt, ptz_map[camera_fields.TILT_IDX].range.minimum, ptz_map[camera_fields.TILT_IDX].range.maximum) - zoom = utils.clamp_value(cmd.args.zoom, ptz_map[camera_fields.ZOOM_IDX].range.minimum, ptz_map[camera_fields.ZOOM_IDX].range.maximum) + if cmd.args.pan ~= nil then + pan = utils.clamp_value(cmd.args.pan, ptz_map[camera_fields.PAN_IDX].range.minimum, ptz_map[camera_fields.PAN_IDX].range.maximum) + end + if cmd.args.tilt ~= nil then + tilt = utils.clamp_value(cmd.args.tilt, ptz_map[camera_fields.TILT_IDX].range.minimum, ptz_map[camera_fields.TILT_IDX].range.maximum) + end + if cmd.args.zoom ~= nil then + zoom = utils.clamp_value(cmd.args.zoom, ptz_map[camera_fields.ZOOM_IDX].range.minimum, ptz_map[camera_fields.ZOOM_IDX].range.maximum) + end elseif command == capabilities.mechanicalPanTiltZoom.commands.setPan then pan = utils.clamp_value(cmd.args.pan, ptz_map[camera_fields.PAN_IDX].range.minimum, ptz_map[camera_fields.PAN_IDX].range.maximum) elseif command == capabilities.mechanicalPanTiltZoom.commands.setTilt then diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index 52c5e7bb9c..e1467edecf 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -2352,6 +2352,24 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Set PTZ command with a zoom-omitting setPanTiltZoom should not clamp the missing axis", + function() + update_device_profile() + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mechanicalPanTiltZoom", component = "main", command = "setPanTiltZoom", args = { 0, 0 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZSetPosition(mock_device, CAMERA_EP, 0, 0, nil) + }) + end, + { + min_api_version = 14 + } +) + test.register_coroutine_test( "Preset commands should send the appropriate commands", function() From 8d5cac5d8257a00ec0fd0d5568e2387b5b3299cd Mon Sep 17 00:00:00 2001 From: Zach Varberg Date: Tue, 8 Sep 2026 13:53:57 -0500 Subject: [PATCH 517/538] Adjust tests to handle trustworthy child_ids change --- .../src/test/test_aqara_light_switch_h2.lua | 11 +++------- .../src/test/test_hager_waasys.lua | 10 ++++++--- .../test_matter_multi_button_switch_mcd.lua | 22 +++++++++++-------- ..._aeotec_home_energy_meter_gen8_1_phase.lua | 3 ++- ..._aeotec_home_energy_meter_gen8_2_phase.lua | 3 ++- ..._aeotec_home_energy_meter_gen8_3_phase.lua | 3 ++- .../test/test_zwave_dual_switch_migration.lua | 12 ++++++---- 7 files changed, 37 insertions(+), 27 deletions(-) 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 08402cf273..a78a2375c0 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 @@ -4,8 +4,6 @@ local test = require "integration_test" local t_utils = require "integration_test.utils" local capabilities = require "st.capabilities" -local utils = require "st.utils" -local dkjson = require "dkjson" local clusters = require "st.matter.clusters" local version = require "version" @@ -205,19 +203,16 @@ local function test_init() parent_assigned_child_key = string.format("%d", aqara_child2_ep) }) - local device_info_copy = utils.deep_copy(aqara_mock_device.raw_st_data) - device_info_copy.profile.id = "4-button" - local device_info_json = dkjson.encode(device_info_copy) - test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "infoChanged", device_info_json }) - configure_buttons() - test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive(aqara_mock_device:generate_info_changed({profile = t_utils.get_profile_definition("4-button.yml")})) end + test.set_test_init_function(test_init) test.register_coroutine_test( "Button/Switch device : button/switch capability should send the appropriate commands", function() + test.wait_for_events() test.socket.matter:__queue_receive( { aqara_mock_device.id, diff --git a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua index a85137d0e3..4aeee85023 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua @@ -494,10 +494,13 @@ local function button_supported_values (matter_device) test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) end -local function initiate_info_changed(device, profile) +local function initiate_info_changed(device, profile, parent) test.socket.device_lifecycle:__queue_receive(device:generate_info_changed({ profile = { id = profile } })) test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") test.mock_time.advance_time(2) + if parent ~= nil then + test.socket.device_lifecycle:__queue_receive(parent:generate_info_changed({})) + end end local function configure_parent(device) @@ -1432,7 +1435,7 @@ test.register_coroutine_test("Test: PIR Device - Complete Functionality with Mot }) test.mock_device.add_test_device(child_dimmer) - initiate_info_changed(child_dimmer, "light-level") + initiate_info_changed(child_dimmer, "light-level", parent_pir) test.socket.matter:__expect_send({ parent_pir.id, cluster_base.subscribe(parent_pir, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) @@ -1558,7 +1561,7 @@ test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile }) test.mock_device.add_test_device(child_wc) - initiate_info_changed(child_wc, "window-covering") + initiate_info_changed(child_wc, "window-covering", parent) test.socket.matter:__expect_send({ parent.id, @@ -1725,6 +1728,7 @@ test.register_coroutine_test("Test: Window Covering - Preference Changes for Rev test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { reverse = "false" } })) test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { reverse = "true" } })) + test.socket.device_lifecycle():__queue_receive(parent:generate_info_changed({ })) test.wait_for_events() local reverse_preference_set = child_wc.preferences.reverse assert(reverse_preference_set == "true", "reverse_preference_set is True") 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 b3390edc29..1c9e901277 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 @@ -419,17 +419,20 @@ 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:generate_info_changed({})) + test.wait_for_events() 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_metadata_update({ provisioning_state = "PROVISIONED" }) + 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_metadata_update({ provisioning_state = "PROVISIONED" }) end, { min_api_version = 15 @@ -475,6 +478,7 @@ test.register_coroutine_test( 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" }) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({})) 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 diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_1_phase.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_1_phase.lua index f581f457df..484ba90c7c 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_1_phase.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_1_phase.lua @@ -128,7 +128,8 @@ test.register_coroutine_test( ) end end - end + end, + {test_init = function() test.mock_device.add_test_device(mock_parent) end} ) test.register_coroutine_test( diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_2_phase.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_2_phase.lua index 43ea5cdbe3..7ab752246a 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_2_phase.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_2_phase.lua @@ -128,7 +128,8 @@ test.register_coroutine_test( ) end end - end + end, + {test_init = function() test.mock_device.add_test_device(mock_parent) end} ) test.register_coroutine_test( diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_3_phase.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_3_phase.lua index 465e3add9f..c1fedbf717 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_3_phase.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_home_energy_meter_gen8_3_phase.lua @@ -128,7 +128,8 @@ test.register_coroutine_test( ) end end - end + end, + {test_init = function() test.mock_device.add_test_device(mock_parent) end} ) test.register_coroutine_test( 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 ef2e4997b2..9b5328a748 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 @@ -36,12 +36,15 @@ local mock_parent = test.mock_device.build_test_zwave_device({ zwave_manufacturer_id = 0x0086, zwave_product_type = 0x0103, zwave_product_id = 0x008C, - child_ids = { - "abcdefghijklmnopq", - "12345678910111213" - } }) +local mock_child = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("switch-binary.yml"), + parent_device_id = mock_parent.id, + parent_assigned_child_key = string.format("%02X", 2) +}) + + local mock_parent_no_data = test.mock_device.build_test_zwave_device({ label = "Aeotec Switch 1", profile = t_utils.get_profile_definition("switch-binary.yml"), @@ -53,6 +56,7 @@ local mock_parent_no_data = test.mock_device.build_test_zwave_device({ local function test_init() test.mock_device.add_test_device(mock_parent) + test.mock_device.add_test_device(mock_child) test.mock_device.add_test_device(mock_parent_no_data) end From 37e3766d9366b50f0c5724c2dcbb8a3b80e9441c Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Thu, 10 Sep 2026 13:23:54 -0500 Subject: [PATCH 518/538] WWSTCERT-13643 Linkind Light Stick T19 (#3221) --- drivers/SmartThings/matter-switch/fingerprints.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 4590ba0a74..64c5e17dde 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -231,6 +231,16 @@ matterManufacturer: vendorId: 0x1396 productId: 0x103E deviceProfileName: fan-modular + - id: "5014/4576" + deviceLabel: Linkind Light Stick T19 + vendorId: 0x1396 + productId: 0x11E0 + deviceProfileName: light-color-level + - id: "5014/4574" + deviceLabel: Linkind Light Stick T19 + vendorId: 0x1396 + productId: 0x11DE + deviceProfileName: light-color-level #Bosch Smart Home - id: "4617/12310" deviceLabel: Plug Compact [M] From ea9d3f45d14c359a4dbb4b32a8f17d243a7a3553 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Thu, 10 Sep 2026 15:18:26 -0500 Subject: [PATCH 519/538] add CLAUDE.md --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..eef4bd20cf --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md \ No newline at end of file From 19bb6f9b75a4a7590dfb5c5f9aed3bbf3308c77c Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Fri, 11 Sep 2026 18:00:30 +0900 Subject: [PATCH 520/538] Fix addUser() -> addCredential() issue (#3220) Signed-off-by: Hunsup Jung --- drivers/SmartThings/matter-lock/src/init.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-lock/src/init.lua b/drivers/SmartThings/matter-lock/src/init.lua index f94702c496..72bfaae425 100755 --- a/drivers/SmartThings/matter-lock/src/init.lua +++ b/drivers/SmartThings/matter-lock/src/init.lua @@ -1313,7 +1313,10 @@ local function get_user_response_handler(driver, device, ib, response) end local ep = find_default_endpoint(device, DoorLock.ID) - local status = elements.user_status.value + local status = nil + if elements.user_status ~= nil then + status = elements.user_status.value + end local maxUser = device:get_latest_state( "main", capabilities.lockUsers.ID, @@ -1624,11 +1627,11 @@ local function set_pin_response_handler(driver, device, ib, response) -- If user is added also, update User table if userIdx == nil then + userIdx = elements.user_index.value add_user_to_table(device, elements.user_index.value, nil, userType) end -- Update Credential table - userIdx = elements.user_index.value if cmdName == "addCredential" then add_credential_to_table(device, userIdx, credIdx, "pin") end From 80e5285f037489a64a2f2f9a3b635814a482b06f Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Mon, 14 Sep 2026 11:11:03 -0500 Subject: [PATCH 521/538] WWSTCERT-13701 Lowes - Utilitech A19 Smart Bulb (#3228) --- drivers/SmartThings/matter-switch/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 64c5e17dde..4929f42c08 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -4280,6 +4280,12 @@ matterManufacturer: vendorId: 0x164B productId: 0x0001 deviceProfileName: light-color-level +#Utilitech + - id: "5732/2" + deviceLabel: Lowes - Utilitech A19 Smart Bulb + vendorId: 0x1664 + productId: 0x0002 + deviceProfileName: light-color-level matterGeneric: From 6434162b0af004c3c1baf180a47ec739245ee7fe Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 15 Sep 2026 10:03:06 -0500 Subject: [PATCH 522/538] WWSTCERT-13745 Govee Christmas 2-in-1 String Lights (#3234) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 4929f42c08..4247d1699b 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -958,6 +958,11 @@ matterManufacturer: vendorId: 0x1387 productId: 0x2A40 deviceProfileName: light-color-level + - id: "4999/25168" + deviceLabel: Govee Christmas 2-in-1 String Lights + vendorId: 0x1387 + productId: 0x6250 + deviceProfileName: light-color-level # Hager - id: "4741/8" deviceLabel: Hager matter 2 buttons (battery) From 87c61ada4858f37beb03a12550f58a5376e016ce Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 15 Sep 2026 11:00:02 -0500 Subject: [PATCH 523/538] WWSTCERT-13738 Utilitech 17 Smart Flush Mount - Full Color (#3235) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 4247d1699b..46120cdc59 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -4291,6 +4291,11 @@ matterManufacturer: vendorId: 0x1664 productId: 0x0002 deviceProfileName: light-color-level + - id: "5732/1536" + deviceLabel: Utilitech 17 Smart Flush Mount - Full Color + vendorId: 0x1664 + productId: 0x0600 + deviceProfileName: light-color-level matterGeneric: From 1673928e793bd952f53597a99c6aac282df34d6a Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Wed, 16 Sep 2026 16:06:15 -0500 Subject: [PATCH 524/538] Updated CredentialId for CN Prod --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 752d3d358c..cedacdf64d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -39,7 +39,7 @@ def getDockerCredentialId() { def nodeLabel = params.NODE_LABEL ?: 'production' def region = get_region() if (nodeLabel == 'production' && region == 'cn') { - return 'artifactory-credentials' + return 'artifactory-cn' } else { return '' From 3f8f6e5ed7350115b55f2bf6d2360a4483a8eb73 Mon Sep 17 00:00:00 2001 From: "Jan Jakubiszyn/SmartThings Development (BJ.jakubiszynS) /SRPOL/Engineer/Samsung Electronics" Date: Tue, 1 Sep 2026 12:26:40 +0200 Subject: [PATCH 525/538] Fix hager driver delay # Conflicts: # drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua --- .../src/sub_drivers/hager/init.lua | 6 +- .../src/test/test_hager_waasys.lua | 366 ++++++++++++++++-- 2 files changed, 329 insertions(+), 43 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua index db8cfe67d4..acfaa10871 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua @@ -189,10 +189,10 @@ local function info_changed(driver, device, event, args) end if device.profile.id ~= args.old_st_store.profile.id or device.network_type == device_lib.NETWORK_TYPE_CHILD then - local parent = device:get_parent_device() - local matter_device = get_matter_device(parent) - local map = {} device.thread:call_with_delay(2, function() + local parent = device:get_parent_device() + local matter_device = get_matter_device(parent) + local map = {} if device:supports_capability(capabilities.button) then local button_eps = parent:get_field(BUTTON_EPS) local clean_eps = {} diff --git a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua index 4aeee85023..072229ffae 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua @@ -17,6 +17,7 @@ end local MATTER_DEVICE_ID = "MATTER_DEVICE_ID" local PARENT_ID = "PARENT_ID" local BUTTON_EPS = "__button_eps" +local MAIN_WC_EP = "__main_wc_ep" local function create_parent_device(product_id) return test.mock_device.build_test_matter_device({ @@ -434,6 +435,80 @@ local function create_hager_pir_device(profile_name, parent) }) end +-- Create Hager 1G device with ONLY a WindowCovering endpoint (12) - no button or occupancy endpoints +local function create_hager_1g_window_covering(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 1G Window Covering", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + type = "MATTER", + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0005, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 12, + clusters = { + { + cluster_id = clusters.WindowCovering.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.WindowCovering.types.Feature.LIFT | + clusters.WindowCovering.types.Feature.POSITION_AWARE_LIFT | + clusters.WindowCovering.types.Feature.ABSOLUTE_POSITION, + attributes = { + [clusters.WindowCovering.attributes.OperationalStatus.ID] = 0x00, + [clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID] = 0x0000, + } + } + }, + device_types = { { device_type_id = 0x0202, device_type_revision = 1 } } + }, + } + }) +end + +-- Create Hager 1G Relay device with endpoint 3 (OnOff cluster) +local function create_hager_1g_relay(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 1G Relay", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + type = "MATTER", + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0005, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 3, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OnOff.attributes.OnOff.ID] = false + } + } + }, + device_types = { { device_type_id = 0x0100, device_type_revision = 1 } } + }, + } + }) +end + local function add_parent_device(parent) test.mock_device.add_test_device(parent) test.socket.device_lifecycle:__queue_receive({ parent.id, "added" }) @@ -494,13 +569,10 @@ local function button_supported_values (matter_device) test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) end -local function initiate_info_changed(device, profile, parent) - test.socket.device_lifecycle:__queue_receive(device:generate_info_changed({ profile = { id = profile } })) +local function initiate_info_changed(device, profile) test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.device_lifecycle:__queue_receive(device:generate_info_changed({ profile = { id = profile } })) test.mock_time.advance_time(2) - if parent ~= nil then - test.socket.device_lifecycle:__queue_receive(parent:generate_info_changed({})) - end end local function configure_parent(device) @@ -515,6 +587,50 @@ local function configure_parent(device) test.mock_time.advance_time(3) end +-- Create window covering child device on EP12 via PartsList and DeviceTypeList reports +local function announce_window_covering_child(host) + test.socket.matter:__queue_receive({ + host.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(host, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(12), + })) + }) + for _, ep in ipairs({ 8, 9, 12 }) do + test.socket.matter:__expect_send({ + host.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(host, ep) + }) + end + + test.socket.matter:__queue_receive({ + host.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(host, 12, data_types.Array { + { + device_type = data_types.Uint32(514), + revision = data_types.Uint16(1) + } + }) + }) + host:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "window-covering", + parent_device_id = host.id, + parent_assigned_child_key = "12" + }) + + local child_wc = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("window-covering.yml"), + device_network_id = string.format("%s:12", host.id), + parent_device_id = host.id, + parent_assigned_child_key = "12" + }) + test.mock_device.add_test_device(child_wc) + return child_wc +end + local parent = create_parent_device(0x0006) -- 2G button product local parent_1g = create_parent_device(0x0005) -- 1G button product local parent_pir = create_parent_device(0x0007) -- PIR product @@ -596,9 +712,9 @@ test.register_coroutine_test("Test: 4-Button Device Detection - Profile Changes }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Button Event Handling - Pushed, Double Press, and Held Events on 4-Button Device", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -803,9 +919,9 @@ test.register_coroutine_test("Test: Button Event Handling - Pushed, Double Press }) test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.held({ state_change = true }))) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Device Type Handler - Handles Button (Type 15) and OnOff (Type 256) Device Types with Child Creation", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -899,9 +1015,9 @@ test.register_coroutine_test("Test: Device Type Handler - Handles Button (Type 1 clusters.OnOff.attributes.OnOff:subscribe(parent, nil) }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: 2G Relay - Profile Changes Between light-binary and 2-button Based On Endpoint Availability", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1144,9 +1260,9 @@ test.register_coroutine_test("Test: 2G Relay - Profile Changes Between light-bin parent_assigned_child_key = "4" }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Dimmer Device - Child Creation for Dimmable Endpoint with Button Support", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1243,9 +1359,9 @@ test.register_coroutine_test("Test: Dimmer Device - Child Creation for Dimmable }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: 1G Dimmer - Initialization and Profile Update to light-level", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1255,9 +1371,9 @@ test.register_coroutine_test("Test: 1G Dimmer - Initialization and Profile Updat test.wait_for_events() configure_parent(parent) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: 1G Dimmer - Host Commands and Level Control Capabilities", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1330,9 +1446,9 @@ test.register_coroutine_test("Test: 1G Dimmer - Host Commands and Level Control clusters.LevelControl.server.commands.MoveToLevelWithOnOff(dimmer, 4, 51, nil, 0, 0) }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: PIR Device - Initialization with Motion and Illuminance Capabilities", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1343,9 +1459,9 @@ test.register_coroutine_test("Test: PIR Device - Initialization with Motion and configure_parent(parent_pir) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: PIR Device - Complete Functionality with Motion, Illuminance, and Dimmer Support", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1435,7 +1551,7 @@ test.register_coroutine_test("Test: PIR Device - Complete Functionality with Mot }) test.mock_device.add_test_device(child_dimmer) - initiate_info_changed(child_dimmer, "light-level", parent_pir) + initiate_info_changed(child_dimmer, "light-level") test.socket.matter:__expect_send({ parent_pir.id, cluster_base.subscribe(parent_pir, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) @@ -1500,9 +1616,9 @@ test.register_coroutine_test("Test: PIR Device - Complete Functionality with Mot test.socket.capability:__expect_send(child_dimmer:generate_test_message("main", capabilities.switchLevel.level(50))) parent_pir.expect_native_attr_handler_registration(parent_pir, "switchLevel", "level") end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile with Window Covering Child Device", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1561,7 +1677,7 @@ test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile }) test.mock_device.add_test_device(child_wc) - initiate_info_changed(child_wc, "window-covering", parent) + initiate_info_changed(child_wc, "window-covering") test.socket.matter:__expect_send({ parent.id, @@ -1641,9 +1757,9 @@ test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closed())) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Window Covering - Preference Changes for Reverse Polarity and Preset Position", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1728,7 +1844,6 @@ test.register_coroutine_test("Test: Window Covering - Preference Changes for Rev test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { reverse = "false" } })) test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { reverse = "true" } })) - test.socket.device_lifecycle():__queue_receive(parent:generate_info_changed({ })) test.wait_for_events() local reverse_preference_set = child_wc.preferences.reverse assert(reverse_preference_set == "true", "reverse_preference_set is True") @@ -1810,8 +1925,179 @@ test.register_coroutine_test("Test: Window Covering - Preference Changes for Rev cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) }) end, - { - min_api_version = 15 + { + min_api_version = 15 + }) + +test.register_coroutine_test("Test: 1G Window Covering - window-covering Profile and No Duplicate Child Device", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent_1g) + local wc_1g = create_hager_1g_window_covering("window-covering", parent_1g) + add_matter_device(wc_1g, parent_1g, "window-covering") + test.wait_for_events() + configure_parent(parent_1g) + test.wait_for_events() + + local main_wc_ep = wc_1g:get_field(MAIN_WC_EP) + assert(main_wc_ep == 12, "MAIN_WC_EP is set to 12") + + -- EP12 reported again → no second child created, MAIN_WC_EP already set + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_1g, 2, data_types.Array({ + data_types.Uint16(12), + })) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 12) + }) + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_1g, 12, data_types.Array { + { + device_type = data_types.Uint32(514), + revision = data_types.Uint16(1) + } }) + }) + test.wait_for_events() +end, + { + min_api_version = 15 + }) + +test.register_coroutine_test("Test: 1G Relay - Profile Changes Between light-binary and 2-button Based On EP3 Availability", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent_1g) + local relay_1g = create_hager_1g_relay("light-binary", parent_1g) + add_switch_matter_device(relay_1g, parent_1g) + test.wait_for_events() + configure_parent(parent_1g) + test.wait_for_events() + + -- Scenario 1: EP3 present → light-binary profile + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_1g, 2, data_types.Array({ + data_types.Uint16(3), + })) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 3) + }) + relay_1g:expect_metadata_update({ profile = "light-binary" }) + test.wait_for_events() + + -- EP3 is device type 256 (OnOff) - no child device created for 1G + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_1g, 3, data_types.Array { + { + device_type = data_types.Uint32(256), + revision = data_types.Uint16(1) + } + }) + }) + test.wait_for_events() + + -- Scenario 2: EP3 removed, buttons 8 & 9 present → 2-button profile + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_1g, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + })) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 8) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 9) + }) + relay_1g:expect_metadata_update({ profile = "2-button" }) + test.wait_for_events() +end, + { + min_api_version = 15 + }) + +test.register_coroutine_test("Test: Window Covering - Position Attribute Reports With Reverse Polarity", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local window_covering_device = create_matter_device_with_window("2-button", parent) + add_matter_device(window_covering_device, parent, "2-button") + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + local child_wc = announce_window_covering_child(parent) + test.wait_for_events() + + -- Attribute reports are addressed to the host, so reverse is set on the host + test.socket.device_lifecycle():__queue_receive( + parent:generate_info_changed({ preferences = { reverse = "true" } }) + ) + test.wait_for_events() + + -- Verify 50% position via attribute report - direction independent + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 5000) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50))) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.partially_open())) + test.wait_for_events() + + -- Verify 100% position via attribute report - with reverse true, this should report closed + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 0) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100))) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closed())) + test.wait_for_events() + + -- Verify 0% position via attribute report - with reverse true, this should report open + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 10000) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0))) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.open())) + test.wait_for_events() +end, + { + min_api_version = 15 + }) + +test.register_coroutine_test("Test: Window Covering - Unrecognised OperationalStatus Reports Unknown State", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local window_covering_device = create_matter_device_with_window("2-button", parent) + add_matter_device(window_covering_device, parent, "2-button") + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + local child_wc = announce_window_covering_child(parent) + test.wait_for_events() + + -- OperationalStatus 0x03 is neither opening (1) nor closing (2) + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.OperationalStatus:build_test_report_data(parent, 12, 0x03) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.unknown())) + test.wait_for_events() +end, + { + min_api_version = 15 + }) test.run_registered_tests() From 7be19456552364148bc2bd0f687cebf237af0ce3 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Mon, 21 Sep 2026 10:23:13 -0500 Subject: [PATCH 526/538] WWSTCERT-13529 SwitchBot RGBIC Wire Neon Rope Light (#3208) * WWSTCERT-13529 SwitchBot RGBIC Wire Neon Rope Light * Update device label for SwitchBot RGBIC Wire Light --- drivers/SmartThings/matter-switch/fingerprints.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 46120cdc59..b756cfad60 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -4253,6 +4253,16 @@ matterManufacturer: vendorId: 0x1397 productId: 0x07E7 deviceProfileName: matter-bridge + - id: "5015/2040" + deviceLabel: SwitchBot Color Lighting + vendorId: 0x1397 + productId: 0x07F8 + deviceProfileName: light-color-level + - id: "5015/2039" + deviceLabel: SwitchBot Floor Lamp + vendorId: 0x1397 + productId: 0x07F7 + deviceProfileName: light-color-level - id: "Nature/Bridge" deviceLabel: Matter Bridge vendorId: 0x138A From db0ec25fac22e1681445ee0530759544ec7ac042 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Wed, 9 Sep 2026 13:22:13 -0500 Subject: [PATCH 527/538] Update pairs table emptiness checks --- .../switch_handlers/attribute_handlers.lua | 16 +-- .../matter-switch/src/switch_utils/utils.lua | 11 ++- .../src/test/test_electrical_sensor_set.lua | 99 +++++++++++++++++++ 3 files changed, 118 insertions(+), 8 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index ae1f219606..46338233a0 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -325,12 +325,12 @@ end --- SET feature, all AvailableEndpoints responses must be handled before profiling. function AttributeHandlers.available_endpoints_handler(driver, device, ib, response) if device:get_field(fields.profiling_data.POWER_TOPOLOGY) ~= nil then - device.log.warn("Received an AvailableEndpoints response after power topology has already been determined. Ignoring this response.") + device.log.warn_with({hub_logs=true},"Received an AvailableEndpoints response after power topology has already been determined. Ignoring this response.") return end local set_topology_eps = device:get_field(fields.ELECTRICAL_SENSOR_EPS) if set_topology_eps == nil then - device.log.warn("Received an AvailableEndpoints response but no Electrical Sensor endpoints have been identified as supporting the Power Topology cluster with SET feature. Ignoring this response.") + device.log.warn_with({hub_logs=true},"Received an AvailableEndpoints response but no Electrical Sensor endpoints have been identified as supporting the Power Topology cluster with SET feature. Ignoring this response.") return end @@ -349,7 +349,8 @@ function AttributeHandlers.available_endpoints_handler(driver, device, ib, respo break end end - if #set_topology_eps == 0 then -- in other words, all AvailableEndpoints attribute responses have been handled + if switch_utils.is_field_empty(device, fields.ELECTRICAL_SENSOR_EPS) then + device.log.info_with({hub_logs=true}, "All AvailableEndpoints attribute responses for SET Electrical Sensor endpoints have been handled, attempting to match profile") device:set_field(fields.profiling_data.POWER_TOPOLOGY, clusters.PowerTopology.types.Feature.SET_TOPOLOGY, {persist=true}) device_cfg.match_profile(driver, device) end @@ -360,16 +361,16 @@ end function AttributeHandlers.parts_list_handler(driver, device, ib, response) if device:get_field(fields.profiling_data.POWER_TOPOLOGY) ~= nil then - device.log.warn("Received a PartsList response after power topology has already been determined. Ignoring this response.") + device.log.warn_with({hub_logs=true}, "Received a PartsList response after power topology has already been determined. Ignoring this response.") return end local tree_topology_eps = device:get_field(fields.ELECTRICAL_SENSOR_EPS) if tree_topology_eps == nil then - device.log.warn("Received a PartsList response but no Electrical Sensor endpoints have been identified as supporting the Power Topology cluster with TREE feature. Ignoring this response.") + device.log.warn_with({hub_logs=true}, "Received a PartsList response but no Electrical Sensor endpoints have been identified as supporting the Power Topology cluster with TREE feature. Ignoring this response.") return end - device.log.debug_with({hub_logs=true}, string.format("Handling PartsList response for endpoint %d with elements: %s", ib.endpoint_id, st_utils.stringify_table(ib.data.elements or {}))) + device.log.info_with({hub_logs=true}, string.format("Handling PartsList response for endpoint %d with elements: %s", ib.endpoint_id, st_utils.stringify_table(ib.data.elements or {}))) for i, tree_ep_info in pairs(tree_topology_eps or {}) do if ib.endpoint_id == tree_ep_info.endpoint_id then -- since EP response is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table @@ -384,7 +385,8 @@ function AttributeHandlers.parts_list_handler(driver, device, ib, response) break end end - if #tree_topology_eps == 0 then -- in other words, all PartsList attribute responses for TREE Electrical Sensor EPs have been handled + if switch_utils.is_field_empty(device, fields.ELECTRICAL_SENSOR_EPS) then + device.log.info_with({hub_logs=true}, "All PartsList attribute responses for TREE Electrical Sensor endpoints have been handled, attempting to match profile") device:set_field(fields.profiling_data.POWER_TOPOLOGY, clusters.PowerTopology.types.Feature.TREE_TOPOLOGY, {persist=true}) device_cfg.match_profile(driver, device) end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 1d12f41781..cb95ff770b 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -53,11 +53,20 @@ 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 + new_table[index] = nil -- remove value associated with index from table without altering table structure device:set_field(field_name, new_table) end end +function utils.is_field_empty(device, field_name) + local field = device:get_field(field_name) + if type(field) == "table" then + return next(field) == nil + else + return field == nil + end +end + function utils.mired_to_kelvin(value, minOrMax) if value == 0 then -- shouldn't happen, but has value = 1 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 index 32989187fa..18074c9b4e 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -112,6 +112,59 @@ local mock_device_periodic = test.mock_device.build_test_matter_device({ }, }) +--- Models an outlet of a multi-outlet power strip, which combines the Electrical Sensor and +--- OnOff Plug In Unit device types on a single endpoint. Note that the Electrical Sensor device +--- type is listed first, so it is the endpoint's primary device type and no profile is mapped to it. +local function build_power_strip_outlet_endpoint(endpoint_id) + return { + endpoint_id = endpoint_id, + clusters = { + { cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 1, }, + { cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER", feature_map = 2, }, + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 15, }, + { 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 + { device_type_id = 0x010A, device_type_revision = 1 }, -- OnOff Plug In Unit + } + } +end + +--- A 4-outlet power strip that reports its endpoints out of numerical order, as the Tapo +--- P304M does. The order in which the AvailableEndpoints reports are handled must not affect +--- profiling: every Electrical Sensor endpoint has to be accounted for before profiles are matched. +local mock_device_power_strip = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("plug-power-energy-powerConsumption.yml"), + manufacturer_info = { + vendor_id = 0x1392, + product_id = 0x010F, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + } + }, + build_power_strip_outlet_endpoint(1), + build_power_strip_outlet_endpoint(3), + build_power_strip_outlet_endpoint(4), + build_power_strip_outlet_endpoint(2), + }, +}) + +local subscribed_attributes_power_strip = { + clusters.OnOff.attributes.OnOff, + clusters.ElectricalPowerMeasurement.attributes.ActivePower, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + clusters.PowerTopology.attributes.AvailableEndpoints, +} + local subscribed_attributes_periodic = { clusters.OnOff.attributes.OnOff, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, @@ -201,6 +254,17 @@ local function test_init_periodic() test.socket.matter:__expect_send({ mock_device_periodic.id, subscribe_request }) end +local function test_init_power_strip() + test.mock_device.add_test_device(mock_device_power_strip) + local subscribe_request = subscribed_attributes_power_strip[1]:subscribe(mock_device_power_strip) + for i, cluster in ipairs(subscribed_attributes_power_strip) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_power_strip)) + end + end + test.socket.matter:__expect_send({ mock_device_power_strip.id, subscribe_request }) +end + test.register_message_test( "Active power measurement should generate correct messages", { @@ -711,4 +775,39 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Profiling of a power strip must wait for the AvailableEndpoints report of every Electrical Sensor endpoint", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_power_strip.id, "doConfigure" }) + mock_device_power_strip:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + -- the reports arrive in endpoint order, which does not match the order that the endpoints + -- were reported in during the interview. Each outlet is the only endpoint in its own power set. + for _, endpoint_id in ipairs({1, 2, 3, 4}) do + test.socket.matter:__queue_receive({ + mock_device_power_strip.id, + clusters.PowerTopology.attributes.AvailableEndpoints:build_test_report_data( + mock_device_power_strip, endpoint_id, {uint32(endpoint_id)} + ) + }) + end + -- every outlet supports both power and energy measurement, so none of them should fall back + -- to the generic "switch-binary" profile used for an OnOff endpoint without electrical tags + for _, endpoint_id in ipairs({2, 3, 4}) do + mock_device_power_strip:expect_device_create({ + type = "EDGE_CHILD", + label = string.format("nil %d", endpoint_id), + profile = "plug-power-energy-powerConsumption", + parent_device_id = mock_device_power_strip.id, + parent_assigned_child_key = string.format("%d", endpoint_id) + }) + end + mock_device_power_strip:expect_metadata_update({ profile = "plug-power-energy-powerConsumption" }) + end, + { + test_init = test_init_power_strip, + min_api_version = 14 + } +) + test.run_registered_tests() From 31cd2c97bb36a10e8671df9e57701c624aa86f33 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 22 Sep 2026 10:56:49 -0500 Subject: [PATCH 528/538] WWSTCERT-13832 Blinds motor (#3244) --- drivers/SmartThings/matter-window-covering/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-window-covering/fingerprints.yml b/drivers/SmartThings/matter-window-covering/fingerprints.yml index 34406cf01c..66b33f0bf7 100644 --- a/drivers/SmartThings/matter-window-covering/fingerprints.yml +++ b/drivers/SmartThings/matter-window-covering/fingerprints.yml @@ -255,6 +255,12 @@ matterManufacturer: vendorId: 0x1523 productId: 0x0A69 deviceProfileName: window-covering-battery +#3L Smart + - id: "5412/48897" + deviceLabel: Blinds motor + vendorId: 0x1524 + productId: 0xBF01 + deviceProfileName: window-covering-battery matterGeneric: - id: "windowcovering" deviceLabel: Matter Window Covering From 4a77e6fab7a3eecdc5a47807ef911a56a2ef2f4b Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Tue, 22 Sep 2026 10:57:54 -0500 Subject: [PATCH 529/538] WWSTCERT-13815 Utilitech 1x4 Smart Panel - Full Color (#3243) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index b756cfad60..fd2a35c321 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -4306,6 +4306,11 @@ matterManufacturer: vendorId: 0x1664 productId: 0x0600 deviceProfileName: light-color-level + - id: "5732/768" + deviceLabel: Utilitech 1x4 Smart Panel - Full Color + vendorId: 0x1664 + productId: 0x0300 + deviceProfileName: light-color-level matterGeneric: From 5703703e63cf132d0ee4cca86e6d8f8b369fa850 Mon Sep 17 00:00:00 2001 From: NoahCornell Date: Tue, 22 Sep 2026 17:10:13 -0500 Subject: [PATCH 530/538] sonos: fix offline grouped speakers --- .../SmartThings/sonos/src/api/sonos_connection.lua | 11 +++++++---- .../sonos/src/api/sonos_websocket_router.lua | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/SmartThings/sonos/src/api/sonos_connection.lua b/drivers/SmartThings/sonos/src/api/sonos_connection.lua index 6a131c3b0c..4852fc517c 100644 --- a/drivers/SmartThings/sonos/src/api/sonos_connection.lua +++ b/drivers/SmartThings/sonos/src/api/sonos_connection.lua @@ -547,6 +547,8 @@ function SonosConnection.new(driver, device) self.on_close = function(_) log.debug(string.format("OnClose for %s", device_name)) if self._initialized then + self._initialized = false + log.debug(string.format("Marking %s as offline due to websocket closure", device_name)) self.device:offline() end if self._keepalive then @@ -566,10 +568,10 @@ function SonosConnection:is_running() string.format( "%s all connections running? %s", self.device.label, - st_utils.stringify_table({ coordinator = self_running, mine = self_running }) + st_utils.stringify_table({ coordinator = coord_running, mine = self_running }) ) ) - return self_running and coord_running + return self_running and coord_running and self._initialized end --- Whether or not the connection has a live websocket connection @@ -586,7 +588,7 @@ function SonosConnection:self_running() ) ) end - return type(unique_key) == "string" and Router.is_connected(unique_key) and self._initialized + return type(unique_key) == "string" and Router.is_connected(unique_key) end --- Whether or not the connection has a live websocket connection to its coordinator @@ -685,7 +687,7 @@ function SonosConnection:start() end if not self:coordinator_running() then - --TODO this is not infallible + --TODO this is not infallible, but is checked below _open_coordinator_socket(self, household_id, player_id, api_key) end @@ -700,6 +702,7 @@ function SonosConnection:start() and coordinator_unique_key and Router.is_connected(coordinator_unique_key) then + log.debug(string.format("Marking %s as online since all websockets connected", self.device.label)) self.device:online() self._initialized = true self._keepalive = true diff --git a/drivers/SmartThings/sonos/src/api/sonos_websocket_router.lua b/drivers/SmartThings/sonos/src/api/sonos_websocket_router.lua index c19e35274b..9ba4710a66 100644 --- a/drivers/SmartThings/sonos/src/api/sonos_websocket_router.lua +++ b/drivers/SmartThings/sonos/src/api/sonos_websocket_router.lua @@ -233,11 +233,11 @@ end ---@return string? function SonosWebSocketRouter.register_listener_for_socket(listener, unique_key_for_socket) if listener and listener.device and listener.device.label then - log.debug( + log.debug(string.format( "Registering SonosConnection for device %s as listener for player %s websocket", listener.device.label, unique_key_for_socket - ) + )) end local ws = websockets[unique_key_for_socket] From 5183653601bf94bbb68887b331f652fd5558c630 Mon Sep 17 00:00:00 2001 From: JanJakubiszyn <101173721+JanJakubiszyn@users.noreply.github.com> Date: Wed, 23 Sep 2026 17:59:50 +0200 Subject: [PATCH 531/538] Revert "Fix hager driver delay" (#3247) --- .../src/sub_drivers/hager/init.lua | 6 +- .../src/test/test_hager_waasys.lua | 366 ++---------------- 2 files changed, 43 insertions(+), 329 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua index acfaa10871..db8cfe67d4 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua @@ -189,10 +189,10 @@ local function info_changed(driver, device, event, args) end if device.profile.id ~= args.old_st_store.profile.id or device.network_type == device_lib.NETWORK_TYPE_CHILD then + local parent = device:get_parent_device() + local matter_device = get_matter_device(parent) + local map = {} device.thread:call_with_delay(2, function() - local parent = device:get_parent_device() - local matter_device = get_matter_device(parent) - local map = {} if device:supports_capability(capabilities.button) then local button_eps = parent:get_field(BUTTON_EPS) local clean_eps = {} diff --git a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua index 072229ffae..4aeee85023 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua @@ -17,7 +17,6 @@ end local MATTER_DEVICE_ID = "MATTER_DEVICE_ID" local PARENT_ID = "PARENT_ID" local BUTTON_EPS = "__button_eps" -local MAIN_WC_EP = "__main_wc_ep" local function create_parent_device(product_id) return test.mock_device.build_test_matter_device({ @@ -435,80 +434,6 @@ local function create_hager_pir_device(profile_name, parent) }) end --- Create Hager 1G device with ONLY a WindowCovering endpoint (12) - no button or occupancy endpoints -local function create_hager_1g_window_covering(profile_name, parent) - return test.mock_device.build_test_matter_device({ - label = "Hager G2 1G Window Covering", - profile = t_utils.get_profile_definition(profile_name .. ".yml"), - type = "MATTER", - manufacturer_info = { - vendor_id = 0x1285, - product_id = 0x0005, - }, - parent_device_id = parent.id, - endpoints = { - { - endpoint_id = 0, - clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, - device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } - }, - { - endpoint_id = 12, - clusters = { - { - cluster_id = clusters.WindowCovering.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = clusters.WindowCovering.types.Feature.LIFT | - clusters.WindowCovering.types.Feature.POSITION_AWARE_LIFT | - clusters.WindowCovering.types.Feature.ABSOLUTE_POSITION, - attributes = { - [clusters.WindowCovering.attributes.OperationalStatus.ID] = 0x00, - [clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID] = 0x0000, - } - } - }, - device_types = { { device_type_id = 0x0202, device_type_revision = 1 } } - }, - } - }) -end - --- Create Hager 1G Relay device with endpoint 3 (OnOff cluster) -local function create_hager_1g_relay(profile_name, parent) - return test.mock_device.build_test_matter_device({ - label = "Hager G2 1G Relay", - profile = t_utils.get_profile_definition(profile_name .. ".yml"), - type = "MATTER", - manufacturer_info = { - vendor_id = 0x1285, - product_id = 0x0005, - }, - parent_device_id = parent.id, - endpoints = { - { - endpoint_id = 0, - clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, - device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } - }, - { - endpoint_id = 3, - clusters = { - { - cluster_id = clusters.OnOff.ID, - cluster_type = "SERVER", - cluster_revision = 1, - attributes = { - [clusters.OnOff.attributes.OnOff.ID] = false - } - } - }, - device_types = { { device_type_id = 0x0100, device_type_revision = 1 } } - }, - } - }) -end - local function add_parent_device(parent) test.mock_device.add_test_device(parent) test.socket.device_lifecycle:__queue_receive({ parent.id, "added" }) @@ -569,10 +494,13 @@ local function button_supported_values (matter_device) test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.supportedButtonValues({ "pushed", "double", "held" }))) end -local function initiate_info_changed(device, profile) - test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") +local function initiate_info_changed(device, profile, parent) test.socket.device_lifecycle:__queue_receive(device:generate_info_changed({ profile = { id = profile } })) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") test.mock_time.advance_time(2) + if parent ~= nil then + test.socket.device_lifecycle:__queue_receive(parent:generate_info_changed({})) + end end local function configure_parent(device) @@ -587,50 +515,6 @@ local function configure_parent(device) test.mock_time.advance_time(3) end --- Create window covering child device on EP12 via PartsList and DeviceTypeList reports -local function announce_window_covering_child(host) - test.socket.matter:__queue_receive({ - host.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(host, 2, data_types.Array({ - data_types.Uint16(8), - data_types.Uint16(9), - data_types.Uint16(12), - })) - }) - for _, ep in ipairs({ 8, 9, 12 }) do - test.socket.matter:__expect_send({ - host.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(host, ep) - }) - end - - test.socket.matter:__queue_receive({ - host.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(host, 12, data_types.Array { - { - device_type = data_types.Uint32(514), - revision = data_types.Uint16(1) - } - }) - }) - host:expect_device_create({ - type = "EDGE_CHILD", - label = "Hager G2 4x Button 1", - profile = "window-covering", - parent_device_id = host.id, - parent_assigned_child_key = "12" - }) - - local child_wc = test.mock_device.build_test_child_device({ - profile = t_utils.get_profile_definition("window-covering.yml"), - device_network_id = string.format("%s:12", host.id), - parent_device_id = host.id, - parent_assigned_child_key = "12" - }) - test.mock_device.add_test_device(child_wc) - return child_wc -end - local parent = create_parent_device(0x0006) -- 2G button product local parent_1g = create_parent_device(0x0005) -- 1G button product local parent_pir = create_parent_device(0x0007) -- PIR product @@ -712,9 +596,9 @@ test.register_coroutine_test("Test: 4-Button Device Detection - Profile Changes }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Button Event Handling - Pushed, Double Press, and Held Events on 4-Button Device", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -919,9 +803,9 @@ test.register_coroutine_test("Test: Button Event Handling - Pushed, Double Press }) test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.held({ state_change = true }))) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Device Type Handler - Handles Button (Type 15) and OnOff (Type 256) Device Types with Child Creation", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1015,9 +899,9 @@ test.register_coroutine_test("Test: Device Type Handler - Handles Button (Type 1 clusters.OnOff.attributes.OnOff:subscribe(parent, nil) }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: 2G Relay - Profile Changes Between light-binary and 2-button Based On Endpoint Availability", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1260,9 +1144,9 @@ test.register_coroutine_test("Test: 2G Relay - Profile Changes Between light-bin parent_assigned_child_key = "4" }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Dimmer Device - Child Creation for Dimmable Endpoint with Button Support", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1359,9 +1243,9 @@ test.register_coroutine_test("Test: Dimmer Device - Child Creation for Dimmable }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: 1G Dimmer - Initialization and Profile Update to light-level", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1371,9 +1255,9 @@ test.register_coroutine_test("Test: 1G Dimmer - Initialization and Profile Updat test.wait_for_events() configure_parent(parent) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: 1G Dimmer - Host Commands and Level Control Capabilities", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1446,9 +1330,9 @@ test.register_coroutine_test("Test: 1G Dimmer - Host Commands and Level Control clusters.LevelControl.server.commands.MoveToLevelWithOnOff(dimmer, 4, 51, nil, 0, 0) }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: PIR Device - Initialization with Motion and Illuminance Capabilities", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1459,9 +1343,9 @@ test.register_coroutine_test("Test: PIR Device - Initialization with Motion and configure_parent(parent_pir) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: PIR Device - Complete Functionality with Motion, Illuminance, and Dimmer Support", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1551,7 +1435,7 @@ test.register_coroutine_test("Test: PIR Device - Complete Functionality with Mot }) test.mock_device.add_test_device(child_dimmer) - initiate_info_changed(child_dimmer, "light-level") + initiate_info_changed(child_dimmer, "light-level", parent_pir) test.socket.matter:__expect_send({ parent_pir.id, cluster_base.subscribe(parent_pir, nil, clusters.OnOff.ID, clusters.OnOff.attributes.OnOff.ID, nil) @@ -1616,9 +1500,9 @@ test.register_coroutine_test("Test: PIR Device - Complete Functionality with Mot test.socket.capability:__expect_send(child_dimmer:generate_test_message("main", capabilities.switchLevel.level(50))) parent_pir.expect_native_attr_handler_registration(parent_pir, "switchLevel", "level") end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile with Window Covering Child Device", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1677,7 +1561,7 @@ test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile }) test.mock_device.add_test_device(child_wc) - initiate_info_changed(child_wc, "window-covering") + initiate_info_changed(child_wc, "window-covering", parent) test.socket.matter:__expect_send({ parent.id, @@ -1757,9 +1641,9 @@ test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closed())) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Window Covering - Preference Changes for Reverse Polarity and Preset Position", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1844,6 +1728,7 @@ test.register_coroutine_test("Test: Window Covering - Preference Changes for Rev test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { reverse = "false" } })) test.socket.device_lifecycle():__queue_receive(child_wc:generate_info_changed({ preferences = { reverse = "true" } })) + test.socket.device_lifecycle():__queue_receive(parent:generate_info_changed({ })) test.wait_for_events() local reverse_preference_set = child_wc.preferences.reverse assert(reverse_preference_set == "true", "reverse_preference_set is True") @@ -1925,179 +1810,8 @@ test.register_coroutine_test("Test: Window Covering - Preference Changes for Rev cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) }) end, - { - min_api_version = 15 - }) - -test.register_coroutine_test("Test: 1G Window Covering - window-covering Profile and No Duplicate Child Device", function() - test.socket.matter:__set_channel_ordering("relaxed") - add_parent_device(parent_1g) - local wc_1g = create_hager_1g_window_covering("window-covering", parent_1g) - add_matter_device(wc_1g, parent_1g, "window-covering") - test.wait_for_events() - configure_parent(parent_1g) - test.wait_for_events() - - local main_wc_ep = wc_1g:get_field(MAIN_WC_EP) - assert(main_wc_ep == 12, "MAIN_WC_EP is set to 12") - - -- EP12 reported again → no second child created, MAIN_WC_EP already set - test.socket.matter:__queue_receive({ - parent_1g.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_1g, 2, data_types.Array({ - data_types.Uint16(12), - })) - }) - test.socket.matter:__expect_send({ - parent_1g.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 12) - }) - test.wait_for_events() - - test.socket.matter:__queue_receive({ - parent_1g.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_1g, 12, data_types.Array { - { - device_type = data_types.Uint32(514), - revision = data_types.Uint16(1) - } + { + min_api_version = 15 }) - }) - test.wait_for_events() -end, - { - min_api_version = 15 - }) - -test.register_coroutine_test("Test: 1G Relay - Profile Changes Between light-binary and 2-button Based On EP3 Availability", function() - test.socket.matter:__set_channel_ordering("relaxed") - add_parent_device(parent_1g) - local relay_1g = create_hager_1g_relay("light-binary", parent_1g) - add_switch_matter_device(relay_1g, parent_1g) - test.wait_for_events() - configure_parent(parent_1g) - test.wait_for_events() - - -- Scenario 1: EP3 present → light-binary profile - test.socket.matter:__queue_receive({ - parent_1g.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_1g, 2, data_types.Array({ - data_types.Uint16(3), - })) - }) - test.socket.matter:__expect_send({ - parent_1g.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 3) - }) - relay_1g:expect_metadata_update({ profile = "light-binary" }) - test.wait_for_events() - - -- EP3 is device type 256 (OnOff) - no child device created for 1G - test.socket.matter:__queue_receive({ - parent_1g.id, - clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_1g, 3, data_types.Array { - { - device_type = data_types.Uint32(256), - revision = data_types.Uint16(1) - } - }) - }) - test.wait_for_events() - - -- Scenario 2: EP3 removed, buttons 8 & 9 present → 2-button profile - test.socket.matter:__queue_receive({ - parent_1g.id, - clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_1g, 2, data_types.Array({ - data_types.Uint16(8), - data_types.Uint16(9), - })) - }) - test.socket.matter:__expect_send({ - parent_1g.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 8) - }) - test.socket.matter:__expect_send({ - parent_1g.id, - clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 9) - }) - relay_1g:expect_metadata_update({ profile = "2-button" }) - test.wait_for_events() -end, - { - min_api_version = 15 - }) - -test.register_coroutine_test("Test: Window Covering - Position Attribute Reports With Reverse Polarity", function() - test.socket.matter:__set_channel_ordering("relaxed") - add_parent_device(parent) - local window_covering_device = create_matter_device_with_window("2-button", parent) - add_matter_device(window_covering_device, parent, "2-button") - test.wait_for_events() - configure_parent(parent) - test.wait_for_events() - - local child_wc = announce_window_covering_child(parent) - test.wait_for_events() - - -- Attribute reports are addressed to the host, so reverse is set on the host - test.socket.device_lifecycle():__queue_receive( - parent:generate_info_changed({ preferences = { reverse = "true" } }) - ) - test.wait_for_events() - - -- Verify 50% position via attribute report - direction independent - test.socket.matter:__queue_receive({ - parent.id, - clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 5000) - }) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50))) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.partially_open())) - test.wait_for_events() - - -- Verify 100% position via attribute report - with reverse true, this should report closed - test.socket.matter:__queue_receive({ - parent.id, - clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 0) - }) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100))) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closed())) - test.wait_for_events() - - -- Verify 0% position via attribute report - with reverse true, this should report open - test.socket.matter:__queue_receive({ - parent.id, - clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 10000) - }) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0))) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.open())) - test.wait_for_events() -end, - { - min_api_version = 15 - }) - -test.register_coroutine_test("Test: Window Covering - Unrecognised OperationalStatus Reports Unknown State", function() - test.socket.matter:__set_channel_ordering("relaxed") - add_parent_device(parent) - local window_covering_device = create_matter_device_with_window("2-button", parent) - add_matter_device(window_covering_device, parent, "2-button") - test.wait_for_events() - configure_parent(parent) - test.wait_for_events() - - local child_wc = announce_window_covering_child(parent) - test.wait_for_events() - - -- OperationalStatus 0x03 is neither opening (1) nor closing (2) - test.socket.matter:__queue_receive({ - parent.id, - clusters.WindowCovering.attributes.OperationalStatus:build_test_report_data(parent, 12, 0x03) - }) - test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.unknown())) - test.wait_for_events() -end, - { - min_api_version = 15 - }) test.run_registered_tests() From 9d99e756031ca0637648ca6296d41a0268c88743 Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Wed, 23 Sep 2026 11:20:01 -0500 Subject: [PATCH 532/538] WWSTCERT-13716 Photoelectric Smoke Alarm (#3229) --- drivers/SmartThings/matter-sensor/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index 45b21dd386..dc42ad4b07 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -89,6 +89,11 @@ matterManufacturer: vendorId: 0x120B productId: 0x1060 deviceProfileName: temperature-humidity-battery + - id: "4619/4129" + deviceLabel: Photoelectric Smoke Alarm + vendorId: 0x120B + productId: 0x1021 + deviceProfileName: smoke-battery # Ikea - id: "4476/32773" deviceLabel: TIMMERFLOTTE Temperature/Humidity Sensor From 84d83ed72fa547d8ce373f4b145f02127fe00527 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Mon, 28 Sep 2026 13:34:57 -0500 Subject: [PATCH 533/538] Matter Camera: Exclude `audioVolume` on unusable volume range (#3242) * CHAD-19242: Exclude audioVolume for unusable range * Keep the capability as-is when ranges are unknown * Update gating for min/max attr subscription, fix log * Guard against divide-by-zero * Track the raw value directly --- .../camera_handlers/attribute_handlers.lua | 32 +- .../camera_handlers/capability_handlers.lua | 12 +- .../camera_utils/device_configuration.lua | 21 +- .../sub_drivers/camera/camera_utils/utils.lua | 22 +- .../src/test/test_matter_camera.lua | 348 ++++++++++++++++++ 5 files changed, 413 insertions(+), 22 deletions(-) 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 index 075f9af600..0b08bf27b0 100644 --- 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 @@ -57,10 +57,14 @@ 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 max_volume = camera_utils.get_field_for_component(device, camera_fields.MAX_VOLUME_LEVEL, component) or camera_fields.ABS_VOL_MAX + local min_volume = camera_utils.get_field_for_component(device, camera_fields.MIN_VOLUME_LEVEL, component) or camera_fields.ABS_VOL_MIN local limited_range = max_volume - min_volume + if limited_range <= 0 then + device:emit_event_for_endpoint(ib, capabilities.audioVolume.volume(0)) + return + end + -- Convert from [min_volume, max_volume] to [0, 100] before emitting capability 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 @@ -68,23 +72,33 @@ 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 + local min_volume = camera_utils.get_field_for_component(device, camera_fields.MIN_VOLUME_LEVEL, component) + -- min == max is a valid (non-adjustable) range per the Matter spec, so only max < min is malformed; + -- an out-of-bounds value is clamped, but a malformed relationship is only logged and left as-is, + -- since volume_level_handler already guards against a non-positive range. + if max_volume > camera_fields.ABS_VOL_MAX 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 + elseif 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)) end - device:set_field(camera_fields.MAX_VOLUME_LEVEL .. "_" .. component, max_volume) + camera_utils.set_field_for_component(device, camera_fields.MAX_VOLUME_LEVEL, component, max_volume) + camera_cfg.reconcile_profile_and_capabilities(device) 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 + local max_volume = camera_utils.get_field_for_component(device, camera_fields.MAX_VOLUME_LEVEL, component) + -- See max_volume_level_handler: min == max is valid, only min > max is malformed and only logged. + if min_volume < camera_fields.ABS_VOL_MIN 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 + elseif 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)) end - device:set_field(camera_fields.MIN_VOLUME_LEVEL .. "_" .. component, min_volume) + camera_utils.set_field_for_component(device, camera_fields.MIN_VOLUME_LEVEL, component, min_volume) + camera_cfg.reconcile_profile_and_capabilities(device) end function CameraAttributeHandlers.status_light_enabled_handler(driver, device, ib, response) 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 index ef52efcdbe..ebf0411f99 100644 --- 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 @@ -59,8 +59,8 @@ 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 + local max_volume = camera_utils.get_field_for_component(device, camera_fields.MAX_VOLUME_LEVEL, cmd.component) or camera_fields.ABS_VOL_MAX + local min_volume = camera_utils.get_field_for_component(device, 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) @@ -75,8 +75,8 @@ 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 max_volume = camera_utils.get_field_for_component(device, camera_fields.MAX_VOLUME_LEVEL, cmd.component) or camera_fields.ABS_VOL_MAX + local min_volume = camera_utils.get_field_for_component(device, 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 @@ -91,8 +91,8 @@ 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 max_volume = camera_utils.get_field_for_component(device, camera_fields.MAX_VOLUME_LEVEL, cmd.component) or camera_fields.ABS_VOL_MAX + local min_volume = camera_utils.get_field_for_component(device, 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 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 index 282d33677f..8a5db7c5b5 100644 --- 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 @@ -41,6 +41,19 @@ local function set_hard_privacy_mode_presence(device, hard_privacy_mode_present) device:set_field(camera_fields.HARD_PRIVACY_MODE_PRESENT, hard_privacy_mode_present == true, { persist = true }) end +--- Leaves audioVolume as-is if the min/max levels aren't both known yet instead of guessing; +--- otherwise requires max > min to include it. +--- @param component string a profile component name +--- @return should_include boolean whether audioVolume belongs on the component argument. +local function should_include_volume_capability(device, component) + local min_volume = camera_utils.get_field_for_component(device, camera_fields.MIN_VOLUME_LEVEL, component) + local max_volume = camera_utils.get_field_for_component(device, camera_fields.MAX_VOLUME_LEVEL, component) + if min_volume == nil or max_volume == nil then + return device:supports_capability(capabilities.audioVolume, component) + end + return max_volume > min_volume +end + local function build_webrtc_supported_features() return { bundle = true, @@ -258,7 +271,9 @@ function CameraDeviceConfiguration.match_profile(device) table.insert(main_component_capabilities, capabilities.audioRecording.ID) end table.insert(microphone_component_capabilities, capabilities.audioMute.ID) - table.insert(microphone_component_capabilities, capabilities.audioVolume.ID) + if should_include_volume_capability(device, camera_fields.profile_components.microphone) then + table.insert(microphone_component_capabilities, capabilities.audioVolume.ID) + end end if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.SNAPSHOT) then table.insert(main_component_capabilities, capabilities.imageCapture.ID) @@ -268,7 +283,9 @@ function CameraDeviceConfiguration.match_profile(device) 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) + if should_include_volume_capability(device, camera_fields.profile_components.speaker) then + table.insert(speaker_component_capabilities, capabilities.audioVolume.ID) + end end if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.IMAGE_CONTROL) then table.insert(main_component_capabilities, capabilities.imageControl.ID) 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 index 78792b94b3..5cc7dee498 100644 --- 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 @@ -82,6 +82,14 @@ function CameraUtils.feature_supported(device, cluster_id, feature_flag) return #device:get_endpoints(cluster_id, { feature_bitmap = feature_flag }) > 0 end +function CameraUtils.get_field_for_component(device, field, component) + return device:get_field(string.format("%s_%s", field, component)) +end + +function CameraUtils.set_field_for_component(device, field, component, value, additional_params) + device:set_field(string.format("%s_%s", field, component), value, additional_params) +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 @@ -212,18 +220,22 @@ function CameraUtils.subscribe(device) clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMax, clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMin }, + -- SpeakerMaxLevel/MinLevel and MicrophoneMaxLevel/MinLevel are subscribed alongside audioMute + -- (unconditional on the AUDIO/SPEAKER feature) rather than audioVolume, since audioVolume's own + -- presence in the profile depends on first discovering these values -- gating their subscription + -- on audioVolume already being present would make that discovery impossible. [capabilities.audioMute.ID] = { clusters.CameraAvStreamManagement.attributes.SpeakerMuted, - clusters.CameraAvStreamManagement.attributes.MicrophoneMuted - }, - [capabilities.audioVolume.ID] = { - clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel, + clusters.CameraAvStreamManagement.attributes.MicrophoneMuted, clusters.CameraAvStreamManagement.attributes.SpeakerMaxLevel, clusters.CameraAvStreamManagement.attributes.SpeakerMinLevel, - clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel, clusters.CameraAvStreamManagement.attributes.MicrophoneMaxLevel, clusters.CameraAvStreamManagement.attributes.MicrophoneMinLevel }, + [capabilities.audioVolume.ID] = { + clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel, + clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel + }, [capabilities.mode.ID] = { clusters.CameraAvStreamManagement.attributes.StatusLightBrightness }, diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index e1467edecf..6f5b1251eb 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -420,6 +420,15 @@ local additional_subscribed_attributes = { clusters.Switch.server.events.MultiPressComplete } +-- audioVolume is only added to the speaker/microphone components once a usable (max > min) volume +-- range has been reported; seed a usable range directly so unrelated tests don't have to simulate it. +local function seed_usable_volume_range(device) + device:set_field(camera_fields.MIN_VOLUME_LEVEL .. "_" .. camera_fields.profile_components.speaker, 0) + device:set_field(camera_fields.MAX_VOLUME_LEVEL .. "_" .. camera_fields.profile_components.speaker, 200) + device:set_field(camera_fields.MIN_VOLUME_LEVEL .. "_" .. camera_fields.profile_components.microphone, 0) + device:set_field(camera_fields.MAX_VOLUME_LEVEL .. "_" .. camera_fields.profile_components.microphone, 200) +end + local expected_metadata = { optional_component_capabilities = { { @@ -473,6 +482,7 @@ local expected_metadata = { } local function update_device_profile() + seed_usable_volume_range(mock_device) test.socket.matter:__queue_receive({ mock_device.id, clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device, CAMERA_EP, { @@ -712,6 +722,7 @@ end test.register_coroutine_test( "Software version change should trigger camera reprofiling when camera endpoint is present", function() + seed_usable_volume_range(mock_device) test.socket.device_lifecycle:__queue_receive( mock_device:generate_info_changed({ matter_version = { hardware = 1, software = 2 } }) ) @@ -965,6 +976,293 @@ test.register_coroutine_test( } ) +local function build_fake_device_with_volume_ranges(raw_levels) + return { + profile = { components = {} }, + endpoints = { + { + endpoint_id = CAMERA_EP, + device_types = { + {device_type_id = 0x0142, device_type_revision = 1} -- Camera + }, + clusters = { + { + cluster_id = clusters.CameraAvStreamManagement.ID, + feature_map = clusters.CameraAvStreamManagement.types.Feature.AUDIO | + clusters.CameraAvStreamManagement.types.Feature.SPEAKER, + cluster_type = "SERVER" + } + } + } + }, + get_field = function(_, field) return raw_levels[field] end, + get_endpoints = function() return {} end, + supports_capability = function() return false end, + try_update_metadata = function(self, metadata) self.updated_metadata = metadata end, + } +end + +local function component_capabilities(updated_metadata, component_name) + for _, component in ipairs(updated_metadata.optional_component_capabilities) do + if component[1] == component_name then + return component[2] + end + end + return {} +end + +local function list_contains(list, value) + for _, v in ipairs(list) do + if v == value then return true end + end + return false +end + +test.register_coroutine_test( + "audioVolume should be excluded from speaker and microphone when the volume range is unusable or unknown", + function() + local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" + + -- microphone reports an unusable range (min >= max); speaker hasn't reported a range at all yet + local fake_device = build_fake_device_with_volume_ranges({ + [camera_fields.MIN_VOLUME_LEVEL .. "_microphone"] = 100, + [camera_fields.MAX_VOLUME_LEVEL .. "_microphone"] = 100, + }) + + camera_cfg.match_profile(fake_device) + + assert(fake_device.updated_metadata ~= nil, "profile update should be requested") + local speaker_capabilities = component_capabilities(fake_device.updated_metadata, "speaker") + local microphone_capabilities = component_capabilities(fake_device.updated_metadata, "microphone") + + assert(list_contains(speaker_capabilities, capabilities.audioMute.ID), "audioMute should still be present on speaker") + assert(not list_contains(speaker_capabilities, capabilities.audioVolume.ID), "audioVolume should be excluded from speaker when its range is unknown") + assert(list_contains(microphone_capabilities, capabilities.audioMute.ID), "audioMute should still be present on microphone") + assert(not list_contains(microphone_capabilities, capabilities.audioVolume.ID), "audioVolume should be excluded from microphone when min >= max") + end, + { + min_api_version = 14 + } +) + +test.register_coroutine_test( + "audioVolume should be included on speaker and microphone when the volume range is usable", + function() + local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" + + local fake_device = build_fake_device_with_volume_ranges({ + [camera_fields.MIN_VOLUME_LEVEL .. "_speaker"] = 0, + [camera_fields.MAX_VOLUME_LEVEL .. "_speaker"] = 200, + [camera_fields.MIN_VOLUME_LEVEL .. "_microphone"] = 0, + [camera_fields.MAX_VOLUME_LEVEL .. "_microphone"] = 200, + }) + + camera_cfg.match_profile(fake_device) + + assert(fake_device.updated_metadata ~= nil, "profile update should be requested") + local speaker_capabilities = component_capabilities(fake_device.updated_metadata, "speaker") + local microphone_capabilities = component_capabilities(fake_device.updated_metadata, "microphone") + + assert(list_contains(speaker_capabilities, capabilities.audioVolume.ID), "audioVolume should be included on speaker when max > min") + assert(list_contains(microphone_capabilities, capabilities.audioVolume.ID), "audioVolume should be included on microphone when max > min") + end, + { + min_api_version = 14 + } +) + +test.register_coroutine_test( + "audioVolume should be preserved on a component whose volume range isn't fully known yet", + function() + local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" + + -- Speaker already has audioVolume from a prior match; its raw min/max aren't in raw_levels (nil), + -- so it should be carried over as-is rather than being dropped for lack of information. + local existing_components = { + speaker = { capabilities = { audioVolume = { id = capabilities.audioVolume.ID } } } + } + local raw_levels = { + [camera_fields.MIN_VOLUME_LEVEL .. "_microphone"] = 100, + [camera_fields.MAX_VOLUME_LEVEL .. "_microphone"] = 100, -- unusable; forces a real profile change so we can inspect it + } + local fake_device = { + profile = { components = existing_components }, + endpoints = { + { + endpoint_id = CAMERA_EP, + device_types = { + {device_type_id = 0x0142, device_type_revision = 1} -- Camera + }, + clusters = { + { + cluster_id = clusters.CameraAvStreamManagement.ID, + feature_map = clusters.CameraAvStreamManagement.types.Feature.AUDIO | + clusters.CameraAvStreamManagement.types.Feature.SPEAKER, + cluster_type = "SERVER" + } + } + } + }, + get_field = function(_, field) return raw_levels[field] end, + get_endpoints = function() return {} end, + supports_capability = function(_, capability, component) + local comp = existing_components[component] + if not comp then return false end + for _, cap in pairs(comp.capabilities) do + if cap.id == capability.ID then return true end + end + return false + end, + try_update_metadata = function(self, metadata) self.updated_metadata = metadata end, + } + + camera_cfg.match_profile(fake_device) + + assert(fake_device.updated_metadata ~= nil, "profile update should be requested") + local speaker_capabilities = component_capabilities(fake_device.updated_metadata, "speaker") + local microphone_capabilities = component_capabilities(fake_device.updated_metadata, "microphone") + + assert(list_contains(speaker_capabilities, capabilities.audioVolume.ID), "audioVolume should be preserved on speaker while its range is unknown") + assert(list_contains(microphone_capabilities, capabilities.audioMute.ID), "audioMute should still be present on microphone") + assert(not list_contains(microphone_capabilities, capabilities.audioVolume.ID), "audioVolume should be excluded from microphone when min >= max") + end, + { + min_api_version = 14 + } +) + +test.register_coroutine_test( + "Speaker audioVolume should be removed from the profile when its volume range becomes unusable", + function() + update_device_profile() + test.wait_for_events() + + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.SpeakerMaxLevel:build_test_report_data(mock_device, CAMERA_EP, 0) + }) + + local updated_expected_metadata = { + optional_component_capabilities = { + { "main", + { "videoCapture2", "cameraViewportSettings", "videoStreamSettings", "localMediaStorage", "audioRecording", + "cameraPrivacyMode", "imageControl", "hdr", "nightVision", "mechanicalPanTiltZoom", "zoneManagement", + "webrtc", "motionSensor", "sounds" } + }, + { "statusLed", + { "switch", "mode" } + }, + { "speaker", + { "audioMute" } -- audioVolume removed: reported max (0) is no longer greater than the known min (0) + }, + { "microphone", + { "audioMute", "audioVolume" } + }, + { "doorbell", + { "button" } + } + }, + profile = "camera" + } + mock_device:expect_metadata_update(updated_expected_metadata) + test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)}) + end, + { + min_api_version = 14 + } +) + +test.register_coroutine_test( + "Speaker/Microphone volume range attributes should stay subscribed even when audioVolume isn't granted yet", + function() + -- Fresh pairing: no volume range has been reported yet, so audioVolume shouldn't be granted on + -- the very first profile match. + 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) + }) + }) + local first_pass_metadata = { + optional_component_capabilities = { + { "main", + { "videoCapture2", "cameraViewportSettings", "videoStreamSettings", "localMediaStorage", "audioRecording", + "cameraPrivacyMode", "imageControl", "hdr", "nightVision", "mechanicalPanTiltZoom", "zoneManagement", + "webrtc", "motionSensor", "sounds" } + }, + { "statusLed", { "switch", "mode" } }, + { "speaker", { "audioMute" } }, + { "microphone", { "audioMute" } }, + { "doorbell", { "button" } } + }, + profile = "camera" + } + mock_device:expect_metadata_update(first_pass_metadata) + test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)}) + test.wait_for_events() + + local first_pass_profile = t_utils.get_profile_definition( + "camera.yml", {enabled_optional_capabilities = first_pass_metadata.optional_component_capabilities} + ) + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ profile = first_pass_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"} + )) + ) + + -- audioVolume isn't active yet, so its current-value attributes shouldn't be (re-)subscribed, but + -- the min/max discovery attributes -- now bundled with audioMute -- must still be. Without that, + -- the driver could never learn the range needed to decide whether audioVolume belongs at all. + for _, attr in ipairs(additional_subscribed_attributes) do + if attr ~= clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel and + attr ~= clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel then + subscribe_request:merge(attr:subscribe(mock_device)) + end + 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)}) + end, + { + min_api_version = 14 + } +) + test.register_coroutine_test( "Camera privacy mode supportedAttributes/supportedCommands should only expose hardPrivacyMode when PRIV feature is absent", function() @@ -1282,6 +1580,56 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "A volume level report racing a zero-width range update should not divide by zero", + function() + update_device_profile() + test.wait_for_events() + + -- Narrow the range to min == max. This drops audioVolume from the profile, but the internal + -- MAX/MIN_VOLUME_LEVEL fields used for normalization are updated synchronously, before the + -- platform round-trips the narrowed profile back down. + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.SpeakerMaxLevel:build_test_report_data(mock_device, CAMERA_EP, 100) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.SpeakerMinLevel:build_test_report_data(mock_device, CAMERA_EP, 100) + }) + local updated_expected_metadata = { + optional_component_capabilities = { + { "main", + { "videoCapture2", "cameraViewportSettings", "videoStreamSettings", "localMediaStorage", "audioRecording", + "cameraPrivacyMode", "imageControl", "hdr", "nightVision", "mechanicalPanTiltZoom", "zoneManagement", + "webrtc", "motionSensor", "sounds" } + }, + { "statusLed", { "switch", "mode" } }, + { "speaker", { "audioMute" } }, -- audioVolume dropped: the range is now zero-width (100 == 100) + { "microphone", { "audioMute", "audioVolume" } }, + { "doorbell", { "button" } } + }, + profile = "camera" + } + mock_device:expect_metadata_update(updated_expected_metadata) + test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)}) + test.wait_for_events() + + -- A late SpeakerVolumeLevel report can still arrive before the narrowed profile comes back down. + -- It should report a fixed value instead of dividing by (max - min) == 0. + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel:build_test_report_data(mock_device, CAMERA_EP, 100) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("speaker", capabilities.audioVolume.volume(0)) + ) + end, + { + min_api_version = 14 + } +) + test.register_coroutine_test( "Status Light Enabled reports should generate appropriate events", function() From 6773dc048c9dc2c111221e6897f5f0e575f341d5 Mon Sep 17 00:00:00 2001 From: Konrad K <33450498+KKlimczukS@users.noreply.github.com> Date: Tue, 29 Sep 2026 09:51:04 +0200 Subject: [PATCH 534/538] [WWSTCERT-13880, WWSTCERT-13884, WWSTCERT-13888] adds new fingerprints for Legrand/BTicino switches (#3250) * adds new fingerprint for Legrand Micromodule switch * adds new fingerprint for Legrand/BTicino Light switch * adds new fingerprint for Legrand Connected outlet --- .../SmartThings/zigbee-switch/fingerprints.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index bb2ce5f027..613c471dcb 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2542,6 +2542,22 @@ zigbeeManufacturer: manufacturer: FIRSTLED model: DC2DC12MiV1 deviceProfileName: light-color-temp-time-restore +#Legrand/BTicino + - id: Legrand/Micromodule switch + deviceLabel: Connected Lighting Micromodule + manufacturer: Legrand + model: Micromodule switch + deviceProfileName: basic-switch + - id: Legrand/Light switch with neutral + deviceLabel: Connected Switch with Neutral + manufacturer: Legrand + model: Light switch with neutral + deviceProfileName: basic-switch + - id: Legrand/Connected outlet + deviceLabel: Connected Socket + manufacturer: Legrand + model: Connected outlet + deviceProfileName: switch-power zigbeeGeneric: - id: "genericSwitch" deviceLabel: Zigbee Switch From 1bc6ae9fc7a80b370cb43c5315853f6eff817a0b Mon Sep 17 00:00:00 2001 From: "Jan Jakubiszyn/SmartThings Development (BJ.jakubiszynS) /SRPOL/Engineer/Samsung Electronics" Date: Wed, 23 Sep 2026 17:41:53 +0200 Subject: [PATCH 535/538] Fix: Reorder delay and increase to 3 seconds --- .../src/sub_drivers/hager/init.lua | 8 +- .../src/test/test_hager_waasys.lua | 376 ++++++++++++++++-- 2 files changed, 343 insertions(+), 41 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua index db8cfe67d4..8df254bde8 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua @@ -189,10 +189,10 @@ local function info_changed(driver, device, event, args) end if device.profile.id ~= args.old_st_store.profile.id or device.network_type == device_lib.NETWORK_TYPE_CHILD then - local parent = device:get_parent_device() - local matter_device = get_matter_device(parent) - local map = {} - device.thread:call_with_delay(2, function() + device.thread:call_with_delay(3, function() + local parent = device:get_parent_device() + local matter_device = get_matter_device(parent) + local map = {} if device:supports_capability(capabilities.button) then local button_eps = parent:get_field(BUTTON_EPS) local clean_eps = {} diff --git a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua index 4aeee85023..666550dfcf 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_hager_waasys.lua @@ -17,6 +17,7 @@ end local MATTER_DEVICE_ID = "MATTER_DEVICE_ID" local PARENT_ID = "PARENT_ID" local BUTTON_EPS = "__button_eps" +local MAIN_WC_EP = "__main_wc_ep" local function create_parent_device(product_id) return test.mock_device.build_test_matter_device({ @@ -434,6 +435,80 @@ local function create_hager_pir_device(profile_name, parent) }) end +-- Create Hager 1G device with ONLY a WindowCovering endpoint (12) - no button or occupancy endpoints +local function create_hager_1g_window_covering(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 1G Window Covering", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + type = "MATTER", + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0005, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 12, + clusters = { + { + cluster_id = clusters.WindowCovering.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.WindowCovering.types.Feature.LIFT | + clusters.WindowCovering.types.Feature.POSITION_AWARE_LIFT | + clusters.WindowCovering.types.Feature.ABSOLUTE_POSITION, + attributes = { + [clusters.WindowCovering.attributes.OperationalStatus.ID] = 0x00, + [clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID] = 0x0000, + } + } + }, + device_types = { { device_type_id = 0x0202, device_type_revision = 1 } } + }, + } + }) +end + +-- Create Hager 1G Relay device with endpoint 3 (OnOff cluster) +local function create_hager_1g_relay(profile_name, parent) + return test.mock_device.build_test_matter_device({ + label = "Hager G2 1G Relay", + profile = t_utils.get_profile_definition(profile_name .. ".yml"), + type = "MATTER", + manufacturer_info = { + vendor_id = 0x1285, + product_id = 0x0005, + }, + parent_device_id = parent.id, + endpoints = { + { + endpoint_id = 0, + clusters = { { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" } }, + device_types = { { device_type_id = 0x0016, device_type_revision = 1 } } + }, + { + endpoint_id = 3, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + attributes = { + [clusters.OnOff.attributes.OnOff.ID] = false + } + } + }, + device_types = { { device_type_id = 0x0100, device_type_revision = 1 } } + }, + } + }) +end + local function add_parent_device(parent) test.mock_device.add_test_device(parent) test.socket.device_lifecycle:__queue_receive({ parent.id, "added" }) @@ -496,8 +571,8 @@ end local function initiate_info_changed(device, profile, parent) test.socket.device_lifecycle:__queue_receive(device:generate_info_changed({ profile = { id = profile } })) - test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") - test.mock_time.advance_time(2) + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + test.mock_time.advance_time(3) if parent ~= nil then test.socket.device_lifecycle:__queue_receive(parent:generate_info_changed({})) end @@ -515,6 +590,50 @@ local function configure_parent(device) test.mock_time.advance_time(3) end +-- Create window covering child device on EP12 via PartsList and DeviceTypeList reports +local function announce_window_covering_child(host, matter_device) + test.socket.matter:__queue_receive({ + host.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(host, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + data_types.Uint16(12), + })) + }) + for _, ep in ipairs({ 8, 9, 12 }) do + test.socket.matter:__expect_send({ + host.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(host, ep) + }) + end + + test.socket.matter:__queue_receive({ + host.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(host, 12, data_types.Array { + { + device_type = data_types.Uint32(514), + revision = data_types.Uint16(1) + } + }) + }) + host:expect_device_create({ + type = "EDGE_CHILD", + label = "Hager G2 4x Button 1", + profile = "window-covering", + parent_device_id = host.id, + parent_assigned_child_key = "12" + }) + + local child_wc = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("window-covering.yml"), + device_network_id = string.format("%s:12", host.id), + parent_device_id = host.id, + parent_assigned_child_key = "12" + }) + test.mock_device.add_test_device(child_wc) + return child_wc +end + local parent = create_parent_device(0x0006) -- 2G button product local parent_1g = create_parent_device(0x0005) -- 1G button product local parent_pir = create_parent_device(0x0007) -- PIR product @@ -596,9 +715,9 @@ test.register_coroutine_test("Test: 4-Button Device Detection - Profile Changes }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Button Event Handling - Pushed, Double Press, and Held Events on 4-Button Device", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -803,9 +922,9 @@ test.register_coroutine_test("Test: Button Event Handling - Pushed, Double Press }) test.socket.capability:__expect_send(matter_device:generate_test_message("button4", capabilities.button.button.held({ state_change = true }))) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Device Type Handler - Handles Button (Type 15) and OnOff (Type 256) Device Types with Child Creation", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -899,9 +1018,9 @@ test.register_coroutine_test("Test: Device Type Handler - Handles Button (Type 1 clusters.OnOff.attributes.OnOff:subscribe(parent, nil) }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: 2G Relay - Profile Changes Between light-binary and 2-button Based On Endpoint Availability", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1144,9 +1263,9 @@ test.register_coroutine_test("Test: 2G Relay - Profile Changes Between light-bin parent_assigned_child_key = "4" }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Dimmer Device - Child Creation for Dimmable Endpoint with Button Support", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1243,9 +1362,9 @@ test.register_coroutine_test("Test: Dimmer Device - Child Creation for Dimmable }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: 1G Dimmer - Initialization and Profile Update to light-level", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1255,9 +1374,9 @@ test.register_coroutine_test("Test: 1G Dimmer - Initialization and Profile Updat test.wait_for_events() configure_parent(parent) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: 1G Dimmer - Host Commands and Level Control Capabilities", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1330,9 +1449,9 @@ test.register_coroutine_test("Test: 1G Dimmer - Host Commands and Level Control clusters.LevelControl.server.commands.MoveToLevelWithOnOff(dimmer, 4, 51, nil, 0, 0) }) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: PIR Device - Initialization with Motion and Illuminance Capabilities", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1343,9 +1462,9 @@ test.register_coroutine_test("Test: PIR Device - Initialization with Motion and configure_parent(parent_pir) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: PIR Device - Complete Functionality with Motion, Illuminance, and Dimmer Support", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1417,6 +1536,7 @@ test.register_coroutine_test("Test: PIR Device - Complete Functionality with Mot }) test.wait_for_events() initiate_info_changed(pir_device, "motion-illuminance") + test.socket.matter:__expect_send({ parent_pir.id, cluster_base.subscribe(parent_pir, nil, clusters.IlluminanceMeasurement.ID, clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID, nil) @@ -1433,8 +1553,8 @@ test.register_coroutine_test("Test: PIR Device - Complete Functionality with Mot parent_device_id = parent_pir.id, parent_assigned_child_key = "3" }) - test.mock_device.add_test_device(child_dimmer) + initiate_info_changed(child_dimmer, "light-level", parent_pir) test.socket.matter:__expect_send({ parent_pir.id, @@ -1453,6 +1573,7 @@ test.register_coroutine_test("Test: PIR Device - Complete Functionality with Mot cluster_base.subscribe(parent_pir, nil, clusters.LevelControl.ID, clusters.LevelControl.attributes.MinLevel.ID, nil) }) test.wait_for_events() + --Verify motion detected event test.socket.matter:__queue_receive({ parent_pir.id, @@ -1476,13 +1597,14 @@ test.register_coroutine_test("Test: PIR Device - Complete Functionality with Mot parent_pir.id, clusters.OnOff.commands.On(parent_pir, 3) }) + -- Verify on state via attribute report test.socket.matter:__queue_receive({ parent_pir.id, clusters.OnOff.attributes.OnOff:build_test_report_data(parent_pir, 3, true) }) - test.socket.capability:__expect_send(child_dimmer:generate_test_message("main", capabilities.switch.switch.on())) parent_pir.expect_native_attr_handler_registration(parent_pir, "switch", "switch") + test.socket.capability:__expect_send(child_dimmer:generate_test_message("main", capabilities.switch.switch.on())) -- Set dimmer level to 50% test.socket.capability:__queue_receive({ child_dimmer.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 50 } } }) @@ -1500,9 +1622,9 @@ test.register_coroutine_test("Test: PIR Device - Complete Functionality with Mot test.socket.capability:__expect_send(child_dimmer:generate_test_message("main", capabilities.switchLevel.level(50))) parent_pir.expect_native_attr_handler_registration(parent_pir, "switchLevel", "level") end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile with Window Covering Child Device", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1561,7 +1683,7 @@ test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile }) test.mock_device.add_test_device(child_wc) - initiate_info_changed(child_wc, "window-covering", parent) + initiate_info_changed(child_wc, "window-covering",parent) test.socket.matter:__expect_send({ parent.id, @@ -1641,9 +1763,9 @@ test.register_coroutine_test("Test: Host with Window Covering - 2-Button Profile test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closed())) end, - { - min_api_version = 15 - }) + { + min_api_version = 15 + }) test.register_coroutine_test("Test: Window Covering - Preference Changes for Reverse Polarity and Preset Position", function() test.socket.matter:__set_channel_ordering("relaxed") @@ -1810,8 +1932,188 @@ test.register_coroutine_test("Test: Window Covering - Preference Changes for Rev cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) }) end, - { - min_api_version = 15 + { + min_api_version = 15 + }) + +test.register_coroutine_test("Test: 1G Window Covering - window-covering Profile and No Duplicate Child Device", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent_1g) + local wc_1g = create_hager_1g_window_covering("window-covering", parent_1g) + add_matter_device(wc_1g, parent_1g, "window-covering") + test.wait_for_events() + configure_parent(parent_1g) + test.wait_for_events() + + local main_wc_ep = wc_1g:get_field(MAIN_WC_EP) + assert(main_wc_ep == 12, "MAIN_WC_EP is set to 12") + + -- EP12 reported again → no second child created, MAIN_WC_EP already set + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_1g, 2, data_types.Array({ + data_types.Uint16(12), + })) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 12) + }) + test.wait_for_events() + + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_1g, 12, data_types.Array { + { + device_type = data_types.Uint32(514), + revision = data_types.Uint16(1) + } + }) + }) + test.wait_for_events() +end, + { + min_api_version = 15 + }) + +test.register_coroutine_test("Test: 1G Relay - Profile Changes Between light-binary and 2-button Based On EP3 Availability", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent_1g) + local relay_1g = create_hager_1g_relay("light-binary", parent_1g) + add_switch_matter_device(relay_1g, parent_1g) + test.wait_for_events() + configure_parent(parent_1g) + test.wait_for_events() + + -- Scenario 1: EP3 present → light-binary profile + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_1g, 2, data_types.Array({ + data_types.Uint16(3), + })) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 3) + }) + relay_1g:expect_metadata_update({ profile = "light-binary" }) + test.wait_for_events() + + -- EP3 is device type 256 (OnOff) - no child device created for 1G + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:build_test_report_data(parent_1g, 3, data_types.Array { + { + device_type = data_types.Uint32(256), + revision = data_types.Uint16(1) + } }) + }) + test.wait_for_events() + + -- Scenario 2: EP3 removed, buttons 8 & 9 present → 2-button profile + test.socket.matter:__queue_receive({ + parent_1g.id, + clusters.Descriptor.attributes.PartsList:build_test_report_data(parent_1g, 2, data_types.Array({ + data_types.Uint16(8), + data_types.Uint16(9), + })) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 8) + }) + test.socket.matter:__expect_send({ + parent_1g.id, + clusters.Descriptor.attributes.DeviceTypeList:subscribe(parent_1g, 9) + }) + relay_1g:expect_metadata_update({ profile = "2-button" }) + test.wait_for_events() +end, + { + min_api_version = 15 + }) + +test.register_coroutine_test("Test: Window Covering - Position Attribute Reports With Reverse Polarity", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local window_covering_device = create_matter_device_with_window("2-button", parent) + add_matter_device(window_covering_device, parent, "2-button") + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + local child_wc = announce_window_covering_child(parent) + test.wait_for_events() + + -- Attribute reports are addressed to the host, so reverse is set on the host + test.socket.device_lifecycle():__queue_receive( + parent:generate_info_changed({ preferences = { reverse = "true" } }) + ) + test.wait_for_events() + + -- Verify 50% position via attribute report - direction independent + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 5000) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50))) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.partially_open())) + test.wait_for_events() + + -- Verify 100% position via attribute report - with reverse true, this should report closed + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 0) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100))) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.closed())) + test.wait_for_events() + + -- Verify 0% position via attribute report - with reverse true, this should report open + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data(parent, 12, 10000) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0))) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.open())) + test.wait_for_events() +end, + { + min_api_version = 15 + }) + +test.register_coroutine_test("Test: Window Covering - Unrecognised OperationalStatus Reports Unknown State", function() + test.socket.matter:__set_channel_ordering("relaxed") + add_parent_device(parent) + local window_covering_device = create_matter_device_with_window("2-button", parent) + add_matter_device(window_covering_device, parent, "2-button") + test.wait_for_events() + configure_parent(parent) + test.wait_for_events() + + local child_wc = announce_window_covering_child(parent) + initiate_info_changed(child_wc, "window-covering", parent) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.OperationalStatus.ID, nil) + }) + test.socket.matter:__expect_send({ + parent.id, + cluster_base.subscribe(parent, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil) + }) + test.wait_for_events() + + -- OperationalStatus 0x03 is neither opening (1) nor closing (2) + test.socket.matter:__queue_receive({ + parent.id, + clusters.WindowCovering.attributes.OperationalStatus:build_test_report_data(parent, 12, 0x03) + }) + test.socket.capability:__expect_send(child_wc:generate_test_message("main", capabilities.windowShade.windowShade.unknown())) + test.wait_for_events() +end, + { + min_api_version = 15 + }) test.run_registered_tests() From 7ae09375170b5290ce1e194b81a57e2ef5c6ef1e Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Fri, 2 Oct 2026 10:01:04 -0500 Subject: [PATCH 536/538] WWSTCERT-13927 Linkind Smart Outdoor Plug (#3256) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index fd2a35c321..6dcbffe532 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -241,6 +241,11 @@ matterManufacturer: vendorId: 0x1396 productId: 0x11DE deviceProfileName: light-color-level + - id: "5014/4713" + deviceLabel: Linkind Smart Outdoor Plug + vendorId: 0x1396 + productId: 0x1269 + deviceProfileName: plug-binary #Bosch Smart Home - id: "4617/12310" deviceLabel: Plug Compact [M] From 43f47501b217336d17ed2fc92b64d08971f305ce Mon Sep 17 00:00:00 2001 From: Chris Baumler Date: Mon, 5 Oct 2026 10:45:20 -0500 Subject: [PATCH 537/538] WWSTCERT-13921 Linkind Smart Light Bulb (#3258) --- .../matter-switch/fingerprints.yml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 6dcbffe532..a889663b92 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -241,6 +241,36 @@ matterManufacturer: vendorId: 0x1396 productId: 0x11DE deviceProfileName: light-color-level + - id: "5014/4224" + deviceLabel: Linkind Smart Light Bulb + vendorId: 0x1396 + productId: 0x1080 + deviceProfileName: light-color-level + - id: "5014/4223" + deviceLabel: Linkind Smart Light Bulb + vendorId: 0x1396 + productId: 0x107F + deviceProfileName: light-color-level + - id: "5014/4227" + deviceLabel: Linkind Smart Light Bulb + vendorId: 0x1396 + productId: 0x1083 + deviceProfileName: light-color-level + - id: "5014/4218" + deviceLabel: Linkind Smart Filament Bulb + vendorId: 0x1396 + productId: 0x107A + deviceProfileName: light-color-level + - id: "5014/4220" + deviceLabel: Linkind Smart Filament Bulb + vendorId: 0x1396 + productId: 0x107C + deviceProfileName: light-color-level + - id: "5014/4221" + deviceLabel: Linkind Smart Filament Bulb + vendorId: 0x1396 + productId: 0x107D + deviceProfileName: light-color-level - id: "5014/4713" deviceLabel: Linkind Smart Outdoor Plug vendorId: 0x1396 From b1d3b53c571ea36676b59fba7d81cc5de7242f51 Mon Sep 17 00:00:00 2001 From: quannan <96817118+QuAnnan2019@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:42:19 +0800 Subject: [PATCH 538/538] [WWSTCERT-13849] feat(zigbee): add SONOFF TRV-ZBT thermostat (#3204) * feat(zigbee): add SONOFF TRV-ZBT thermostat * fix(zigbee): support legacy thermostat commands * fix(zigbee): align SONOFF setpoint precision * test(zigbee): cover SONOFF setpoint precision * fix(zigbee): address SONOFF TRV-ZBT review feedback * fix(zigbee): address SONOFF pairing review feedback * fix(zigbee): rely on SONOFF mode reporting * fix(zigbee): use default SONOFF battery reporting * refactor(zigbee): use framework battery configuration * fix(zigbee): sync SONOFF TRV-ZBT setpoint limits --- .../zigbee-thermostat/fingerprints.yml | 5 + .../profiles/thermostat-sonoff-trv-zbt.yml | 38 ++ .../src/sonoff/can_handle.lua | 12 + .../zigbee-thermostat/src/sonoff/init.lua | 321 +++++++++++++ .../zigbee-thermostat/src/sub_drivers.lua | 1 + .../src/test/test_sonoff_trv_zbt.lua | 429 ++++++++++++++++++ 6 files changed, 806 insertions(+) create mode 100644 drivers/SmartThings/zigbee-thermostat/profiles/thermostat-sonoff-trv-zbt.yml create mode 100644 drivers/SmartThings/zigbee-thermostat/src/sonoff/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/sonoff/init.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/test/test_sonoff_trv_zbt.lua diff --git a/drivers/SmartThings/zigbee-thermostat/fingerprints.yml b/drivers/SmartThings/zigbee-thermostat/fingerprints.yml index efa89be552..ed6afd59b5 100644 --- a/drivers/SmartThings/zigbee-thermostat/fingerprints.yml +++ b/drivers/SmartThings/zigbee-thermostat/fingerprints.yml @@ -1,4 +1,9 @@ zigbeeManufacturer: + - id: "SONOFF/TRV-ZBT" + deviceLabel: SONOFF TRV-ZBT + manufacturer: SONOFF + model: TRV-ZBT + deviceProfileName: thermostat-sonoff-trv-zbt - id: "Vimar/02973-smart-wheel-thermostat" deviceLabel: Vimar Smart Thermostat manufacturer: Vimar diff --git a/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-sonoff-trv-zbt.yml b/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-sonoff-trv-zbt.yml new file mode 100644 index 0000000000..16ad1353b6 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-sonoff-trv-zbt.yml @@ -0,0 +1,38 @@ +name: thermostat-sonoff-trv-zbt +components: +- id: main + capabilities: + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + config: + values: + - key: "heatingSetpoint.value" + range: [5, 30] + - id: thermostatMode + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: Thermostat +preferences: +- name: "childLock" + title: "Child lock" + description: "Enable or disable the child lock of the thermostat." + required: false + preferenceType: enumeration + definition: + options: + 0: "Unlock" + 1: "Lock" + default: 0 +- name: "enterBluetoothPairing" + title: "Enter Bluetooth pairing" + description: "Enable to put the thermostat into Bluetooth pairing mode. The pairing window is controlled by the thermostat firmware." + required: false + preferenceType: boolean + definition: + default: false diff --git a/drivers/SmartThings/zigbee-thermostat/src/sonoff/can_handle.lua b/drivers/SmartThings/zigbee-thermostat/src/sonoff/can_handle.lua new file mode 100644 index 0000000000..9b270451e8 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/sonoff/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle(opts, driver, device) + if device:get_manufacturer() == "SONOFF" and device:get_model() == "TRV-ZBT" then + return true, require("sonoff") + end + + return false +end + +return can_handle diff --git a/drivers/SmartThings/zigbee-thermostat/src/sonoff/init.lua b/drivers/SmartThings/zigbee-thermostat/src/sonoff/init.lua new file mode 100644 index 0000000000..0097dae163 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/sonoff/init.lua @@ -0,0 +1,321 @@ +-- Copyright 2026 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" +local data_types = require "st.zigbee.data_types" +local generic_body = require "st.zigbee.generic_body" +local messages = require "st.zigbee.messages" +local zcl_messages = require "st.zigbee.zcl" +local zb_const = require "st.zigbee.constants" +local utils = require "st.utils" + +local PowerConfiguration = clusters.PowerConfiguration +local Thermostat = clusters.Thermostat + +local Battery = capabilities.battery +local Refresh = capabilities.refresh +local TemperatureMeasurement = capabilities.temperatureMeasurement +local ThermostatHeatingSetpoint = capabilities.thermostatHeatingSetpoint +local ThermostatMode = capabilities.thermostatMode + +local SONOFF_CLUSTER = 0xFC11 +local CHILD_LOCK_ATTR = 0x0000 +local WORK_MODE_ATTR = 0x0018 +local WORK_MODE_OFF = 0x01 +local WORK_MODE_MANUAL = 0x03 +local WORK_MODE_SCHEDULE = 0x04 +local WORK_MODE_TEMP_MANUAL = 0x05 + +local DEFAULT_MIN_SETPOINT = 5 +local DEFAULT_MAX_SETPOINT = 30 +local SETPOINT_STEP = 0.5 +local CHILD_LOCK_PREFERENCE = "childLock" +local BLUETOOTH_PAIRING_PREFERENCE = "enterBluetoothPairing" +local MIN_HEAT_SETPOINT_LIMIT_FIELD = "min_heat_setpoint_limit" +local MAX_HEAT_SETPOINT_LIMIT_FIELD = "max_heat_setpoint_limit" +local SUPPORTED_MODES = { + ThermostatMode.thermostatMode.off.NAME, + ThermostatMode.thermostatMode.heat.NAME, + ThermostatMode.thermostatMode.auto.NAME, +} + +local CONFIGURATIONS = { + { + cluster = Thermostat.ID, + attribute = Thermostat.attributes.LocalTemperature.ID, + minimum_interval = 10, + maximum_interval = 300, + data_type = Thermostat.attributes.LocalTemperature.base_type, + reportable_change = 50, + }, + { + cluster = Thermostat.ID, + attribute = Thermostat.attributes.OccupiedHeatingSetpoint.ID, + minimum_interval = 1, + maximum_interval = 600, + data_type = Thermostat.attributes.OccupiedHeatingSetpoint.base_type, + reportable_change = 50, + }, + { + cluster = Thermostat.ID, + attribute = Thermostat.attributes.SystemMode.ID, + minimum_interval = 1, + maximum_interval = 600, + data_type = Thermostat.attributes.SystemMode.base_type, + }, + { + cluster = Thermostat.ID, + attribute = Thermostat.attributes.MinHeatSetpointLimit.ID, + minimum_interval = 30, + maximum_interval = 21600, + data_type = Thermostat.attributes.MinHeatSetpointLimit.base_type, + reportable_change = 50, + }, + { + cluster = Thermostat.ID, + attribute = Thermostat.attributes.MaxHeatSetpointLimit.ID, + minimum_interval = 30, + maximum_interval = 21600, + data_type = Thermostat.attributes.MaxHeatSetpointLimit.base_type, + reportable_change = 50, + }, +} + +local function get_setpoint_range(device) + local reported_min = device:get_field(MIN_HEAT_SETPOINT_LIMIT_FIELD) + local reported_max = device:get_field(MAX_HEAT_SETPOINT_LIMIT_FIELD) + local min = reported_min and (reported_min / 100) or DEFAULT_MIN_SETPOINT + local max = reported_max and (reported_max / 100) or DEFAULT_MAX_SETPOINT + + if min > max then + min = DEFAULT_MIN_SETPOINT + max = DEFAULT_MAX_SETPOINT + end + + return min, max +end + +local function emit_setpoint_range(device) + local min, max = get_setpoint_range(device) + device:emit_event(ThermostatHeatingSetpoint.heatingSetpointRange({ + value = { + minimum = min, + maximum = max, + step = SETPOINT_STEP, + }, + unit = "C", + }, { visibility = { displayed = false } })) +end + +local function emit_supported_modes(device) + device:emit_event(ThermostatMode.supportedThermostatModes(SUPPORTED_MODES, { + visibility = { displayed = false }, + })) +end + +local function emit_mode(device, mode) + device:emit_event(ThermostatMode.thermostatMode[mode]()) +end + +local function command_argument(command, name) + if command.args ~= nil and command.args[name] ~= nil then + return command.args[name] + end + + return command.named_args and command.named_args[name] +end + +local function refresh(_, device) + local attributes = { + Thermostat.attributes.LocalTemperature, + Thermostat.attributes.OccupiedHeatingSetpoint, + Thermostat.attributes.SystemMode, + PowerConfiguration.attributes.BatteryPercentageRemaining, + } + + for _, attribute in ipairs(attributes) do + device:send(attribute:read(device)) + end +end + +local function configure(driver, device) + device:configure() + refresh(driver, device) + device:send(Thermostat.attributes.MinHeatSetpointLimit:read(device)) + device:send(Thermostat.attributes.MaxHeatSetpointLimit:read(device)) +end + +local function added(_, device) + emit_supported_modes(device) + emit_setpoint_range(device) +end + +local function init(driver, device) + for _, configuration in ipairs(CONFIGURATIONS) do + device:add_configured_attribute(configuration) + end + added(driver, device) + refresh(driver, device) +end + +local function info_changed(_, device, _, args) + local old_preferences = args.old_st_store and args.old_st_store.preferences or {} + local child_lock = device.preferences[CHILD_LOCK_PREFERENCE] + if child_lock ~= nil and old_preferences[CHILD_LOCK_PREFERENCE] ~= child_lock then + device:send(cluster_base.write_attribute( + device, + data_types.ClusterId(SONOFF_CLUSTER), + data_types.AttributeId(CHILD_LOCK_ATTR), + data_types.validate_or_build_type(tonumber(child_lock) == 1, data_types.Boolean, "payload") + )) + end + + local bluetooth_pairing = device.preferences[BLUETOOTH_PAIRING_PREFERENCE] + if bluetooth_pairing == true and old_preferences[BLUETOOTH_PAIRING_PREFERENCE] ~= true then + local zcl_header = zcl_messages.ZclHeader({ + cmd = data_types.ZCLCommandId(0x10), + }) + zcl_header.frame_ctrl:set_cluster_specific() + device:send(messages.ZigbeeMessageTx({ + address_header = messages.AddressHeader( + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + device:get_short_address(), + device:get_endpoint(SONOFF_CLUSTER) or 1, + zb_const.HA_PROFILE_ID, + SONOFF_CLUSTER + ), + body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_header, + zcl_body = generic_body.GenericBody(string.char(0x03, 0x01, 0x01)), + }), + })) + end +end + +local function local_temperature_handler(_, device, value) + if value.value ~= 0x8000 and value.value ~= -32768 then + device:emit_event(TemperatureMeasurement.temperature({ value = value.value / 100, unit = "C" })) + end +end + +local function heating_setpoint_handler(_, device, value) + if value.value ~= 0x8000 and value.value ~= -32768 then + device:emit_event(ThermostatHeatingSetpoint.heatingSetpoint({ value = value.value / 100, unit = "C" })) + end +end + +local function setpoint_limit_handler(field) + return function(_, device, value) + if value.value ~= 0x8000 and value.value ~= -32768 then + device:set_field(field, value.value, { persist = true }) + emit_setpoint_range(device) + end + end +end + +local function system_mode_handler(_, device, value) + if value.value == Thermostat.attributes.SystemMode.OFF then + emit_mode(device, ThermostatMode.thermostatMode.off.NAME) + elseif value.value == Thermostat.attributes.SystemMode.AUTO then + emit_mode(device, ThermostatMode.thermostatMode.auto.NAME) + elseif value.value == Thermostat.attributes.SystemMode.HEAT then + emit_mode(device, ThermostatMode.thermostatMode.heat.NAME) + end +end + +local function battery_handler(_, device, value) + device:emit_event(Battery.battery(utils.clamp_value(utils.round(value.value / 2), 0, 100))) +end + +local function work_mode_handler(_, device, value) + if value.value == WORK_MODE_OFF then + emit_mode(device, ThermostatMode.thermostatMode.off.NAME) + elseif value.value == WORK_MODE_SCHEDULE then + emit_mode(device, ThermostatMode.thermostatMode.auto.NAME) + elseif value.value == WORK_MODE_MANUAL or value.value == WORK_MODE_TEMP_MANUAL then + emit_mode(device, ThermostatMode.thermostatMode.heat.NAME) + end +end + +local function set_heating_setpoint(_, device, command) + local setpoint = command_argument(command, "setpoint") + if setpoint >= 40 then + setpoint = utils.f_to_c(setpoint) + end + + local min, max = get_setpoint_range(device) + setpoint = utils.clamp_value(setpoint, min, max) + -- The TRV-ZBT persists heating setpoints in 0.5 C increments. + setpoint = math.floor((setpoint / SETPOINT_STEP) + 0.000001) * SETPOINT_STEP + device:send(Thermostat.attributes.OccupiedHeatingSetpoint:write(device, utils.round(setpoint * 100))) + device:emit_event(ThermostatHeatingSetpoint.heatingSetpoint({ value = setpoint, unit = "C" })) +end + +local function set_mode(_, device, command) + local mode_to_system_mode = { + [ThermostatMode.thermostatMode.off.NAME] = Thermostat.attributes.SystemMode.OFF, + [ThermostatMode.thermostatMode.heat.NAME] = Thermostat.attributes.SystemMode.HEAT, + [ThermostatMode.thermostatMode.auto.NAME] = Thermostat.attributes.SystemMode.AUTO, + } + local mode = command_argument(command, "mode") + local system_mode = mode_to_system_mode[mode] + if system_mode == nil then + return + end + + device:send(Thermostat.attributes.SystemMode:write(device, system_mode)) + emit_mode(device, mode) +end + +local function mode_setter(mode) + return function(driver, device, command) + set_mode(driver, device, { args = { mode = mode } }) + end +end + +local sonoff = { + NAME = "SONOFF TRV-ZBT", + capability_handlers = { + [Refresh.ID] = { + [Refresh.commands.refresh.NAME] = refresh, + }, + [ThermostatHeatingSetpoint.ID] = { + [ThermostatHeatingSetpoint.commands.setHeatingSetpoint.NAME] = set_heating_setpoint, + }, + [ThermostatMode.ID] = { + [ThermostatMode.commands.setThermostatMode.NAME] = set_mode, + [ThermostatMode.commands.off.NAME] = mode_setter(ThermostatMode.thermostatMode.off.NAME), + [ThermostatMode.commands.heat.NAME] = mode_setter(ThermostatMode.thermostatMode.heat.NAME), + [ThermostatMode.commands.auto.NAME] = mode_setter(ThermostatMode.thermostatMode.auto.NAME), + }, + }, + zigbee_handlers = { + attr = { + [PowerConfiguration.ID] = { + [PowerConfiguration.attributes.BatteryPercentageRemaining.ID] = battery_handler, + }, + [Thermostat.ID] = { + [Thermostat.attributes.LocalTemperature.ID] = local_temperature_handler, + [Thermostat.attributes.OccupiedHeatingSetpoint.ID] = heating_setpoint_handler, + [Thermostat.attributes.MinHeatSetpointLimit.ID] = setpoint_limit_handler(MIN_HEAT_SETPOINT_LIMIT_FIELD), + [Thermostat.attributes.MaxHeatSetpointLimit.ID] = setpoint_limit_handler(MAX_HEAT_SETPOINT_LIMIT_FIELD), + [Thermostat.attributes.SystemMode.ID] = system_mode_handler, + }, + [SONOFF_CLUSTER] = { + [WORK_MODE_ATTR] = work_mode_handler, + }, + }, + }, + lifecycle_handlers = { + added = added, + init = init, + doConfigure = configure, + infoChanged = info_changed, + }, + can_handle = require "sonoff.can_handle", +} + +return sonoff diff --git a/drivers/SmartThings/zigbee-thermostat/src/sub_drivers.lua b/drivers/SmartThings/zigbee-thermostat/src/sub_drivers.lua index 3b535c3cf0..12bccc65a7 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/sub_drivers.lua @@ -3,6 +3,7 @@ local lazy_load_if_possible = require "lazy_load_subdriver" local sub_drivers = { + lazy_load_if_possible("sonoff"), lazy_load_if_possible("zenwithin"), lazy_load_if_possible("fidure"), lazy_load_if_possible("sinope"), diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_sonoff_trv_zbt.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_sonoff_trv_zbt.lua new file mode 100644 index 0000000000..150ad7bfe2 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_sonoff_trv_zbt.lua @@ -0,0 +1,429 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local device_management = require "st.zigbee.device_management" +local generic_body = require "st.zigbee.generic_body" +local messages = require "st.zigbee.messages" +local zcl_messages = require "st.zigbee.zcl" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local zb_const = require "st.zigbee.constants" + +local PowerConfiguration = clusters.PowerConfiguration +local Thermostat = clusters.Thermostat +local TemperatureMeasurement = clusters.TemperatureMeasurement + +local SONOFF_CLUSTER = 0xFC11 +local CHILD_LOCK_ATTR = 0x0000 +local WORK_MODE_ATTR = 0x0018 +local BUTTON_COMMAND = 0x10 + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("thermostat-sonoff-trv-zbt.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "SONOFF", + model = "TRV-ZBT", + server_clusters = { 0x0001, 0x0201, 0x0402, SONOFF_CLUSTER }, + }, + }, +}) + +zigbee_test_utils.prepare_zigbee_env_info() +test.set_test_init_function(function() + test.mock_device.add_test_device(mock_device) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes( + { "off", "heat", "auto" }, { visibility = { displayed = false } } + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpointRange({ + value = { minimum = 5, maximum = 30, step = 0.5 }, + unit = "C", + }, { visibility = { displayed = false } })) + ) + for _, attribute in ipairs({ + Thermostat.attributes.LocalTemperature, + Thermostat.attributes.OccupiedHeatingSetpoint, + Thermostat.attributes.SystemMode, + PowerConfiguration.attributes.BatteryPercentageRemaining, + }) do + test.socket.zigbee:__expect_send({ mock_device.id, attribute:read(mock_device) }) + end +end) + +local function bluetooth_pairing_command(device) + local zcl_header = zcl_messages.ZclHeader({ + cmd = data_types.ZCLCommandId(BUTTON_COMMAND), + }) + zcl_header.frame_ctrl:set_cluster_specific() + + return messages.ZigbeeMessageTx({ + address_header = messages.AddressHeader( + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + device:get_short_address(), + 1, + zb_const.HA_PROFILE_ID, + SONOFF_CLUSTER + ), + body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_header, + zcl_body = generic_body.GenericBody(string.char(0x03, 0x01, 0x01)), + }), + }) +end + +test.register_message_test( + "SONOFF TRV-ZBT reports local temperature", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.LocalTemperature:build_test_attr_report(mock_device, 2150) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = "C" })), + }, + }, + { min_api_version = 14 } +) + +test.register_message_test( + "SONOFF TRV-ZBT reports setpoint and battery percentage", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.OccupiedHeatingSetpoint:build_test_attr_report(mock_device, 2250) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 22.5, unit = "C" })), + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device, 146) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(73)), + }, + }, + { min_api_version = 14 } +) + +test.register_message_test( + "SONOFF TRV-ZBT maps standard and proprietary modes", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, Thermostat.attributes.SystemMode.HEAT) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.heat()), + }, + { + channel = "zigbee", + direction = "receive", + message = { + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, SONOFF_CLUSTER, { + { WORK_MODE_ATTR, data_types.Uint8.ID, 0x04 }, + }), + }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.auto()), + }, + }, + { min_api_version = 14 } +) + +test.register_coroutine_test( + "SONOFF TRV-ZBT writes setpoints in Celsius and Fahrenheit", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "thermostatHeatingSetpoint", + component = "main", + command = "setHeatingSetpoint", + args = {}, + named_args = { setpoint = 21.5 }, + }, + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 2150), + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 21.5, unit = "C" })) + ) + test.wait_for_events() + + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "thermostatHeatingSetpoint", + component = "main", + command = "setHeatingSetpoint", + args = {}, + named_args = { setpoint = 68 }, + }, + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 2000), + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 20.0, unit = "C" })) + ) + test.wait_for_events() + + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "thermostatHeatingSetpoint", + component = "main", + command = "setHeatingSetpoint", + args = {}, + named_args = { setpoint = 64 }, + }, + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 1750), + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 17.5, unit = "C" })) + ) + test.wait_for_events() + end, + { min_api_version = 14 } +) + +test.register_coroutine_test( + "SONOFF TRV-ZBT updates setpoint range from reported limits", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.MinHeatSetpointLimit:build_test_attr_report(mock_device, 1200), + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpointRange({ + value = { minimum = 12.0, maximum = 30, step = 0.5 }, + unit = "C", + }, { visibility = { displayed = false } })) + ) + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.MaxHeatSetpointLimit:build_test_attr_report(mock_device, 2200), + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpointRange({ + value = { minimum = 12.0, maximum = 22.0, step = 0.5 }, + unit = "C", + }, { visibility = { displayed = false } })) + ) + test.wait_for_events() + + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "thermostatHeatingSetpoint", + component = "main", + command = "setHeatingSetpoint", + args = {}, + named_args = { setpoint = 25 }, + }, + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 2200), + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 22.0, unit = "C" })) + ) + test.wait_for_events() + end, + { min_api_version = 14 } +) + +test.register_coroutine_test( + "SONOFF TRV-ZBT starts Bluetooth pairing when the preference is enabled", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { enterBluetoothPairing = true }, + })) + test.socket.zigbee:__expect_send({ mock_device.id, bluetooth_pairing_command(mock_device) }) + test.wait_for_events() + + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { enterBluetoothPairing = false }, + })) + test.wait_for_events() + end, + { min_api_version = 14 } +) + +test.register_coroutine_test( + "SONOFF TRV-ZBT writes the child lock preference", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { childLock = 1 }, + })) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_attribute( + mock_device, + data_types.ClusterId(SONOFF_CLUSTER), + data_types.AttributeId(CHILD_LOCK_ATTR), + data_types.validate_or_build_type(true, data_types.Boolean, "payload") + ), + }) + test.wait_for_events() + + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { childLock = 0 }, + })) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_attribute( + mock_device, + data_types.ClusterId(SONOFF_CLUSTER), + data_types.AttributeId(CHILD_LOCK_ATTR), + data_types.validate_or_build_type(false, data_types.Boolean, "payload") + ), + }) + test.wait_for_events() + end, + { min_api_version = 14 } +) + +test.register_coroutine_test( + "SONOFF TRV-ZBT writes thermostat mode and refreshes required attributes", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { + capability = "thermostatMode", + component = "main", + command = "setThermostatMode", + args = {}, + named_args = { mode = "auto" }, + }, + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.SystemMode:write(mock_device, Thermostat.attributes.SystemMode.AUTO), + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.auto()) + ) + test.wait_for_events() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} }, + }) + for _, attribute in ipairs({ + Thermostat.attributes.LocalTemperature, + Thermostat.attributes.OccupiedHeatingSetpoint, + Thermostat.attributes.SystemMode, + PowerConfiguration.attributes.BatteryPercentageRemaining, + }) do + test.socket.zigbee:__expect_send({ mock_device.id, attribute:read(mock_device) }) + end + test.wait_for_events() + end, + { min_api_version = 14 } +) + +test.register_coroutine_test( + "SONOFF TRV-ZBT configures reporting and cluster bindings", + function() + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + 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, + device_management.build_bind_request(mock_device, Thermostat.ID, zigbee_test_utils.mock_hub_eui), + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.build_bind_request(mock_device, PowerConfiguration.ID, zigbee_test_utils.mock_hub_eui), + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + device_management.build_bind_request(mock_device, TemperatureMeasurement.ID, zigbee_test_utils.mock_hub_eui), + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.LocalTemperature:configure_reporting(mock_device, 10, 300, 50), + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:configure_reporting(mock_device, 1, 600, 50), + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.SystemMode:configure_reporting(mock_device, 1, 600, 1), + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.MinHeatSetpointLimit:configure_reporting(mock_device, 30, 21600, 50), + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.MaxHeatSetpointLimit:configure_reporting(mock_device, 30, 21600, 50), + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:configure_reporting(mock_device, 30, 21600, 1), + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100), + }) + for _, attribute in ipairs({ + Thermostat.attributes.LocalTemperature, + Thermostat.attributes.OccupiedHeatingSetpoint, + Thermostat.attributes.SystemMode, + PowerConfiguration.attributes.BatteryPercentageRemaining, + }) do + test.socket.zigbee:__expect_send({ mock_device.id, attribute:read(mock_device) }) + end + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.MinHeatSetpointLimit:read(mock_device), + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.MaxHeatSetpointLimit:read(mock_device), + }) + test.wait_for_events() + end, + { min_api_version = 14 } +) + +test.run_registered_tests()